Merge pull request #371 from ylei-tsubame/server-vnc-rework

Web UI: rework server VNC
main
Digimer 1 year ago committed by GitHub
commit 74008ce744
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 22
      Anvil/Tools.pm
  2. 14
      Anvil/Tools/Remote.pm
  3. 16
      Anvil/Tools/Server.pm
  4. 2
      anvil.spec.in
  5. 92
      libvirt/hooks/qemu.d/ws
  6. 2
      striker-ui-api/out/index.js
  7. 213
      striker-ui-api/package-lock.json
  8. 1
      striker-ui-api/package.json
  9. 5
      striker-ui-api/src/index.ts
  10. 43
      striker-ui-api/src/lib/accessModule.ts
  11. 1
      striker-ui-api/src/lib/consts/SERVER_PATHS.ts
  12. 1
      striker-ui-api/src/lib/request_handlers/command/index.ts
  13. 52
      striker-ui-api/src/lib/request_handlers/command/manageVncSshTunnel.ts
  14. 26
      striker-ui-api/src/lib/request_handlers/server/getServerDetail.ts
  15. 1
      striker-ui-api/src/middlewares/index.ts
  16. 52
      striker-ui-api/src/middlewares/proxyServerVnc.ts
  17. 2
      striker-ui-api/src/routes/command.ts
  18. 13
      striker-ui-api/src/types/ApiServer.d.ts
  19. 399
      striker-ui/components/Display/FullSize.tsx
  20. 151
      striker-ui/components/Display/VncDisplay.tsx
  21. 1
      striker-ui/out/_next/static/80gKnCQuMqHJTSo3gTfw0/_buildManifest.js
  22. 1
      striker-ui/out/_next/static/chunks/116-dfb7b81f7280bb6d.js
  23. 1
      striker-ui/out/_next/static/chunks/203-2f903a41f9b4e31e.js
  24. 1
      striker-ui/out/_next/static/chunks/62-532ed713980da8db.js
  25. 1
      striker-ui/out/_next/static/chunks/665.ae67dcf3c1b6f7f6.js
  26. 1
      striker-ui/out/_next/static/chunks/665.b5fa539eea66745a.js
  27. 1
      striker-ui/out/_next/static/chunks/746-a9b6e396d532b9a2.js
  28. 1
      striker-ui/out/_next/static/chunks/780-e8b3396d257460a4.js
  29. 1
      striker-ui/out/_next/static/chunks/825-d34974d169ea09cc.js
  30. 1
      striker-ui/out/_next/static/chunks/906-86856e16ad160b72.js
  31. 1
      striker-ui/out/_next/static/chunks/94-db0af749b6e45543.js
  32. 1
      striker-ui/out/_next/static/chunks/pages/anvil-c1177b17efcafc34.js
  33. 1
      striker-ui/out/_next/static/chunks/pages/anvil-fbef5033b416c0dd.js
  34. 2
      striker-ui/out/_next/static/chunks/pages/config-0ecdb2b2b3f8c089.js
  35. 2
      striker-ui/out/_next/static/chunks/pages/file-manager-1a707639a4834587.js
  36. 2
      striker-ui/out/_next/static/chunks/pages/index-1f8f0ad3b3894dbc.js
  37. 2
      striker-ui/out/_next/static/chunks/pages/init-ae1befa8975f7914.js
  38. 2
      striker-ui/out/_next/static/chunks/pages/login-b5de0cd2f49998d6.js
  39. 2
      striker-ui/out/_next/static/chunks/pages/manage-element-2b1d8792c2a5bf47.js
  40. 1
      striker-ui/out/_next/static/chunks/pages/server-bf2d408a68a09e13.js
  41. 1
      striker-ui/out/_next/static/chunks/pages/server-db52258419acacf3.js
  42. 2
      striker-ui/out/_next/static/chunks/webpack-b267a65404defb57.js
  43. 1
      striker-ui/out/_next/static/emoQsTp-EllqO7CyhhLqo/_buildManifest.js
  44. 0
      striker-ui/out/_next/static/emoQsTp-EllqO7CyhhLqo/_middlewareManifest.js
  45. 0
      striker-ui/out/_next/static/emoQsTp-EllqO7CyhhLqo/_ssgManifest.js
  46. 2
      striker-ui/out/anvil.html
  47. 2
      striker-ui/out/config.html
  48. 2
      striker-ui/out/file-manager.html
  49. 2
      striker-ui/out/index.html
  50. 2
      striker-ui/out/init.html
  51. 2
      striker-ui/out/login.html
  52. 2
      striker-ui/out/manage-element.html
  53. 2
      striker-ui/out/server.html
  54. 7
      striker-ui/pages/server/index.tsx
  55. 6
      striker-ui/types/FullSize.d.ts
  56. 32
      striker-ui/types/VncDisplay.d.ts
  57. 3
      tools/Makefile.am
  58. 519
      tools/anvil-manage-vnc-pipe
  59. 1113
      tools/striker-manage-vnc-pipes
  60. 136
      tools/striker-open-ssh-tunnel

@ -195,6 +195,8 @@ sub new
# Set passed parameters if needed. # Set passed parameters if needed.
my $debug = 3; my $debug = 3;
my $on_sig_int;
my $on_sig_term;
if (ref($parameter) eq "HASH") if (ref($parameter) eq "HASH")
{ {
# Local parameters... # Local parameters...
@ -210,6 +212,9 @@ sub new
{ {
$debug = $parameter->{debug}; $debug = $parameter->{debug};
} }
$on_sig_int = $parameter->{on_sig_int};
$on_sig_term = $parameter->{on_sig_term};
} }
elsif ($parameter) elsif ($parameter)
{ {
@ -219,8 +224,16 @@ sub new
} }
# This will help clean up if we catch a signal. # This will help clean up if we catch a signal.
$SIG{INT} = sub { $anvil->catch_sig({signal => "INT"}); }; $SIG{INT} = sub {
$SIG{TERM} = sub { $anvil->catch_sig({signal => "TERM"}); }; $on_sig_int->({ debug => $debug }) if (ref($on_sig_int) eq "CODE");
$anvil->catch_sig({signal => "INT"});
};
$SIG{TERM} = sub {
$on_sig_term->({ debug => $debug }) if (ref($on_sig_term) eq "CODE");
$anvil->catch_sig({signal => "TERM"});
};
# This sets the environment this program is running in. # This sets the environment this program is running in.
if ($ENV{SERVER_NAME}) if ($ENV{SERVER_NAME})
@ -1140,6 +1153,7 @@ sub _set_paths
'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall", 'anvil-manage-firewall' => "/usr/sbin/anvil-manage-firewall",
'anvil-manage-keys' => "/usr/sbin/anvil-manage-keys", 'anvil-manage-keys' => "/usr/sbin/anvil-manage-keys",
'anvil-manage-power' => "/usr/sbin/anvil-manage-power", 'anvil-manage-power' => "/usr/sbin/anvil-manage-power",
'anvil-manage-vnc-pipe' => "/usr/sbin/anvil-manage-vnc-pipe",
'anvil-migrate-server' => "/usr/sbin/anvil-migrate-server", 'anvil-migrate-server' => "/usr/sbin/anvil-migrate-server",
'anvil-parse-fence-agents' => "/usr/sbin/anvil-parse-fence-agents", 'anvil-parse-fence-agents' => "/usr/sbin/anvil-parse-fence-agents",
'anvil-provision-server' => "/usr/sbin/anvil-provision-server", 'anvil-provision-server' => "/usr/sbin/anvil-provision-server",
@ -1260,6 +1274,7 @@ sub _set_paths
snmpget => "/usr/bin/snmpget", snmpget => "/usr/bin/snmpget",
snmpset => "/usr/bin/snmpset", snmpset => "/usr/bin/snmpset",
'sort' => "/usr/bin/sort", 'sort' => "/usr/bin/sort",
ss => "/usr/sbin/ss",
'ssh-keygen' => "/usr/bin/ssh-keygen", 'ssh-keygen' => "/usr/bin/ssh-keygen",
'ssh-keyscan' => "/usr/bin/ssh-keyscan", 'ssh-keyscan' => "/usr/bin/ssh-keyscan",
'stat' => "/usr/bin/stat", 'stat' => "/usr/bin/stat",
@ -1270,8 +1285,6 @@ sub _set_paths
'striker-initialize-host' => "/usr/sbin/striker-initialize-host", 'striker-initialize-host' => "/usr/sbin/striker-initialize-host",
'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target", 'striker-manage-install-target' => "/usr/sbin/striker-manage-install-target",
'striker-manage-peers' => "/usr/sbin/striker-manage-peers", 'striker-manage-peers' => "/usr/sbin/striker-manage-peers",
'striker-manage-vnc-pipes' => "/usr/sbin/striker-manage-vnc-pipes",
'striker-open-ssh-tunnel' => "/usr/sbin/striker-open-ssh-tunnel",
'striker-parse-oui' => "/usr/sbin/striker-parse-oui", 'striker-parse-oui' => "/usr/sbin/striker-parse-oui",
'striker-prep-database' => "/usr/sbin/striker-prep-database", 'striker-prep-database' => "/usr/sbin/striker-prep-database",
'striker-scan-network' => "/usr/sbin/striker-scan-network", 'striker-scan-network' => "/usr/sbin/striker-scan-network",
@ -1295,6 +1308,7 @@ sub _set_paths
uuidgen => "/usr/bin/uuidgen", uuidgen => "/usr/bin/uuidgen",
virsh => "/usr/bin/virsh", virsh => "/usr/bin/virsh",
'virt-install' => "/usr/bin/virt-install", 'virt-install' => "/usr/bin/virt-install",
websockify => "/usr/bin/websockify",
wipefs => "/usr/sbin/wipefs", wipefs => "/usr/sbin/wipefs",
vgs => "/usr/sbin/vgs", vgs => "/usr/sbin/vgs",
vgscan => "/usr/sbin/vgscan", vgscan => "/usr/sbin/vgscan",

@ -236,6 +236,10 @@ If set, the method will use the given log level. Valid values are integers betwe
If set, and if an existing cached connection is open, it will be closed and a new connection to the target will be established. If set, and if an existing cached connection is open, it will be closed and a new connection to the target will be established.
=head3 ossh_opts (optional, default [])
This is a ref to an array of named elements which extends the options passed to Net:OpenSSH->new().
=head3 password (optional) =head3 password (optional)
This is the password used to connect to the remote target as the given user. This is the password used to connect to the remote target as the given user.
@ -284,15 +288,17 @@ sub call
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Remote->call()" }}); $anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => $debug, key => "log_0125", variables => { method => "Remote->call()" }});
# Get the target and port so that we can create the ssh_fh key # Get the target and port so that we can create the ssh_fh key
my $port = $parameter->{port} ? $parameter->{port} : 22; my $port = $parameter->{port} ? $parameter->{port} : 22;
my $target = defined $parameter->{target} ? $parameter->{target} : ""; my $target = defined $parameter->{target} ? $parameter->{target} : "";
my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root"; my $remote_user = defined $parameter->{remote_user} ? $parameter->{remote_user} : "root";
my $ossh_opts = ref($parameter->{ossh_opts}) eq "ARRAY" ? $parameter->{ossh_opts} : [];
my $ssh_fh_key = $remote_user."\@".$target.":".$port; my $ssh_fh_key = $remote_user."\@".$target.":".$port;
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => { $anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => $debug, list => {
's1:remote_user' => $remote_user, 's1:remote_user' => $remote_user,
's2:target' => $target, 's2:target' => $target,
's3:port' => $port, 's3:port' => $port,
's4:ssh_fh_key' => $ssh_fh_key, 's4:ssh_fh_key' => $ssh_fh_key,
's5:ossh_opts' => $ossh_opts,
}}); }});
# This will store the SSH file handle for the given target after the initial connection. # This will store the SSH file handle for the given target after the initial connection.
@ -477,6 +483,7 @@ sub call
user => $remote_user, user => $remote_user,
port => $port, port => $port,
batch_mode => 1, batch_mode => 1,
@$ossh_opts,
); );
}; };
$connect_output =~ s/\r//gs; $connect_output =~ s/\r//gs;
@ -574,6 +581,7 @@ sub call
port => $port, port => $port,
passwd => $password, passwd => $password,
batch_mode => 1, batch_mode => 1,
@$ossh_opts,
); );
}; };
$connect_output =~ s/\n$//; $connect_output =~ s/\n$//;

@ -501,8 +501,9 @@ sub find_processes
my $self = shift; my $self = shift;
my $parameters = shift; my $parameters = shift;
my $anvil = $self->parent; my $anvil = $self->parent;
my $base_vnc_port = $parameters->{base_vnc_port} // 5900; my $base_vnc_port = $parameters->{base_vnc_port} || 5900;
my $debug = $parameters->{debug} // 3; my $debug = $parameters->{debug} || 3;
my $ps_name = $parameters->{ps_name} // "qemu-kvm";
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters }); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters });
@ -515,12 +516,11 @@ sub find_processes
# Servers only exist on non-striker # Servers only exist on non-striker
return (1) if ($anvil->data->{sys}{host_type} eq "striker"); return (1) if ($anvil->data->{sys}{host_type} eq "striker");
my $grep = $anvil->data->{path}{exe}{'grep'}; my $nc = $anvil->data->{path}{exe}{'nc'};
my $nc = $anvil->data->{path}{exe}{'nc'}; my $pgrep = $anvil->data->{path}{exe}{'pgrep'};
my $ps = $anvil->data->{path}{exe}{'ps'}; my $sed = $anvil->data->{path}{exe}{'sed'};
my $sed = $anvil->data->{path}{exe}{'sed'};
my $ps_call = "$ps -ef | $grep '[q]emu-kvm' | $sed -E 's/^.*guest=([^,]+).*-uuid[[:space:]]+([^[:space:]]+)(.*-vnc[[:space:]]+([[:digit:].:]+))?.*\$/\\2,\\1,\\4/'"; my $ps_call = "$pgrep -a '$ps_name' | $sed -E 's/^.*guest=([^,]+).*-uuid[[:space:]]+([^[:space:]]+)(.*-vnc[[:space:]]+([[:digit:].:]+))?.*\$/\\2,\\1,\\4/'";
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { ps_call => $ps_call }}); $anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { ps_call => $ps_call }});
@ -540,7 +540,7 @@ sub find_processes
server_vnc => $vnc, server_vnc => $vnc,
}}); }});
$result->{uuids}{$uuid} = { name => $name }; $result->{uuids}{$uuid} = { name => $name, uuid => $uuid };
# Record name to UUID mapping # Record name to UUID mapping
$result->{names}{$name} = $uuid; $result->{names}{$name} = $uuid;

@ -257,6 +257,8 @@ getent passwd %{suiapi} >/dev/null \
%{suiapi} %{suiapi}
if [ $1 -gt 1 ]; then # >1=Upgrade if [ $1 -gt 1 ]; then # >1=Upgrade
# Disable and stop apache to free the port.
systemctl disable --now httpd.service
# Transfer files owned by apache to Striker UI API user. # Transfer files owned by apache to Striker UI API user.
chown -R --from apache %{suiapi}: /mnt chown -R --from apache %{suiapi}: /mnt
chown -R --from apache %{suiapi}: %{_localstatedir}/www chown -R --from apache %{suiapi}: %{_localstatedir}/www

@ -1,51 +1,45 @@
#!/bin/bash #!/bin/bash
#
# Note: libvirt hook scripts execute with uid=0(root) gid=0(root) for all
# operations, i.e., started, stopped.
#
# TODO: re-enable after the possible libvirt deadlock is fixed. function log {
exit 0 echo "$(date +"%Y/%m/%d %T"):libvirt_hooks:ws; $@" >>/var/log/anvil.log;
}
{
echo "wsargs=$@" log "wsargs=$@"
domain_xml=$(</dev/stdin) domain_xml=$(</dev/stdin)
guest_name="$1" operation="$2"
operation="$2"
# Operation migrate will:
# Operation migrate will: # 1. Trigger migrate->prepare->start->started operation on the destination host.
# 1. Trigger migrate->prepare->start->started operation on the destination host. # 2. Trigger stopped->release operations on the source host.
# 2. Trigger stopped->release operations on the source host. if [[ ! $operation =~ ^(started|stopped)$ ]]
if [[ "$operation" == "started" || "$operation" == "stopped" ]] then
then exit
ws_open_flag="" fi
ws_port_flag=""
ws_suuid_flag="" guest_uuid=$( sed -En "s/^.*<uuid>([^[:space:]]+)<.*$/\1/p" <<<"$domain_xml" )
ws_server_uuid_flag="--server-uuid $guest_uuid"
if [[ "$operation" == "started" ]]
then ws_open_flag=""
ws_open_flag="--open" ws_port_flag=""
# Cannot call $ virsh vncdisplay... because libvirt hooks if [[ $operation == "started" ]]
# cannot call anything related to libvirt, i.e., virsh, because then
# a deadlock will happen. ws_open_flag="--open"
server_vnc_port=$( grep "<graphics.*type=['\"]vnc['\"]" <<<$domain_xml | grep -oPm1 "(?<=port=['\"])\d+" )
ws_port_flag="--server-vnc-port ${server_vnc_port}" # Cannot call $ virsh vncdisplay... because libvirt hooks
# cannot call anything related to libvirt, i.e., virsh, because
server_uuid=$( grep -oPm1 "(?<=<uuid>)[^\s]+(?=<)" <<<$domain_xml ) # a deadlock will happen.
ws_suuid_flag="--server-uuid ${server_uuid}" server_vnc_port=$( sed -En "s/^.*<graphics.*type=['\"]vnc['\"].*port=['\"]([[:digit:]]+)['\"].*$/\1/p" <<<"$domain_xml" )
ws_port_flag="--server-vnc-port $server_vnc_port"
local_host_uuid=$(</etc/anvil/host.uuid) fi
update_sql="UPDATE servers SET server_host_uuid = '$local_host_uuid' WHERE server_name = '$guest_name';" ws_command_args="$ws_server_uuid_flag $ws_port_flag $ws_open_flag"
echo "wsupdate=$update_sql"
anvil-access-module --query "$update_sql" --mode write log "wscmd_args=$ws_command_args"
fi
anvil-manage-vnc-pipe $ws_command_args &
ws_command_args="--server \"$guest_name\" $ws_suuid_flag --server-host-uuid local $ws_port_flag --component ws $ws_open_flag"
echo "wscmd=$ws_command_args"
striker-manage-vnc-pipes --server "$guest_name" $ws_suuid_flag --server-host-uuid local $ws_port_flag --component ws $ws_open_flag
echo "wscmd_exit=$?"
# Don't interrupt libvirt regardless of whether websockify gets setup
# successfully.
exit 0
fi
} >>/var/log/anvil.log

File diff suppressed because one or more lines are too long

@ -13,6 +13,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"format-data-size": "^0.1.0", "format-data-size": "^0.1.0",
"http-proxy-middleware": "^3.0.0-beta.1",
"multer": "^1.4.4", "multer": "^1.4.4",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
@ -2430,6 +2431,14 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"node_modules/@types/http-proxy": {
"version": "1.17.11",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz",
"integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.10", "version": "7.0.10",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
@ -2460,8 +2469,7 @@
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "17.0.22", "version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz",
"integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw=="
"dev": true
}, },
"node_modules/@types/passport": { "node_modules/@types/passport": {
"version": "1.0.12", "version": "1.0.12",
@ -3283,7 +3291,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
}, },
@ -4257,6 +4264,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/events": { "node_modules/events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -4471,7 +4483,6 @@
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -4545,6 +4556,25 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true "dev": true
}, },
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/format-data-size": { "node_modules/format-data-size": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/format-data-size/-/format-data-size-0.1.0.tgz", "resolved": "https://registry.npmjs.org/format-data-size/-/format-data-size-0.1.0.tgz",
@ -4779,6 +4809,56 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dependencies": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/http-proxy-middleware": {
"version": "3.0.0-beta.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0-beta.1.tgz",
"integrity": "sha512-hdiTlVVoaxncf239csnEpG5ew2lRWnoNR1PMWOO6kYulSphlrfLs5JFZtFVH3R5EUWSZNMkeUqvkvfctuWaK8A==",
"dependencies": {
"@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.5"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/http-proxy-middleware/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-proxy-middleware/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -4978,7 +5058,6 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -4987,7 +5066,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
}, },
@ -5011,7 +5089,6 @@
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -5031,6 +5108,17 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-plain-object": { "node_modules/is-plain-object": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@ -5385,13 +5473,12 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.4", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"dependencies": { "dependencies": {
"braces": "^3.0.1", "braces": "^3.0.2",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -5799,7 +5886,6 @@
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
}, },
@ -6053,6 +6139,11 @@
"jsesc": "bin/jsesc" "jsesc": "bin/jsesc"
} }
}, },
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.20.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
@ -6498,7 +6589,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@ -8694,6 +8784,14 @@
"@types/express": "*" "@types/express": "*"
} }
}, },
"@types/http-proxy": {
"version": "1.17.11",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz",
"integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==",
"requires": {
"@types/node": "*"
}
},
"@types/json-schema": { "@types/json-schema": {
"version": "7.0.10", "version": "7.0.10",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.10.tgz",
@ -8724,8 +8822,7 @@
"@types/node": { "@types/node": {
"version": "17.0.22", "version": "17.0.22",
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.22.tgz",
"integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw==", "integrity": "sha512-8FwbVoG4fy+ykY86XCAclKZDORttqE5/s7dyWZKLXTdv3vRy5HozBEinG5IqhvPXXzIZEcTVbuHlQEI6iuwcmw=="
"dev": true
}, },
"@types/passport": { "@types/passport": {
"version": "1.0.12", "version": "1.0.12",
@ -9349,7 +9446,6 @@
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": { "requires": {
"fill-range": "^7.0.1" "fill-range": "^7.0.1"
} }
@ -10091,6 +10187,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
}, },
"eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"events": { "events": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@ -10256,7 +10357,6 @@
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": { "requires": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
} }
@ -10312,6 +10412,11 @@
"integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==",
"dev": true "dev": true
}, },
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"format-data-size": { "format-data-size": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/format-data-size/-/format-data-size-0.1.0.tgz", "resolved": "https://registry.npmjs.org/format-data-size/-/format-data-size-0.1.0.tgz",
@ -10477,6 +10582,44 @@
"toidentifier": "1.0.1" "toidentifier": "1.0.1"
} }
}, },
"http-proxy": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"requires": {
"eventemitter3": "^4.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-proxy-middleware": {
"version": "3.0.0-beta.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0-beta.1.tgz",
"integrity": "sha512-hdiTlVVoaxncf239csnEpG5ew2lRWnoNR1PMWOO6kYulSphlrfLs5JFZtFVH3R5EUWSZNMkeUqvkvfctuWaK8A==",
"requires": {
"@types/http-proxy": "^1.17.10",
"debug": "^4.3.4",
"http-proxy": "^1.18.1",
"is-glob": "^4.0.1",
"is-plain-obj": "^3.0.0",
"micromatch": "^4.0.5"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"human-signals": { "human-signals": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@ -10614,14 +10757,12 @@
"is-extglob": { "is-extglob": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
"dev": true
}, },
"is-glob": { "is-glob": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": { "requires": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
} }
@ -10635,8 +10776,7 @@
"is-number": { "is-number": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
"dev": true
}, },
"is-number-object": { "is-number-object": {
"version": "1.0.6", "version": "1.0.6",
@ -10647,6 +10787,11 @@
"has-tostringtag": "^1.0.0" "has-tostringtag": "^1.0.0"
} }
}, },
"is-plain-obj": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz",
"integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA=="
},
"is-plain-object": { "is-plain-object": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@ -10908,13 +11053,12 @@
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
}, },
"micromatch": { "micromatch": {
"version": "4.0.4", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.2",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
} }
}, },
"mime": { "mime": {
@ -11209,8 +11353,7 @@
"picomatch": { "picomatch": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
"dev": true
}, },
"pkg-dir": { "pkg-dir": {
"version": "4.2.0", "version": "4.2.0",
@ -11392,6 +11535,11 @@
} }
} }
}, },
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"resolve": { "resolve": {
"version": "1.20.0", "version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
@ -11706,7 +11854,6 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": { "requires": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }

@ -16,6 +16,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"format-data-size": "^0.1.0", "format-data-size": "^0.1.0",
"http-proxy-middleware": "^3.0.0-beta.1",
"multer": "^1.4.4", "multer": "^1.4.4",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",

@ -3,12 +3,13 @@ import { getgid, getuid, setgid, setuid } from 'process';
import { PGID, PUID, PORT, ECODE_DROP_PRIVILEGES } from './lib/consts'; import { PGID, PUID, PORT, ECODE_DROP_PRIVILEGES } from './lib/consts';
import app from './app'; import app from './app';
import { proxyServerVncUpgrade } from './middlewares';
import { stderr, stdout } from './lib/shell'; import { stderr, stdout } from './lib/shell';
(async () => { (async () => {
stdout(`Starting process with ownership ${getuid()}:${getgid()}`); stdout(`Starting process with ownership ${getuid()}:${getgid()}`);
(await app).listen(PORT, () => { const server = (await app).listen(PORT, () => {
try { try {
// Group must be set before user to avoid permission error. // Group must be set before user to avoid permission error.
setgid(PGID); setgid(PGID);
@ -23,4 +24,6 @@ import { stderr, stdout } from './lib/shell';
stdout(`Listening on localhost:${PORT}.`); stdout(`Listening on localhost:${PORT}.`);
}); });
server.on('upgrade', proxyServerVncUpgrade);
})(); })();

@ -384,41 +384,22 @@ const getUpsSpec = async () => {
return getData<AnvilDataUPSHash>('ups_data'); return getData<AnvilDataUPSHash>('ups_data');
}; };
const vncpipe = async (serverUuid: string, open?: boolean) => { const getVncinfo = async (serverUuid: string): Promise<ServerDetailVncInfo> => {
const [output, rReturnCode]: [string, string] = await subroutine('call', { const rows: [[string]] = await query(
params: [ `SELECT variable_value FROM variables WHERE variable_name = 'server::${serverUuid}::vncinfo';`,
{ );
shell_call: `${
SERVER_PATHS.usr.sbin['striker-manage-vnc-pipes'].self
} --server-uuid ${serverUuid} --component st${open ? ' --open' : ''}`,
},
],
pre: ['System'],
root: true,
});
const rcode = Number.parseInt(rReturnCode);
if (rcode !== 0) { if (!rows.length) {
throw new Error(`VNC pipe call failed with code ${rcode}`); throw new Error('No record found');
} }
const lines = output.split('\n'); const [[vncinfo]] = rows;
const lastLine = lines[lines.length - 1]; const [domain, rPort] = vncinfo.split(':');
const rVncPipeProps = lastLine
.split(',')
.reduce<Record<string, string>>((previous, pair) => {
const [key, value] = pair.trim().split(/\s*:\s*/, 2);
previous[key] = value;
return previous;
}, {});
const forwardPort = Number.parseInt(rVncPipeProps.forward_port); const port = Number.parseInt(rPort);
const protocol = rVncPipeProps.protocol; const protocol = 'ws';
return { forwardPort, protocol }; return { domain, port, protocol };
}; };
export { export {
@ -438,8 +419,8 @@ export {
getNetworkData, getNetworkData,
getPeerData, getPeerData,
getUpsSpec, getUpsSpec,
getVncinfo,
query, query,
subroutine as sub, subroutine as sub,
vncpipe,
write, write,
}; };

@ -41,7 +41,6 @@ const EMPTY_SERVER_PATHS: ServerPath = {
'striker-initialize-host': {}, 'striker-initialize-host': {},
'striker-manage-install-target': {}, 'striker-manage-install-target': {},
'striker-manage-peers': {}, 'striker-manage-peers': {},
'striker-manage-vnc-pipes': {},
'striker-parse-os-list': {}, 'striker-parse-os-list': {},
}, },
}, },

@ -1,7 +1,6 @@
export * from './getHostSSH'; export * from './getHostSSH';
export * from './joinAn'; export * from './joinAn';
export * from './leaveAn'; export * from './leaveAn';
export * from './manageVncSshTunnel';
export * from './poweroffStriker'; export * from './poweroffStriker';
export * from './rebootStriker'; export * from './rebootStriker';
export * from './runManifest'; export * from './runManifest';

@ -1,52 +0,0 @@
import assert from 'assert';
import { RequestHandler } from 'express';
import { REP_UUID } from '../../consts';
import { vncpipe } from '../../accessModule';
import { sanitize } from '../../sanitize';
import { stderr, stdoutVar } from '../../shell';
export const manageVncSshTunnel: RequestHandler<
unknown,
{ forwardPort: number; protocol: string },
{ open: boolean; serverUuid: string }
> = async (request, response) => {
const { body: { open: rOpen, serverUuid: rServerUuid } = {} } = request;
const isOpen = sanitize(rOpen, 'boolean');
const serverUuid = sanitize(rServerUuid, 'string');
try {
assert(
REP_UUID.test(serverUuid),
`Server UUID must be a valid UUIDv4; got: [${serverUuid}]`,
);
} catch (error) {
stderr(`Assert input failed when manage VNC SSH tunnel; CAUSE: ${error}`);
return response.status(400).send();
}
stdoutVar({ isOpen, serverUuid }, 'Manage VNC SSH tunnel params: ');
let operation = 'close';
if (isOpen) {
operation = 'open';
}
let rsbody: { forwardPort: number; protocol: string };
try {
rsbody = await vncpipe(serverUuid, isOpen);
} catch (error) {
stderr(
`Failed to ${operation} VNC SSH tunnel to server ${serverUuid}; CAUSE: ${error}`,
);
return response.status(500).send();
}
return response.json(rsbody);
};

@ -1,11 +1,12 @@
import assert from 'assert'; import assert from 'assert';
import { execSync } from 'child_process';
import { RequestHandler } from 'express'; import { RequestHandler } from 'express';
import { REP_UUID, SERVER_PATHS } from '../../consts'; import { REP_UUID, SERVER_PATHS } from '../../consts';
import { getVncinfo } from '../../accessModule';
import { sanitize } from '../../sanitize'; import { sanitize } from '../../sanitize';
import { stderr, stdout } from '../../shell'; import { stderr, stdout } from '../../shell';
import { execSync } from 'child_process';
export const getServerDetail: RequestHandler< export const getServerDetail: RequestHandler<
ServerDetailParamsDictionary, ServerDetailParamsDictionary,
@ -15,12 +16,13 @@ export const getServerDetail: RequestHandler<
> = async (request, response) => { > = async (request, response) => {
const { const {
params: { serverUUID: serverUuid }, params: { serverUUID: serverUuid },
query: { ss }, query: { ss: rSs, vnc: rVnc },
} = request; } = request;
const isScreenshot = sanitize(ss, 'boolean'); const ss = sanitize(rSs, 'boolean');
const vnc = sanitize(rVnc, 'boolean');
stdout(`serverUUID=[${serverUuid}],isScreenshot=[${isScreenshot}]`); stdout(`serverUUID=[${serverUuid}],isScreenshot=[${ss}]`);
try { try {
assert( assert(
@ -35,8 +37,8 @@ export const getServerDetail: RequestHandler<
return response.status(500).send(); return response.status(500).send();
} }
if (isScreenshot) { if (ss) {
const rsbody = { screenshot: '' }; const rsbody: ServerDetailScreenshot = { screenshot: '' };
try { try {
rsbody.screenshot = execSync( rsbody.screenshot = execSync(
@ -49,6 +51,18 @@ export const getServerDetail: RequestHandler<
return response.status(500).send(); return response.status(500).send();
} }
return response.send(rsbody);
} else if (vnc) {
let rsbody: ServerDetailVncInfo;
try {
rsbody = await getVncinfo(serverUuid);
} catch (error) {
stderr(`Failed to get server ${serverUuid} VNC info; CAUSE: ${error}`);
return response.status(500).send();
}
return response.send(rsbody); return response.send(rsbody);
} else { } else {
// For getting sever detail data. // For getting sever detail data.

@ -3,5 +3,6 @@ import session from './session';
export * from './assertAuthentication'; export * from './assertAuthentication';
export * from './assertInit'; export * from './assertInit';
export * from './proxyServerVnc';
export { passport, session }; export { passport, session };

@ -0,0 +1,52 @@
import { ServerResponse } from 'http';
import { createProxyMiddleware } from 'http-proxy-middleware';
import { P_UUID } from '../lib/consts';
import { stderr, stdout } from '../lib/shell';
import { getVncinfo } from '../lib/accessModule';
const WS_SVR_VNC_URL_PREFIX = '/ws/server/vnc';
export const proxyServerVnc = createProxyMiddleware({
changeOrigin: true,
pathFilter: `${WS_SVR_VNC_URL_PREFIX}/*`,
router: async (request) => {
const { url = '' } = request;
const serverUuid = url.replace(
new RegExp(`^${WS_SVR_VNC_URL_PREFIX}/(${P_UUID})`),
'$1',
);
stdout(`Got param [${serverUuid}] from [${url}]`);
let domain: string;
let port: number;
let protocol: string;
try {
({ domain, port, protocol } = await getVncinfo(serverUuid));
} catch (error) {
throw new Error(
`Failed to get server ${serverUuid} VNC info; CAUSE: ${error}`,
);
}
return { host: domain, protocol, port };
},
on: {
error: (error, request, response) => {
stderr(String(error));
(response as ServerResponse).writeHead(404).end();
},
},
ws: true,
});
export const proxyServerVncUpgrade =
proxyServerVnc.upgrade ??
(() => {
stdout('No upgrade handler for server VNC connection(s).');
});

@ -4,7 +4,6 @@ import {
getHostSSH, getHostSSH,
joinAn, joinAn,
leaveAn, leaveAn,
manageVncSshTunnel,
poweroffStriker, poweroffStriker,
rebootStriker, rebootStriker,
runManifest, runManifest,
@ -24,7 +23,6 @@ router
.put('/inquire-host', getHostSSH) .put('/inquire-host', getHostSSH)
.put('/join-an/:uuid', joinAn) .put('/join-an/:uuid', joinAn)
.put('/leave-an/:uuid', leaveAn) .put('/leave-an/:uuid', leaveAn)
.put('/vnc-pipe', manageVncSshTunnel)
.put('/poweroff-host', poweroffStriker) .put('/poweroff-host', poweroffStriker)
.put('/reboot-host', rebootStriker) .put('/reboot-host', rebootStriker)
.put('/run-manifest/:manifestUuid', runManifest) .put('/run-manifest/:manifestUuid', runManifest)

@ -12,6 +12,17 @@ type ServerDetailParamsDictionary = {
}; };
type ServerDetailParsedQs = { type ServerDetailParsedQs = {
ss: boolean | number | string;
resize: string; resize: string;
ss: boolean | number | string;
vnc: boolean | number | string;
};
type ServerDetailScreenshot = {
screenshot: string;
};
type ServerDetailVncInfo = {
domain: string;
port: number;
protocol: string;
}; };

@ -2,51 +2,30 @@ import {
Close as CloseIcon, Close as CloseIcon,
Keyboard as KeyboardIcon, Keyboard as KeyboardIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { import { Box, Menu, styled, Typography } from '@mui/material';
Box,
IconButton,
IconButtonProps,
Menu,
MenuItem,
styled,
Typography,
} from '@mui/material';
import RFB from '@novnc/novnc/core/rfb'; import RFB from '@novnc/novnc/core/rfb';
import { useState, useRef, useEffect, FC, useCallback } from 'react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { useState, useEffect, FC, useMemo, useRef, useCallback } from 'react';
import API_BASE_URL from '../../lib/consts/API_BASE_URL'; import IconButton from '../IconButton';
import { BLACK, RED, TEXT } from '../../lib/consts/DEFAULT_THEME';
import ContainedButton from '../ContainedButton';
import { HeaderText } from '../Text';
import keyCombinations from './keyCombinations'; import keyCombinations from './keyCombinations';
import { Panel } from '../Panels'; import MenuItem from '../MenuItem';
import putFetch from '../../lib/fetchers/putFetch'; import { Panel, PanelHeader } from '../Panels';
import putFetchWithTimeout from '../../lib/fetchers/putFetchWithTimeout';
import Spinner from '../Spinner'; import Spinner from '../Spinner';
import useProtectedState from '../../hooks/useProtectedState'; import { HeaderText } from '../Text';
import useIsFirstRender from '../../hooks/useIsFirstRender';
const PREFIX = 'FullSize'; const PREFIX = 'FullSize';
const classes = { const classes = {
displayBox: `${PREFIX}-displayBox`, displayBox: `${PREFIX}-displayBox`,
spinnerBox: `${PREFIX}-spinnerBox`, spinnerBox: `${PREFIX}-spinnerBox`,
closeButton: `${PREFIX}-closeButton`,
keyboardButton: `${PREFIX}-keyboardButton`,
closeBox: `${PREFIX}-closeBox`,
buttonsBox: `${PREFIX}-buttonsBox`,
keysItem: `${PREFIX}-keysItem`,
}; };
const StyledDiv = styled('div')(() => ({ const StyledDiv = styled('div')(() => ({
[`& .${classes.displayBox}`]: { [`& .${classes.displayBox}`]: {
width: '75vw', width: '75vw',
height: '75vh', height: '75vh',
paddingTop: '1em',
paddingBottom: 0,
paddingLeft: 0,
paddingRight: 0,
}, },
[`& .${classes.spinnerBox}`]: { [`& .${classes.spinnerBox}`]: {
@ -56,127 +35,43 @@ const StyledDiv = styled('div')(() => ({
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}, },
[`& .${classes.closeButton}`]: {
borderRadius: 8,
backgroundColor: RED,
'&:hover': {
backgroundColor: RED,
},
},
[`& .${classes.keyboardButton}`]: {
borderRadius: 8,
backgroundColor: TEXT,
'&:hover': {
backgroundColor: TEXT,
},
},
[`& .${classes.closeBox}`]: {
paddingBottom: '1em',
paddingLeft: '.7em',
paddingRight: 0,
},
[`& .${classes.buttonsBox}`]: {
paddingTop: 0,
},
[`& .${classes.keysItem}`]: {
backgroundColor: TEXT,
paddingRight: '3em',
'&:hover': {
backgroundColor: TEXT,
},
},
})); }));
const CMD_VNC_PIPE_URL = `${API_BASE_URL}/command/vnc-pipe`;
const VncDisplay = dynamic(() => import('./VncDisplay'), { ssr: false }); const VncDisplay = dynamic(() => import('./VncDisplay'), { ssr: false });
type FullSizeOptionalProps = { // Unit: seconds
onClickCloseButton?: IconButtonProps['onClick']; const DEFAULT_VNC_RECONNECT_TIMER_START = 5;
};
type FullSizeProps = FullSizeOptionalProps & { const buildServerVncUrl = (host: string, serverUuid: string) =>
serverUUID: string; `ws://${host}/ws/server/vnc/${serverUuid}`;
serverName: string | string[] | undefined;
};
type VncConnectionProps = {
protocol: string;
forwardPort: number;
};
const FULL_SIZE_DEFAULT_PROPS: Required<
Omit<FullSizeOptionalProps, 'onClickCloseButton'>
> &
Pick<FullSizeOptionalProps, 'onClickCloseButton'> = {
onClickCloseButton: undefined,
};
const FullSize: FC<FullSizeProps> = ({ const FullSize: FC<FullSizeProps> = ({
onClickCloseButton, onClickCloseButton,
serverUUID, serverUUID,
serverName, serverName,
vncReconnectTimerStart = DEFAULT_VNC_RECONNECT_TIMER_START,
}): JSX.Element => { }): JSX.Element => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const isFirstRender = useIsFirstRender();
const rfb = useRef<typeof RFB>();
const hostname = useRef<string | undefined>(undefined);
const [vncConnection, setVncConnection] = useProtectedState<
VncConnectionProps | undefined
>(undefined);
const [vncConnecting, setVncConnecting] = useProtectedState<boolean>(false);
const [isError, setIsError] = useProtectedState<boolean>(false);
const connectVnc = useCallback(async () => {
if (vncConnection || vncConnecting) return;
setVncConnecting(true);
try {
const res = await putFetchWithTimeout(
CMD_VNC_PIPE_URL,
{
serverUuid: serverUUID,
open: true,
},
120000,
);
setVncConnection(await res.json());
} catch {
setIsError(true);
} finally {
setVncConnecting(false);
}
}, [
serverUUID,
setIsError,
setVncConnecting,
setVncConnection,
vncConnecting,
vncConnection,
]);
useEffect(() => { const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
if (typeof window !== 'undefined') { const [rfbConnectArgs, setRfbConnectArgs] = useState<
hostname.current = window.location.hostname; Partial<RfbConnectArgs> | undefined
} >(undefined);
const [vncConnecting, setVncConnecting] = useState<boolean>(false);
const [vncError, setVncError] = useState<boolean>(false);
const [vncReconnectTimer, setVncReconnectTimer] = useState<number>(
vncReconnectTimerStart,
);
connectVnc(); const rfb = useRef<typeof RFB | null>(null);
}, [connectVnc]); const rfbScreen = useRef<HTMLDivElement | null>(null);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => { const handleClickKeyboard = (
event: React.MouseEvent<HTMLButtonElement>,
): void => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
const handleClickClose = async () => {
await putFetch(CMD_VNC_PIPE_URL, { serverUuid: serverUUID });
};
const handleSendKeys = (scans: string[]) => { const handleSendKeys = (scans: string[]) => {
if (rfb.current) { if (rfb.current) {
if (!scans.length) rfb.current.sendCtrlAltDel(); if (!scans.length) rfb.current.sendCtrlAltDel();
@ -195,102 +90,166 @@ const FullSize: FC<FullSizeProps> = ({
} }
}; };
const connectServerVnc = useCallback(() => {
setVncConnecting(true);
setVncError(false);
setRfbConnectArgs({
url: buildServerVncUrl(window.location.host, serverUUID),
});
}, [serverUUID]);
const disconnectServerVnc = useCallback(() => {
setRfbConnectArgs(undefined);
}, []);
const reconnectServerVnc = useCallback(() => {
if (!rfb?.current) return;
rfb.current.disconnect();
rfb.current = null;
connectServerVnc();
}, [connectServerVnc]);
const updateVncReconnectTimer = useCallback((): void => {
const intervalId = setInterval((): void => {
setVncReconnectTimer((previous) => {
const current = previous - 1;
if (current < 1) {
clearInterval(intervalId);
}
return current;
});
}, 1000);
}, []);
// 'connect' event emits when a connection successfully completes.
const rfbConnectEventHandler = useCallback(() => {
setVncConnecting(false);
}, []);
// 'disconnect' event emits when a connection fails,
// OR when a user closes the existing connection.
const rfbDisconnectEventHandler = useCallback(
({ detail: { clean } }) => {
if (!clean) {
setVncConnecting(false);
setVncError(true);
updateVncReconnectTimer();
}
},
[updateVncReconnectTimer],
);
const showScreen = useMemo(
() => !vncConnecting && !vncError,
[vncConnecting, vncError],
);
const keyboardMenuElement = useMemo(
() => (
<Box>
<IconButton onClick={handleClickKeyboard}>
<KeyboardIcon />
</IconButton>
<Menu
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
>
{keyCombinations.map(({ keys, scans }) => (
<MenuItem key={keys} onClick={() => handleSendKeys(scans)}>
<Typography variant="subtitle1">{keys}</Typography>
</MenuItem>
))}
</Menu>
</Box>
),
[anchorEl],
);
const vncDisconnectElement = useMemo(
() => (
<IconButton
onClick={(...args) => {
disconnectServerVnc();
onClickCloseButton?.call(null, ...args);
}}
variant="redcontained"
>
<CloseIcon />
</IconButton>
),
[disconnectServerVnc, onClickCloseButton],
);
const vncToolbarElement = useMemo(
() =>
showScreen && (
<>
{keyboardMenuElement}
{vncDisconnectElement}
</>
),
[keyboardMenuElement, showScreen, vncDisconnectElement],
);
useEffect(() => {
if (vncReconnectTimer === 0) {
setVncReconnectTimer(vncReconnectTimerStart);
reconnectServerVnc();
}
}, [reconnectServerVnc, vncReconnectTimer, vncReconnectTimerStart]);
useEffect(() => {
if (isFirstRender) {
connectServerVnc();
}
}, [connectServerVnc, isFirstRender]);
return ( return (
<Panel> <Panel>
<PanelHeader>
<HeaderText text={`Server: ${serverName}`} />
{vncToolbarElement}
</PanelHeader>
<StyledDiv> <StyledDiv>
<Box flexGrow={1}> <Box
<HeaderText text={`Server: ${serverName}`} /> display={showScreen ? 'flex' : 'none'}
className={classes.displayBox}
>
<VncDisplay
onConnect={rfbConnectEventHandler}
onDisconnect={rfbDisconnectEventHandler}
rfb={rfb}
rfbConnectArgs={rfbConnectArgs}
rfbScreen={rfbScreen}
/>
</Box> </Box>
{vncConnection ? ( {!showScreen && (
<Box display="flex" className={classes.displayBox}>
<VncDisplay
rfb={rfb}
url={`${vncConnection.protocol}://${hostname.current}:${vncConnection.forwardPort}`}
viewOnly={false}
focusOnClick={false}
clipViewport={false}
dragViewport={false}
scaleViewport
resizeSession
showDotCursor={false}
background=""
qualityLevel={6}
compressionLevel={2}
onDisconnect={({ detail: { clean } }) => {
if (!clean) {
setVncConnection(undefined);
connectVnc();
}
}}
/>
<Box>
<Box className={classes.closeBox}>
<IconButton
className={classes.closeButton}
style={{ color: TEXT }}
component="span"
onClick={(
...args: Parameters<
Exclude<IconButtonProps['onClick'], undefined>
>
) => {
handleClickClose();
onClickCloseButton?.call(null, ...args);
}}
>
<CloseIcon />
</IconButton>
</Box>
<Box className={classes.closeBox}>
<IconButton
className={classes.keyboardButton}
style={{ color: BLACK }}
component="span"
onClick={handleClick}
>
<KeyboardIcon />
</IconButton>
<Menu
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={() => setAnchorEl(null)}
>
{keyCombinations.map(({ keys, scans }) => (
<MenuItem
onClick={() => handleSendKeys(scans)}
className={classes.keysItem}
key={keys}
>
<Typography variant="subtitle1">{keys}</Typography>
</MenuItem>
))}
</Menu>
</Box>
</Box>
</Box>
) : (
<Box display="flex" className={classes.spinnerBox}> <Box display="flex" className={classes.spinnerBox}>
{!isError ? ( {vncConnecting && (
<> <>
<HeaderText <HeaderText textAlign="center">
text={`Establishing connection with ${serverName}`} Connecting to {serverName}.
/> </HeaderText>
<HeaderText text="This may take a few minutes" />
<Spinner /> <Spinner />
</> </>
) : ( )}
{vncError && (
<> <>
<Box style={{ paddingBottom: '2em' }}> <HeaderText textAlign="center">
<HeaderText text="There was a problem connecting to the server, please try again" /> There was a problem connecting to the server.
</Box> </HeaderText>
<ContainedButton <HeaderText textAlign="center" mt="1em">
onClick={() => { Retrying in {vncReconnectTimer}.
setIsError(false); </HeaderText>
}}
>
Reconnect
</ContainedButton>
</> </>
)} )}
</Box> </Box>
@ -300,6 +259,4 @@ const FullSize: FC<FullSizeProps> = ({
); );
}; };
FullSize.defaultProps = FULL_SIZE_DEFAULT_PROPS;
export default FullSize; export default FullSize;

@ -1,83 +1,96 @@
import { useEffect, useRef, MutableRefObject, memo } from 'react';
import RFB from '@novnc/novnc/core/rfb'; import RFB from '@novnc/novnc/core/rfb';
import { useEffect } from 'react';
type Props = { const rfbConnect: RfbConnectFunction = ({
rfb: MutableRefObject<typeof RFB | undefined>; background = '',
url: string; clipViewport = false,
viewOnly: boolean; compressionLevel = 2,
focusOnClick: boolean; dragViewport = false,
clipViewport: boolean; focusOnClick = false,
dragViewport: boolean; onConnect,
scaleViewport: boolean; onDisconnect,
resizeSession: boolean; qualityLevel = 6,
showDotCursor: boolean; resizeSession = true,
background: string; rfb,
qualityLevel: number; rfbScreen,
compressionLevel: number; scaleViewport = true,
onDisconnect: (event: { detail: { clean: boolean } }) => void; showDotCursor = false,
url,
viewOnly = false,
}) => {
if (!rfbScreen?.current || rfb?.current) return;
rfbScreen.current.innerHTML = '';
rfb.current = new RFB(rfbScreen.current, url);
rfb.current.background = background;
rfb.current.clipViewport = clipViewport;
rfb.current.compressionLevel = compressionLevel;
rfb.current.dragViewport = dragViewport;
rfb.current.focusOnClick = focusOnClick;
rfb.current.qualityLevel = qualityLevel;
rfb.current.resizeSession = resizeSession;
rfb.current.scaleViewport = scaleViewport;
rfb.current.showDotCursor = showDotCursor;
rfb.current.viewOnly = viewOnly;
// RFB extends custom class EventTargetMixin;
// the usual .on or .once doesn't exist.
if (onConnect) {
rfb.current.addEventListener('connect', onConnect);
}
if (onDisconnect) {
rfb.current.addEventListener('disconnect', onDisconnect);
}
}; };
const VncDisplay = (props: Props): JSX.Element => { const rfbDisconnect: RfbDisconnectFunction = (rfb) => {
const screen = useRef<HTMLDivElement>(null); if (!rfb?.current) return;
rfb.current.disconnect();
rfb.current = null;
};
const VncDisplay = (props: VncDisplayProps): JSX.Element => {
const { const {
rfb, onConnect,
url,
viewOnly,
focusOnClick,
clipViewport,
dragViewport,
scaleViewport,
resizeSession,
showDotCursor,
background,
qualityLevel,
compressionLevel,
onDisconnect, onDisconnect,
rfb,
rfbConnectArgs,
rfbScreen,
url: initUrl,
} = props; } = props;
useEffect(() => { useEffect(() => {
if (!screen.current) { if (rfbConnectArgs) {
return (): void => { const { url = initUrl } = rfbConnectArgs;
if (rfb.current) {
rfb?.current.disconnect(); if (!url) return;
rfb.current = undefined;
} const args: RfbConnectArgs = {
onConnect,
onDisconnect,
rfb,
rfbScreen,
url,
...rfbConnectArgs,
}; };
}
if (!rfb.current) { rfbConnect(args);
screen.current.innerHTML = ''; } else {
rfbDisconnect(rfb);
rfb.current = new RFB(screen.current, url);
rfb.current.viewOnly = viewOnly;
rfb.current.focusOnClick = focusOnClick;
rfb.current.clipViewport = clipViewport;
rfb.current.dragViewport = dragViewport;
rfb.current.resizeSession = resizeSession;
rfb.current.scaleViewport = scaleViewport;
rfb.current.showDotCursor = showDotCursor;
rfb.current.background = background;
rfb.current.qualityLevel = qualityLevel;
rfb.current.compressionLevel = compressionLevel;
// RFB extends custom class EventTargetMixin;
// the usual .on or .once doesn't exist.
rfb.current.addEventListener('disconnect', onDisconnect);
} }
}, [initUrl, onConnect, onDisconnect, rfb, rfbConnectArgs, rfbScreen]);
/* eslint-disable consistent-return */ useEffect(
if (!rfb.current) return; () => () => {
rfbDisconnect(rfb);
return (): void => { },
if (rfb.current) { [rfb],
rfb.current.disconnect(); );
rfb.current = undefined;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [rfb]);
const handleMouseEnter = () => { const handleMouseEnter = () => {
if ( if (
@ -93,10 +106,12 @@ const VncDisplay = (props: Props): JSX.Element => {
return ( return (
<div <div
style={{ width: '100%', height: '75vh' }} style={{ width: '100%', height: '75vh' }}
ref={screen} ref={rfbScreen}
onMouseEnter={handleMouseEnter} onMouseEnter={handleMouseEnter}
/> />
); );
}; };
export default memo(VncDisplay); VncDisplay.displayName = 'VncDisplay';
export default VncDisplay;

@ -1 +0,0 @@
self.__BUILD_MANIFEST=function(s,a,c,e,t,n,i,d,f,u,k,b,h,j,r,g){return{__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":[s,c,e,i,d,b,"static/chunks/717-8bd60b96d67fd464.js",a,t,n,f,h,j,"static/chunks/pages/index-2eaaa0d56c4c4f15.js"],"/_error":["static/chunks/pages/_error-2280fa386d040b66.js"],"/anvil":[s,c,e,i,d,b,a,t,n,f,h,"static/chunks/pages/anvil-fbef5033b416c0dd.js"],"/config":[s,c,e,u,a,t,n,k,"static/chunks/pages/config-0381f0311f2b572a.js"],"/file-manager":[s,c,e,i,"static/chunks/768-9ee3dcb62beecb53.js",a,t,"static/chunks/pages/file-manager-21a667deb0a17960.js"],"/init":[s,c,i,d,u,r,a,t,n,f,g,"static/chunks/pages/init-e60db40234cb8025.js"],"/login":[s,c,e,a,t,n,k,"static/chunks/pages/login-398afa43b3c54927.js"],"/manage-element":[s,c,e,i,d,u,r,"static/chunks/195-d5fd184cc249f755.js",a,t,n,f,k,g,"static/chunks/pages/manage-element-79adadce1efb772f.js"],"/server":[s,e,"static/chunks/227-a3756585a7ef09ae.js",a,j,"static/chunks/pages/server-bf2d408a68a09e13.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/manage-element","/server"]}}("static/chunks/382-f51344f6f9208507.js","static/chunks/746-a9b6e396d532b9a2.js","static/chunks/483-f8013e38dca1620d.js","static/chunks/894-e57948de523bcf96.js","static/chunks/203-2f903a41f9b4e31e.js","static/chunks/899-83e9de2a35c6bcf0.js","static/chunks/182-08683bbe95fbb010.js","static/chunks/614-0ce04fd295045ffe.js","static/chunks/140-ec935fb15330b98a.js","static/chunks/644-c7c6e21c71345aed.js","static/chunks/903-dc2a40be612a10c3.js","static/chunks/485-77798bccc4308d0e.js","static/chunks/116-dfb7b81f7280bb6d.js","static/chunks/906-86856e16ad160b72.js","static/chunks/676-6159ce853338cc1f.js","static/chunks/692-59a4ae2829590f4c.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[665],{4665:function(e,n,r){r.r(n);var t=r(5893),o=r(4460),c=r(7294);function i(e,n,r){return n in e?Object.defineProperty(e,n,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[n]=r,e}var u=function(e){(null===e||void 0===e?void 0:e.current)&&(e.current.disconnect(),e.current=null)},l=function(e){var n=e.onConnect,r=e.onDisconnect,l=e.rfb,s=e.rfbConnectArgs,f=e.rfbScreen,v=e.url;(0,c.useEffect)((function(){if(s){var e=s.url,t=void 0===e?v:e;if(!t)return;var c=function(e){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{},t=Object.keys(r);"function"===typeof Object.getOwnPropertySymbols&&(t=t.concat(Object.getOwnPropertySymbols(r).filter((function(e){return Object.getOwnPropertyDescriptor(r,e).enumerable})))),t.forEach((function(n){i(e,n,r[n])}))}return e}({onConnect:n,onDisconnect:r,rfb:l,rfbScreen:f,url:t},s);!function(e){var n=e.background,r=void 0===n?"":n,t=e.clipViewport,c=void 0!==t&&t,i=e.compressionLevel,u=void 0===i?2:i,l=e.dragViewport,s=void 0!==l&&l,f=e.focusOnClick,v=void 0!==f&&f,a=e.onConnect,d=e.onDisconnect,b=e.qualityLevel,p=void 0===b?6:b,y=e.resizeSession,w=void 0===y||y,m=e.rfb,h=e.rfbScreen,E=e.scaleViewport,O=void 0===E||E,g=e.showDotCursor,C=void 0!==g&&g,S=e.url,k=e.viewOnly,L=void 0!==k&&k;(null===h||void 0===h?void 0:h.current)&&!(null===m||void 0===m?void 0:m.current)&&(h.current.innerHTML="",m.current=new o.Z(h.current,S),m.current.background=r,m.current.clipViewport=c,m.current.compressionLevel=u,m.current.dragViewport=s,m.current.focusOnClick=v,m.current.qualityLevel=p,m.current.resizeSession=w,m.current.scaleViewport=O,m.current.showDotCursor=C,m.current.viewOnly=L,a&&m.current.addEventListener("connect",a),d&&m.current.addEventListener("disconnect",d))}(c)}else u(l)}),[v,n,r,l,s,f]),(0,c.useEffect)((function(){return function(){u(l)}}),[l]);return(0,t.jsx)("div",{style:{width:"100%",height:"75vh"},ref:f,onMouseEnter:function(){var e,n;document.activeElement&&(e=document.activeElement,null!=(n=HTMLElement)&&"undefined"!==typeof Symbol&&n[Symbol.hasInstance]?n[Symbol.hasInstance](e):e instanceof n)&&document.activeElement.blur(),(null===l||void 0===l?void 0:l.current)&&l.current.focus()}})};l.displayName="VncDisplay",n.default=l}}]);

@ -1 +0,0 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[665],{4665:function(e,n,r){r.r(n);var t=r(5893),c=r(7294),u=r(4460);var o=function(e){var n=(0,c.useRef)(null),r=e.rfb,o=e.url,i=e.viewOnly,s=e.focusOnClick,l=e.clipViewport,a=e.dragViewport,d=e.scaleViewport,v=e.resizeSession,f=e.showDotCursor,m=e.background,p=e.qualityLevel,w=e.compressionLevel,h=e.onDisconnect;(0,c.useEffect)((function(){return n.current?(r.current||(n.current.innerHTML="",r.current=new u.Z(n.current,o),r.current.viewOnly=i,r.current.focusOnClick=s,r.current.clipViewport=l,r.current.dragViewport=a,r.current.resizeSession=v,r.current.scaleViewport=d,r.current.showDotCursor=f,r.current.background=m,r.current.qualityLevel=p,r.current.compressionLevel=w,r.current.addEventListener("disconnect",h)),r.current?function(){r.current&&(r.current.disconnect(),r.current=void 0)}:void 0):function(){r.current&&(null===r||void 0===r||r.current.disconnect(),r.current=void 0)}}),[r]);return(0,t.jsx)("div",{style:{width:"100%",height:"75vh"},ref:n,onMouseEnter:function(){var e,n;document.activeElement&&(e=document.activeElement,null!=(n=HTMLElement)&&"undefined"!==typeof Symbol&&n[Symbol.hasInstance]?n[Symbol.hasInstance](e):e instanceof n)&&document.activeElement.blur(),(null===r||void 0===r?void 0:r.current)&&r.current.focus()}})};n.default=(0,c.memo)(o)}}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
!function(){"use strict";var e={},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var u=t[r]={exports:{}},i=!0;try{e[r].call(u.exports,u,u.exports,n),i=!1}finally{i&&delete t[r]}return u.exports}n.m=e,function(){var e=[];n.O=function(t,r,o,u){if(!r){var i=1/0;for(l=0;l<e.length;l++){r=e[l][0],o=e[l][1],u=e[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&u||i>=u)&&Object.keys(n.O).every((function(e){return n.O[e](r[c])}))?r.splice(c--,1):(f=!1,u<i&&(i=u));if(f){e.splice(l--,1);var a=o();void 0!==a&&(t=a)}}return t}u=u||0;for(var l=e.length;l>0&&e[l-1][2]>u;l--)e[l]=e[l-1];e[l]=[r,o,u]}}(),n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},function(){var e,t=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__};n.t=function(r,o){if(1&o&&(r=this(r)),8&o)return r;if("object"===typeof r&&r){if(4&o&&r.__esModule)return r;if(16&o&&"function"===typeof r.then)return r}var u=Object.create(null);n.r(u);var i={};e=e||[null,t({}),t([]),t(t)];for(var f=2&o&&r;"object"==typeof f&&!~e.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach((function(e){i[e]=function(){return r[e]}}));return i.default=function(){return r},n.d(u,i),u}}(),n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.f={},n.e=function(e){return Promise.all(Object.keys(n.f).reduce((function(t,r){return n.f[r](e,t),t}),[]))},n.u=function(e){return"static/chunks/"+e+"."+{460:"91d31c8392f2cdc4",665:"b5fa539eea66745a"}[e]+".js"},n.miniCssF=function(e){return"static/css/fc4c5db74ac4baf3.css"},n.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}(),n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e={},t="_N_E:";n.l=function(r,o,u,i){if(e[r])e[r].push(o);else{var f,c;if(void 0!==u)for(var a=document.getElementsByTagName("script"),l=0;l<a.length;l++){var s=a[l];if(s.getAttribute("src")==r||s.getAttribute("data-webpack")==t+u){f=s;break}}f||(c=!0,(f=document.createElement("script")).charset="utf-8",f.timeout=120,n.nc&&f.setAttribute("nonce",n.nc),f.setAttribute("data-webpack",t+u),f.src=r),e[r]=[o];var d=function(t,n){f.onerror=f.onload=null,clearTimeout(p);var o=e[r];if(delete e[r],f.parentNode&&f.parentNode.removeChild(f),o&&o.forEach((function(e){return e(n)})),t)return t(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=d.bind(null,f.onerror),f.onload=d.bind(null,f.onload),c&&document.head.appendChild(f)}}}(),n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/_next/",function(){var e={272:0};n.f.j=function(t,r){var o=n.o(e,t)?e[t]:void 0;if(0!==o)if(o)r.push(o[2]);else if(272!=t){var u=new Promise((function(n,r){o=e[t]=[n,r]}));r.push(o[2]=u);var i=n.p+n.u(t),f=new Error;n.l(i,(function(r){if(n.o(e,t)&&(0!==(o=e[t])&&(e[t]=void 0),o)){var u=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;f.message="Loading chunk "+t+" failed.\n("+u+": "+i+")",f.name="ChunkLoadError",f.type=u,f.request=i,o[1](f)}}),"chunk-"+t,t)}else e[t]=0},n.O.j=function(t){return 0===e[t]};var t=function(t,r){var o,u,i=r[0],f=r[1],c=r[2],a=0;if(i.some((function(t){return 0!==e[t]}))){for(o in f)n.o(f,o)&&(n.m[o]=f[o]);if(c)var l=c(n)}for(t&&t(r);a<i.length;a++)u=i[a],n.o(e,u)&&e[u]&&e[u][0](),e[u]=0;return n.O(l)},r=self.webpackChunk_N_E=self.webpackChunk_N_E||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))}()}(); !function(){"use strict";var e={},t={};function n(r){var o=t[r];if(void 0!==o)return o.exports;var u=t[r]={exports:{}},i=!0;try{e[r].call(u.exports,u,u.exports,n),i=!1}finally{i&&delete t[r]}return u.exports}n.m=e,function(){var e=[];n.O=function(t,r,o,u){if(!r){var i=1/0;for(l=0;l<e.length;l++){r=e[l][0],o=e[l][1],u=e[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&u||i>=u)&&Object.keys(n.O).every((function(e){return n.O[e](r[c])}))?r.splice(c--,1):(f=!1,u<i&&(i=u));if(f){e.splice(l--,1);var a=o();void 0!==a&&(t=a)}}return t}u=u||0;for(var l=e.length;l>0&&e[l-1][2]>u;l--)e[l]=e[l-1];e[l]=[r,o,u]}}(),n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},function(){var e,t=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__};n.t=function(r,o){if(1&o&&(r=this(r)),8&o)return r;if("object"===typeof r&&r){if(4&o&&r.__esModule)return r;if(16&o&&"function"===typeof r.then)return r}var u=Object.create(null);n.r(u);var i={};e=e||[null,t({}),t([]),t(t)];for(var f=2&o&&r;"object"==typeof f&&!~e.indexOf(f);f=t(f))Object.getOwnPropertyNames(f).forEach((function(e){i[e]=function(){return r[e]}}));return i.default=function(){return r},n.d(u,i),u}}(),n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.f={},n.e=function(e){return Promise.all(Object.keys(n.f).reduce((function(t,r){return n.f[r](e,t),t}),[]))},n.u=function(e){return"static/chunks/"+e+"."+{460:"91d31c8392f2cdc4",665:"ae67dcf3c1b6f7f6"}[e]+".js"},n.miniCssF=function(e){return"static/css/fc4c5db74ac4baf3.css"},n.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}(),n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e={},t="_N_E:";n.l=function(r,o,u,i){if(e[r])e[r].push(o);else{var f,c;if(void 0!==u)for(var a=document.getElementsByTagName("script"),l=0;l<a.length;l++){var s=a[l];if(s.getAttribute("src")==r||s.getAttribute("data-webpack")==t+u){f=s;break}}f||(c=!0,(f=document.createElement("script")).charset="utf-8",f.timeout=120,n.nc&&f.setAttribute("nonce",n.nc),f.setAttribute("data-webpack",t+u),f.src=r),e[r]=[o];var d=function(t,n){f.onerror=f.onload=null,clearTimeout(p);var o=e[r];if(delete e[r],f.parentNode&&f.parentNode.removeChild(f),o&&o.forEach((function(e){return e(n)})),t)return t(n)},p=setTimeout(d.bind(null,void 0,{type:"timeout",target:f}),12e4);f.onerror=d.bind(null,f.onerror),f.onload=d.bind(null,f.onload),c&&document.head.appendChild(f)}}}(),n.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.p="/_next/",function(){var e={272:0};n.f.j=function(t,r){var o=n.o(e,t)?e[t]:void 0;if(0!==o)if(o)r.push(o[2]);else if(272!=t){var u=new Promise((function(n,r){o=e[t]=[n,r]}));r.push(o[2]=u);var i=n.p+n.u(t),f=new Error;n.l(i,(function(r){if(n.o(e,t)&&(0!==(o=e[t])&&(e[t]=void 0),o)){var u=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;f.message="Loading chunk "+t+" failed.\n("+u+": "+i+")",f.name="ChunkLoadError",f.type=u,f.request=i,o[1](f)}}),"chunk-"+t,t)}else e[t]=0},n.O.j=function(t){return 0===e[t]};var t=function(t,r){var o,u,i=r[0],f=r[1],c=r[2],a=0;if(i.some((function(t){return 0!==e[t]}))){for(o in f)n.o(f,o)&&(n.m[o]=f[o]);if(c)var l=c(n)}for(t&&t(r);a<i.length;a++)u=i[a],n.o(e,u)&&e[u]&&e[u][0](),e[u]=0;return n.O(l)},r=self.webpackChunk_N_E=self.webpackChunk_N_E||[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))}()}();

@ -0,0 +1 @@
self.__BUILD_MANIFEST=function(s,c,a,e,t,n,i,f,d,u,b,k,h,j,r,g){return{__rewrites:{beforeFiles:[],afterFiles:[],fallback:[]},"/":[s,a,e,i,f,k,"static/chunks/717-8bd60b96d67fd464.js",c,t,n,d,h,j,"static/chunks/pages/index-1f8f0ad3b3894dbc.js"],"/_error":["static/chunks/pages/_error-2280fa386d040b66.js"],"/anvil":[s,a,e,i,f,k,c,t,n,d,h,"static/chunks/pages/anvil-c1177b17efcafc34.js"],"/config":[s,a,e,u,c,t,n,b,"static/chunks/pages/config-0ecdb2b2b3f8c089.js"],"/file-manager":[s,a,e,i,"static/chunks/768-9ee3dcb62beecb53.js",c,t,"static/chunks/pages/file-manager-1a707639a4834587.js"],"/init":[s,a,i,f,u,r,c,t,n,d,g,"static/chunks/pages/init-ae1befa8975f7914.js"],"/login":[s,a,e,c,t,n,b,"static/chunks/pages/login-b5de0cd2f49998d6.js"],"/manage-element":[s,a,e,i,f,u,r,"static/chunks/195-d5fd184cc249f755.js",c,t,n,d,b,g,"static/chunks/pages/manage-element-2b1d8792c2a5bf47.js"],"/server":[s,e,"static/chunks/227-a3756585a7ef09ae.js",c,j,"static/chunks/pages/server-db52258419acacf3.js"],sortedPages:["/","/_app","/_error","/anvil","/config","/file-manager","/init","/login","/manage-element","/server"]}}("static/chunks/382-f51344f6f9208507.js","static/chunks/62-532ed713980da8db.js","static/chunks/483-f8013e38dca1620d.js","static/chunks/894-e57948de523bcf96.js","static/chunks/780-e8b3396d257460a4.js","static/chunks/899-83e9de2a35c6bcf0.js","static/chunks/182-08683bbe95fbb010.js","static/chunks/614-0ce04fd295045ffe.js","static/chunks/140-ec935fb15330b98a.js","static/chunks/644-c7c6e21c71345aed.js","static/chunks/903-dc2a40be612a10c3.js","static/chunks/485-77798bccc4308d0e.js","static/chunks/825-d34974d169ea09cc.js","static/chunks/94-db0af749b6e45543.js","static/chunks/676-6159ce853338cc1f.js","static/chunks/692-59a4ae2829590f4c.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1,8 +1,7 @@
import { useEffect, useState } from 'react'; import { Box, styled } from '@mui/material';
import { useRouter } from 'next/router';
import Head from 'next/head'; import Head from 'next/head';
import { Box } from '@mui/material'; import { useRouter } from 'next/router';
import { styled } from '@mui/material/styles'; import { useEffect, useState } from 'react';
import { FullSize, Preview } from '../../components/Display'; import { FullSize, Preview } from '../../components/Display';
import Header from '../../components/Header'; import Header from '../../components/Header';

@ -0,0 +1,6 @@
type FullSizeProps = {
onClickCloseButton?: import('@mui/material').IconButtonProps['onClick'];
serverUUID: string;
serverName: string;
vncReconnectTimerStart?: number;
};

@ -0,0 +1,32 @@
type RfbRef = import('react').MutableRefObject<
typeof import('@novnc/novnc/core/rfb') | null
>;
type RfbScreenRef = import('react').MutableRefObject<HTMLDivElement | null>;
type RfbConnectArgs = {
background?: string;
clipViewport?: boolean;
compressionLevel?: number;
dragViewport?: boolean;
focusOnClick?: boolean;
onConnect?: () => void;
onDisconnect?: (event: { detail: { clean: boolean } }) => void;
qualityLevel?: number;
resizeSession?: boolean;
rfb: RfbRef;
rfbScreen: RfbScreenRef;
scaleViewport?: boolean;
showDotCursor?: boolean;
url: string;
viewOnly?: boolean;
};
type RfbConnectFunction = (args: RfbConnectArgs) => void;
type RfbDisconnectFunction = (rfb: RfbRef) => void;
type VncDisplayProps = Pick<RfbConnectArgs, 'rfb' | 'rfbScreen'> &
Partial<RfbConnectArgs> & {
rfbConnectArgs?: Partial<RfbConnectArgs>;
};

@ -26,6 +26,7 @@ dist_sbin_SCRIPTS = \
anvil-manage-server \ anvil-manage-server \
anvil-manage-server-storage \ anvil-manage-server-storage \
anvil-manage-storage-groups \ anvil-manage-storage-groups \
anvil-manage-vnc-pipe \
anvil-migrate-server \ anvil-migrate-server \
anvil-network-profiler \ anvil-network-profiler \
anvil-parse-fence-agents \ anvil-parse-fence-agents \
@ -60,8 +61,6 @@ dist_sbin_SCRIPTS = \
striker-initialize-host \ striker-initialize-host \
striker-manage-install-target \ striker-manage-install-target \
striker-manage-peers \ striker-manage-peers \
striker-manage-vnc-pipes \
striker-open-ssh-tunnel \
striker-parse-os-list \ striker-parse-os-list \
striker-parse-oui \ striker-parse-oui \
striker-prep-database \ striker-prep-database \

@ -0,0 +1,519 @@
#!/usr/bin/perl
#
# Manages VNC ports for server VMs that have VNC enabled.
#
use strict;
use warnings;
use Anvil::Tools;
use Data::Dumper;
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new();
my $echo = $anvil->data->{path}{exe}{'echo'};
my $grep = $anvil->data->{path}{exe}{'grep'};
my $kill = $anvil->data->{path}{exe}{'kill'};
my $pgrep = $anvil->data->{path}{exe}{'pgrep'};
my $ss = $anvil->data->{path}{exe}{'ss'};
my $sed = $anvil->data->{path}{exe}{'sed'};
my $websockify = $anvil->data->{path}{exe}{'websockify'};
$anvil->Get->switches;
$anvil->Database->connect;
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132" });
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, exit.
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003" });
$anvil->nice_exit({ exit_code => 1 });
}
my $switch_debug = $anvil->data->{switches}{'debug'};
my $open = $anvil->data->{switches}{'open'};
my $server = $anvil->data->{switches}{'server'};
my $server_uuid = $anvil->data->{switches}{'server-uuid'};
my $server_vnc_port = $anvil->data->{switches}{'server-vnc-port'};
if (defined $server)
{
$server_uuid //= is_uuid_v4($server) ? $server : $anvil->Get->server_uuid_from_name({ server_name => $server });
}
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $switch_debug, list => {
open => $open,
server => $server,
server_uuid => $server_uuid,
server_vnc_port => $server_vnc_port
} });
my $map_to_operation = { start => \&start_pipe, stop => \&stop_pipe };
if ($server_uuid)
{
my $operation = $open ? "start" : "stop";
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $switch_debug, list => { operation => $operation } });
my ($rcode, $err_msg) = $map_to_operation->{$operation}({
debug => $switch_debug,
svr_uuid => $server_uuid,
svr_vnc_port => $server_vnc_port,
});
if ($rcode)
{
$anvil->Log->entry({
source => $THIS_FILE,
line => __LINE__,
level => $switch_debug || 2,
raw => "[ Error ] - Operation $operation failed; CAUSE: $err_msg",
});
}
$anvil->nice_exit({ exit_code => $rcode });
}
$anvil->nice_exit({ exit_code => 0 });
#
# Functions
#
sub build_find_available_port_call
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $start = $parameters->{start};
my $step_operator = $parameters->{step_operator} // "+";
my $step_size = $parameters->{step_size} || 1;
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "build_find_available_port_call" });
return (1) if ( (not $step_operator =~ /^[+-]$/)
|| (not is_int($step_size)) || ($step_size < 1) );
my $call = "ss_output=\$($ss -ant) && port=${start} && while $grep -Eq \":\${port}[[:space:]]+[^[:space:]]+\" <<<\$ss_output; do (( port ${step_operator}= $step_size )); done && $echo \$port";
return (0, $call);
}
sub build_vncinfo_variable_name
{
my ($svr_uuid) = @_;
return "server::${svr_uuid}::vncinfo";
}
sub call
{
my $parameters = shift;
my $background = $parameters->{background} || 0;
my $call = $parameters->{call};
my $debug = $parameters->{debug} || 3;
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "call" });
return (1) if ( (not defined $call) || ($call eq "") );
my ($output, $rcode) = $anvil->System->call({ background => $background, shell_call => $call });
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => {
output => $output,
rcode => $rcode
} });
# Output order reversed keep returns consistent.
return ($rcode, $output);
}
sub find_available_port
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_available_port" });
my ($build_rcode, $call) = build_find_available_port_call($parameters);
return (1) if ($build_rcode);
return call({ call => $call, debug => $debug });
}
sub find_end_port
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $svr_host_name = $parameters->{svr_host_name} // $anvil->data->{sys}{host_name};
my $svr_uuid = $parameters->{svr_uuid};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_end_port" });
return (1) if (not defined $svr_uuid);
my $variable_name = build_vncinfo_variable_name($svr_uuid);
my $variable_value_pattern = "$svr_host_name:%";
# Look in the history variables table because libvirt hook operation
# 'stopped' gets triggered **after** the 'started' operation on the
# peer host during server migration.
my $query = "
SELECT
SUBSTRING(variable_value, ".$anvil->Database->quote("(\\d+)\$").")
FROM
history.variables
WHERE
variable_name = ".$anvil->Database->quote($variable_name)."
AND
variable_value LIKE ".$anvil->Database->quote($variable_value_pattern)."
ORDER BY
modified_date DESC
LIMIT 1
;";
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { query => $query } });
my $results = $anvil->Database->query({ source => $THIS_FILE, line => __LINE__, query => $query });
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => $debug, raw => prettify($results, "results") });
return (1) if (not @{$results});
return (0, int($results->[0]->[0]));
}
sub find_server_vnc_port
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $svr_uuid = $parameters->{svr_uuid};
my $svr_vnc_port = $parameters->{svr_vnc_port};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_server_vnc_port" });
return (1) if (not defined $svr_uuid);
return (0, $svr_vnc_port) if (is_int($svr_vnc_port));
# If we don't have the server's VNC port, find it in its qemu-kvm process.
my ($rcode, $svr_processes) = $anvil->Server->find_processes({ debug => $debug });
return (1) if ($rcode);
my $svr_process = $svr_processes->{uuids}{$svr_uuid};
my $svr_vnc_alive = $svr_process->{vnc_alive};
return (1) if (not $svr_vnc_alive);
return (0, $svr_process->{vnc_port});
}
sub find_ws_processes
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $ps_name = $parameters->{ps_name} // "websockify";
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "find_ws_processes" });
my $ps_call = "$pgrep -a '$ps_name' | $sed -En 's/^([[:digit:]]+).*${ps_name}([[:space:]]+(--?[^[:space:]]+))*[[:space:]:]+([[:digit:]]+)[[:space:]:]+([[:digit:]]+).*\$/\\1,\\4,\\5/p'";
my ($rcode, $output) = call({ call => $ps_call, debug => $debug });
return (1) if ($rcode);
my $result = { pids => {}, sources => {}, targets => {} };
foreach my $line (split(/\n/, $output))
{
chomp($line);
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => { ws_line => $line } });
my ($pid, $sport, $tport) = split(/,/, $line);
my $process = { pid => $pid, sport => $sport, tport => $tport };
set_ws_process({ debug => $debug, entry => $process, entries => $result });
}
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => $debug, raw => prettify($result, "ws_processes") });
return (0, $result);
}
sub is_int
{
return defined $_[0] && $_[0] =~ /^\d+$/;
}
sub is_uuid_v4
{
return defined $_[0] && $_[0] =~ /[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}/;
}
sub prettify
{
my $var_value = shift;
my $var_name = shift;
local $Data::Dumper::Indent = 1;
local $Data::Dumper::Varname = $var_name;
local $Data::Dumper::Terse = (defined $var_name) ? 0 : 1;
return Dumper($var_value);
}
sub set_entry
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $handle_delete = $parameters->{handle_delete};
my $handle_set = $parameters->{handle_set};
my $id = $parameters->{id};
my $entry = $parameters->{entry};
my $entries = $parameters->{entries};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => {
%$parameters,
p_entry => prettify($entry),
p_entries => prettify($entries),
}, prefix => "set_entry" });
return (1) if (not defined $entries);
if (defined $entry)
{
$handle_set->($id, $entry, $entries);
}
elsif (defined $id)
{
$handle_delete->($id, $entry, $entries);
}
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => $debug, raw => prettify($entries, "entries") });
return (0);
}
sub set_vncinfo_variable
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $end_port = $parameters->{end_port};
my $svr_uuid = $parameters->{svr_uuid};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "set_vncinfo_variable" });
my $local_host_name = $anvil->data->{sys}{host_name};
my ($variable_uuid) = $anvil->Database->insert_or_update_variables({
file => $THIS_FILE,
line => __LINE__,
variable_name => build_vncinfo_variable_name($svr_uuid),
variable_value => "${local_host_name}:${end_port}",
});
return (1) if (not is_uuid_v4($variable_uuid));
return (0);
}
sub set_ws_process
{
my $parameters = shift;
$parameters->{handle_delete} = sub {
my ($pid, $process, $processes) = @_;
$process = $processes->{pids}{$pid};
my $sport = $process->{sport};
my $tport = $process->{tport};
delete $processes->{pids}{$pid};
delete $processes->{sources}{$sport};
delete $processes->{targets}{$tport};
};
$parameters->{handle_set} = sub {
my ($pid, $process, $processes) = @_;
$pid = $process->{pid};
my $sport = $process->{sport};
my $tport = $process->{tport};
# The websockify daemon wrapper may remain alive, hence each
# port can map to mutiple pids.
my $spids = $processes->{sources}{$sport} // [];
my $tpids = $processes->{targets}{$tport} // [];
$processes->{pids}{$pid} = $process;
# Process identifiers are already ordered by pgrep, record them
# in ascending order.
$processes->{sources}{$sport} = [@{$spids}, $pid];
$processes->{targets}{$tport} = [@{$tpids}, $pid];
};
return set_entry($parameters);
}
sub start_pipe
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $svr_uuid = $parameters->{svr_uuid};
my $svr_vnc_port = $parameters->{svr_vnc_port};
return (1, __LINE__.": [$svr_uuid]") if (not is_uuid_v4($svr_uuid));
my $common_params = { debug => $debug };
my $rcode;
($rcode, $svr_vnc_port) = find_server_vnc_port($parameters);
return ($rcode, __LINE__.": $rcode,[$svr_vnc_port]") if ($rcode);
($rcode, my $ws_processes) = find_ws_processes($common_params);
return ($rcode, __LINE__.": $rcode,[".prettify($ws_processes)."]") if ($rcode);
($rcode, my $ws_pid) = start_ws({ svr_vnc_port => $svr_vnc_port, ws_processes => $ws_processes, %$common_params });
return ($rcode, __LINE__.": $rcode,[$ws_pid]") if ($rcode);
my $ws_process = $ws_processes->{pids}{$ws_pid};
my $ws_sport = $ws_process->{sport};
($rcode) = set_vncinfo_variable({ end_port => $ws_sport, svr_uuid => $svr_uuid, %$common_params });
return ($rcode, __LINE__.": [$svr_uuid:$ws_sport],$rcode") if ($rcode);
return (0);
}
sub start_ws
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $svr_vnc_port = $parameters->{svr_vnc_port};
my $ws_processes = $parameters->{ws_processes};
my $ws_sport_offset = $parameters->{ws_sport_offset} || 10000;
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "start_ws" });
return (1) if ( (not defined $ws_processes)
|| (not is_int($svr_vnc_port))
|| (not is_int($ws_sport_offset)) );
my $existing_ws_pids = $ws_processes->{targets}{$svr_vnc_port};
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => $debug, raw => prettify($existing_ws_pids, "existing_ws_pids") });
return (0, $existing_ws_pids->[0]) if (defined $existing_ws_pids);
my $rcode;
($rcode, my $ws_sport) = find_available_port({ debug => $debug, start => int($svr_vnc_port) + int($ws_sport_offset) });
return ($rcode) if ($rcode);
# The daemon wrapper can tell us whether the daemon started correctly;
# we won't know this if the process is started in the background.
my $ws_call = "$websockify -D $ws_sport :$svr_vnc_port &>/dev/null";
($rcode) = call({ call => $ws_call, debug => $debug });
return ($rcode) if ($rcode);
# Re-find to locate the new daemon.
($rcode, my $re_ws_processes) = find_ws_processes({ debug => $debug });
return ($rcode) if ($rcode);
my $ws_pid = $re_ws_processes->{targets}{$svr_vnc_port}->[0];
my $ws_process = $re_ws_processes->{pids}{$ws_pid};
# Remember the started daemon.
set_ws_process({ debug => $debug, entry => $ws_process, entries => $ws_processes });
return (0, $ws_pid);
}
sub stop_pipe
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $svr_uuid = $parameters->{svr_uuid};
my $svr_vnc_port = $parameters->{svr_vnc_port};
return (1, __LINE__.": [$svr_uuid]") if (not is_uuid_v4($svr_uuid));
my $common_params = { debug => $debug };
my $rcode;
($rcode, my $ws_processes) = find_ws_processes($common_params);
return ($rcode, __LINE__.": $rcode,[".prettify($ws_processes)."]") if ($rcode);
($rcode, $svr_vnc_port) = find_server_vnc_port($parameters);
my $ws_pids;
if ($rcode)
{
# The server VNC port is not available during the libvirt hook
# operation 'stopped'. Try to locate the websockify daemon by
# its source port.
($rcode, my $end_port) = find_end_port({ svr_uuid => $svr_uuid, %$common_params });
return ($rcode, __LINE__.": $rcode,[$end_port]") if ($rcode);
$ws_pids = $ws_processes->{sources}{$end_port};
}
else
{
$ws_pids = $ws_processes->{targets}{$svr_vnc_port};
}
foreach my $ws_pid (@{$ws_pids})
{
($rcode) = stop_ws({ ws_pid => $ws_pid, ws_processes => $ws_processes, %$common_params });
return ($rcode, __LINE__.": [$ws_pid],$rcode") if ($rcode);
}
return (0);
}
sub stop_ws
{
my $parameters = shift;
my $debug = $parameters->{debug} || 3;
my $ws_pid = $parameters->{ws_pid};
my $ws_processes = $parameters->{ws_processes};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => $debug, list => $parameters, prefix => "stop_ws" });
return (1) if ( (not is_int($ws_pid)) || (not defined $ws_processes) );
call({ debug => $debug, call => "$kill $ws_pid || $kill -9 $ws_pid" });
set_ws_process({ debug => $debug, id => $ws_pid, entries => $ws_processes });
return (0);
}

File diff suppressed because it is too large Load Diff

@ -1,136 +0,0 @@
#!/usr/bin/perl
#
# Open an SSH tunnel using the Net::OpenSSH module and keep it opened with an infinite loop.
#
# Note: this is a temporary solution to avoid directly calling the SSH command.
#
use strict;
use warnings;
use Anvil::Tools;
use Net::OpenSSH;
$| = 1;
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0];
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0];
if (($running_directory =~ /^\./) && ($ENV{PWD}))
{
$running_directory =~ s/^\./$ENV{PWD}/;
}
my $anvil = Anvil::Tools->new();
my $ssh_fh;
sub open_ssh_tunnel
{
my $parameters = shift;
# Required parameters:
my $remote_user = $parameters->{remote_user};
my $target = $parameters->{target};
my $forward_local_port = $parameters->{forward_local_port};
my $forward_remote_port = $parameters->{forward_remote_port};
if ((not defined $remote_user)
or (not defined $target)
or (not defined $forward_local_port)
or (not defined $forward_remote_port))
{
return 1;
}
# Optional parameters:
my $port = $parameters->{port} ? $parameters->{port} : 22;
my $ssh_fh_key = $remote_user."\@".$target.":".$port;
my $query = "
SELECT anv.anvil_password
FROM hosts AS hos
JOIN anvils AS anv
ON hos.host_uuid = anv.anvil_node1_host_uuid
OR hos.host_uuid = anv.anvil_node2_host_uuid
WHERE hos.host_name = ".$anvil->Database->quote($target)."
;";
my $password = $anvil->Database->query({ query => $query, source => $THIS_FILE, line => __LINE__ })->[0]->[0];
my ($output, $error, $return_code) = $anvil->Remote->call({
remote_user => $remote_user,
target => $target,
password => $password,
shell_call => $anvil->data->{path}{exe}{echo}." 1",
no_cache => 1,
});
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
output => $output,
error => $error,
return_code => $return_code
} });
if ($output eq "1")
{
$ssh_fh = $anvil->data->{cache}{ssh_fh}{$ssh_fh_key};
delete $anvil->data->{cache}{ssh_fh}{$ssh_fh_key};
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
is_ssh_fh_defined => defined $ssh_fh ? 1 : 0
} });
}
$ssh_fh->system({ ssh_opts => [ "-O", "forward",
"-L0.0.0.0:".$forward_local_port.":0.0.0.0:".$forward_remote_port ] });
return 0;
}
sub close_ssh_tunnel
{
if (defined $ssh_fh->disconnect)
{
$ssh_fh->disconnect();
$anvil->Log->variables({ source => $THIS_FILE, line => __LINE__, level => 2, list => {
message => "SSH tunnel disconnected."
} });
}
}
$SIG->{INT} = \&close_ssh_tunnel;
$SIG->{TERM} = \&close_ssh_tunnel;
$anvil->Get->switches;
$anvil->Database->connect;
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 2, secure => 0, key => "log_0132" });
if (not $anvil->data->{sys}{database}{connections})
{
# No databases, exit.
$anvil->Log->entry({ source => $THIS_FILE, line => __LINE__, level => 0, 'print' => 1, priority => "err", key => "error_0003" });
$anvil->nice_exit({ exit_code => 1 });
}
if (open_ssh_tunnel({
remote_user => $anvil->data->{switches}{'remote-user'},
target => $anvil->data->{switches}{'target'},
port => $anvil->data->{switches}{'port'},
forward_local_port => $anvil->data->{switches}{'forward-local-port'},
forward_remote_port => $anvil->data->{switches}{'forward-remote-port'}
}) > 0)
{
$anvil->nice_exit({ exit_code => 1 });
}
my $is_ssh_tunnel_alive = 1;
while($is_ssh_tunnel_alive)
{
$is_ssh_tunnel_alive = $ssh_fh->test('echo');
sleep(60);
}
close_ssh_tunnel();
$anvil->nice_exit({ exit_code => 0 });
Loading…
Cancel
Save