From 057379e2790f69774518401a7c3c87022099fbf0 Mon Sep 17 00:00:00 2001 From: MrZaKaRiA Date: Sat, 6 Dec 2025 00:01:45 +0100 Subject: [PATCH] Expand Docker Webmin module features and translations --- README.md | 10 ++ docker.wbm.gz | Bin 0 -> 19426 bytes docker/command.cgi | 161 ++++++++++++++++++++- docker/container.cgi | 77 +++++++++- docker/docker-lib.pl | 290 +++++++++++++++++++++++++++++++++++++- docker/index.cgi | 317 +++++++++++++++++++++++++++++++++++------- docker/lang/ar | 105 ++++++++++++++ docker/lang/cn | 105 ++++++++++++++ docker/lang/en | 81 ++++++++++- docker/lang/es | 105 ++++++++++++++ docker/lang/fr | 105 ++++++++++++++ docker/lang/it | 81 ++++++++++- docker/lang/ja | 105 ++++++++++++++ docker/lang/pl | 79 +++++++++++ docker/lang/pt | 105 ++++++++++++++ docker/module.info.ar | 4 + docker/module.info.cn | 4 + docker/module.info.es | 4 + docker/module.info.fr | 4 + docker/module.info.ja | 4 + docker/module.info.pt | 4 + 21 files changed, 1684 insertions(+), 66 deletions(-) create mode 100644 docker.wbm.gz create mode 100644 docker/lang/ar create mode 100644 docker/lang/cn create mode 100644 docker/lang/es create mode 100644 docker/lang/fr create mode 100644 docker/lang/ja create mode 100644 docker/lang/pt create mode 100644 docker/module.info.ar create mode 100644 docker/module.info.cn create mode 100644 docker/module.info.es create mode 100644 docker/module.info.fr create mode 100644 docker/module.info.ja create mode 100644 docker/module.info.pt diff --git a/README.md b/README.md index 98c6566..852af6f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,16 @@ Experimental rootless docker support has been added via [docker contexts](https: Screenshot showing monitor support options +## What's new (expanded features) + +- Container creation/clone wizard with env/ports/volumes, restart policy and healthcheck; one-click exec with inline logs download and host↔container copy helpers. +- Image management: pull/push/tag, build from Dockerfile content, prune dangling images, and update checks. +- Compose runner: choose a compose file path and run `up -d`, `down`, `ps`, or `logs`. +- Storage: list/inspect/prune volumes and networks; create new volumes and networks. +- Registry credentials: save and login to a registry for private pulls. +- Security: trigger `docker scan`/`trivy` if present to scan images. +- Context management: list available Docker contexts and switch within the module. + ## Contributing Improvements or fixes are welcome, please raise it under issues and link to your branch. diff --git a/docker.wbm.gz b/docker.wbm.gz new file mode 100644 index 0000000000000000000000000000000000000000..b1fb51c6741640c722325aee227a41cfe020ed18 GIT binary patch literal 19426 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 0) { + $dest .= "?" . join("&", @params); + } + &redirect($dest); +} + if ($command eq 'start') { $err = container_command($in{'container'}, 'start'); } - -if ($command eq 'stop') { +elsif ($command eq 'stop') { $err = container_command($in{'container'}, 'stop'); } +elsif ($command eq 'restart') { + $err = container_command($in{'container'}, 'restart'); +} +elsif ($command eq 'create') { + my ($code, $result) = create_container_from_opts( + 'name' => $in{'name'}, + 'image' => $in{'image'}, + 'env' => $in{'env'}, + 'ports' => $in{'ports'}, + 'volumes' => $in{'volumes'}, + 'restart' => $in{'restart'}, + 'healthcheck' => $in{'healthcheck'}, + 'command' => $in{'cmd'} + ); + $err = $result if $code; + finish_redirect(text('label_created'), $err); +} +elsif ($command eq 'clone') { + my ($code, $result) = clone_container($in{'source'}, $in{'name'}); + $err = $result if $code; + finish_redirect(text('label_cloned'), $err); +} +elsif ($command eq 'pull_image') { + my ($code, $result) = pull_image($in{'image'}); + $err = $result if $code; + finish_redirect(text('label_pulled'), $err); +} +elsif ($command eq 'push_image') { + my ($code, $result) = push_image($in{'push_image'}); + $err = $result if $code; + finish_redirect(text('label_pushed'), $err); +} +elsif ($command eq 'check_image') { + my ($code, $result) = check_image_update($in{'image'}); + $err = $result if $code; + finish_redirect(text('label_checked'), $err); +} +elsif ($command eq 'tag_image') { + my ($code, $result) = tag_image($in{'from'}, $in{'to'}); + $err = $result if $code; + finish_redirect(text('label_tagged'), $err); +} +elsif ($command eq 'build_image') { + my ($code, $result) = build_image_from_dockerfile($in{'tag'}, $in{'dockerfile'}); + $err = $result if $code; + finish_redirect(text('label_built'), $err); +} +elsif ($command eq 'prune_images') { + my ($code, $result) = prune_images(); + $err = $result if $code; + finish_redirect(text('label_pruned'), $err); +} +elsif ($command eq 'compose') { + my ($code, $result) = compose_command($in{'compose_file'}, $in{'compose_action'}); + $err = $result if $code; + $config{'compose_file'} = $in{'compose_file'}; + save_module_config(%config); + finish_redirect(text('label_compose_ran'), $err); +} +elsif ($command eq 'volume_create') { + my ($code, $result) = create_volume($in{'volume_name'}, $in{'volume_driver'}); + $err = $result if $code; + finish_redirect(text('label_volume_created'), $err); +} +elsif ($command eq 'volume_prune') { + my ($code, $result) = prune_volumes(); + $err = $result if $code; + finish_redirect(text('label_prune_volumes'), $err); +} +elsif ($command eq 'inspect_volume') { + my ($code, $result) = inspect_volume($in{'volume'}); + &ui_print_header(undef, text('label_volume'), "", undef, undef); + if ($code) { + print ui_alert_box($result, 'danger'); + } else { + print "

" . html_escape($result) . "
"; + } + &ui_print_footer("/", text('index_return')); + exit; +} +elsif ($command eq 'network_create') { + my ($code, $result) = create_network($in{'network_name'}, $in{'network_driver'}); + $err = $result if $code; + finish_redirect(text('label_network_created'), $err); +} +elsif ($command eq 'network_prune') { + my ($code, $result) = prune_networks(); + $err = $result if $code; + finish_redirect(text('label_prune_networks'), $err); +} +elsif ($command eq 'inspect_network') { + my ($code, $result) = docker_command('network inspect ' . $in{'network'}); + &ui_print_header(undef, text('label_network'), "", undef, undef); + if ($code) { + print ui_alert_box($result, 'danger'); + } else { + print "
" . html_escape($result) . "
"; + } + &ui_print_footer("/", text('index_return')); + exit; +} +elsif ($command eq 'registry_login') { + $config{'registry_server'} = $in{'registry_server'}; + $config{'registry_user'} = $in{'registry_user'}; + $config{'registry_pass'} = $in{'registry_pass'}; + save_module_config(%config); -if ($command eq 'restart') { - $err = container_command($in{'container'}, 'restart') + if ($in{'registry_user'} && $in{'registry_pass'}) { + my $cmd = 'echo "' . $in{'registry_pass'} . '" | docker login ' . $in{'registry_server'} . ' -u "' . $in{'registry_user'} . '" --password-stdin'; + my ($out, $fail); + my $code = execute_command($cmd, undef, \$out, \$fail, 0, 0); + if ($code != 0) { + $err = $fail; + } + } + finish_redirect(text('label_registry_saved'), $err); +} +elsif ($command eq 'scan_image') { + my ($code, $result) = scan_image($in{'image'}); + &ui_print_header(undef, text('label_scan'), "", undef, undef); + if ($code) { + print ui_alert_box($result, 'danger'); + } else { + print "
" . html_escape($result) . "
"; + } + &ui_print_footer("/", text('index_return')); + exit; +} +elsif ($command eq 'set_context') { + my ($code, $result) = set_context($in{'context'}); + $err = $result if $code; + finish_redirect(text('label_context_set'), $err); } -&error($err) if ($err); +if ($err) { &error($err); } -&redirect(""); \ No newline at end of file +if ($return_tab) { + finish_redirect("", ""); +} else { + &redirect(""); +} diff --git a/docker/container.cgi b/docker/container.cgi index 962d265..fe3f60d 100644 --- a/docker/container.cgi +++ b/docker/container.cgi @@ -10,6 +10,7 @@ our (%in); my $container = $in{'container'}; my $tab = $in{'tab'}; +my $action = $in{'action'}; my ($code, $result) = get_container_attr($container, 'Name'); if ($code) { # Bad container id @@ -22,16 +23,24 @@ my $container_name = substr($result, 1); ui_print_header(undef, text('view_container_title', $container_name), "", undef, undef, $in{'nonavlinks'}); my @tabs = ( [ 'log', &text('tab_log') ], - [ 'inspect', &text('tab_inspect') ] ); + [ 'inspect', &text('tab_inspect') ], + [ 'exec', &text('tab_exec') ] ); print ui_tabs_start(\@tabs, 'log', $tab, 1); # LOGS TAB my ($max_lines) = $in{'max-lines'} || 50; print ui_tabs_start_tab('mode', 'log'); -my ($failed, $result) = container_logs($container, $max_lines, $in{'filter'} || "", $in{'basic-search'} || 0); +my ($failed, $log_result) = container_logs($container, $max_lines, $in{'filter'} || "", $in{'basic-search'} || 0); if ($failed) { - print ui_alert_box($result, 'danger'); + print ui_alert_box($log_result, 'danger'); +} + +if ($in{'download'}) { + print "Content-type: text/plain\n"; + print "Content-Disposition: attachment; filename=\"$container_name.log\"\n\n"; + print $log_result; + exit; } print &ui_form_start("container.cgi"); @@ -45,6 +54,7 @@ print ui_checkbox("basic-search", 1, "Ignore special", html_escape($in{'basic-se print &ui_submit(text('label_refresh')); print &ui_form_end(),"
\n"; +print "

" . text('label_download_log') . "

"; print ' '; -print "
" . $result . "
"; +print "
" . $log_result . "
"; print ui_tabs_end_tab('mode', 'log'); @@ -99,6 +109,65 @@ if ($failedB) { } print ui_tabs_end_tab('mode', 'inspect'); +# EXEC TAB +print ui_tabs_start_tab('mode', 'exec'); +if ($action && $action eq 'exec') { + my ($ecode, $exec_result) = exec_in_container($container, $in{'cmd'}); + if ($ecode) { + print ui_alert_box($exec_result, 'danger'); + } else { + print "
" . html_escape($exec_result) . "
"; + } +} + +if ($action && $action eq 'copy_to') { + my ($ccode, $cresult) = copy_to_container($in{'host_path'}, $container, $in{'target_path'}); + if ($ccode) { print ui_alert_box($cresult, 'danger'); } + else { print ui_alert_box(text('label_copy_ok'), 'info'); } +} + +if ($action && $action eq 'copy_from') { + my ($ccode, $cresult) = copy_from_container($container, $in{'source_path'}, $in{'host_dest'}); + if ($ccode) { print ui_alert_box($cresult, 'danger'); } + else { print ui_alert_box(text('label_copy_ok'), 'info'); } +} + +print &ui_form_start("container.cgi"); +print &ui_hidden("container", $container); +print &ui_hidden("tab", "exec"); +print &ui_hidden("action", "exec"); +print &ui_table_start(text('label_exec')); +print &ui_table_row(text('label_command'), &ui_textbox("cmd", html_escape($in{'cmd'}), 50)); +print &ui_table_end(); +print &ui_submit(text('label_run')); +print &ui_form_end(); + +print ui_hr(); +print &ui_form_start("container.cgi"); +print &ui_hidden("container", $container); +print &ui_hidden("tab", "exec"); +print &ui_hidden("action", "copy_to"); +print &ui_table_start(text('label_copy_to')); +print &ui_table_row(text('label_host_path'), &ui_textbox("host_path", "", 40)); +print &ui_table_row(text('label_target_path'), &ui_textbox("target_path", "/tmp", 30)); +print &ui_table_end(); +print &ui_submit(text('label_copy')); +print &ui_form_end(); + +print ui_hr(); +print &ui_form_start("container.cgi"); +print &ui_hidden("container", $container); +print &ui_hidden("tab", "exec"); +print &ui_hidden("action", "copy_from"); +print &ui_table_start(text('label_copy_from')); +print &ui_table_row(text('label_source_path'), &ui_textbox("source_path", "/", 30)); +print &ui_table_row(text('label_host_dest'), &ui_textbox("host_dest", "/tmp", 30)); +print &ui_table_end(); +print &ui_submit(text('label_copy')); +print &ui_form_end(); + +print ui_tabs_end_tab('mode', 'exec'); + print ui_tabs_end(); &ui_print_footer("", text('index_return')); diff --git a/docker/docker-lib.pl b/docker/docker-lib.pl index 9d8153e..71086e6 100644 --- a/docker/docker-lib.pl +++ b/docker/docker-lib.pl @@ -8,6 +8,7 @@ use warnings; use WebminCore; use JSON::PP; +use File::Temp qw(tempdir tempfile); init_config(); @@ -48,6 +49,293 @@ sub docker_command { return 0, $result } +# Prefer docker compose if present, fallback to docker-compose +sub get_compose_bin { + my $compose_bin = 'docker compose'; + if (&has_command('docker-compose')) { + $compose_bin = 'docker-compose'; + } + return $compose_bin; +} + +sub get_contexts { + my ($code, $result) = docker_command('context ls', '{{json .}}'); + if ($code != 0) { + return $result; + } + + my @contexts; + foreach my $line (split(/\n/, $result)) { + my $ctx = decode_json($line); + push @contexts, $ctx; + } + + return 0, @contexts; +} + +sub set_context { + my ($context) = @_; + our %config; + $config{'docker_context'} = $context; + &save_module_config(%config); + return 0, "Context updated"; +} + +sub parse_lines { + my ($value) = @_; + return grep { $_ ne '' } map { s/^\s+|\s+$//g; $_ } split(/\r?\n/, $value || ""); +} + +sub build_run_args { + my (%opts) = @_; + my @args = ('run', '-d'); + + push @args, '--name ' . $opts{'name'} if $opts{'name'}; + + foreach my $env (parse_lines($opts{'env'})) { + push @args, '-e "' . $env . '"'; + } + + foreach my $port (parse_lines($opts{'ports'})) { + push @args, '-p ' . $port; + } + + foreach my $vol (parse_lines($opts{'volumes'})) { + push @args, '-v ' . $vol; + } + + if ($opts{'restart'}) { + push @args, '--restart ' . $opts{'restart'}; + } + + if ($opts{'healthcheck'}) { + push @args, '--health-cmd "' . $opts{'healthcheck'} . '"'; + } + + push @args, $opts{'image'}; + + if ($opts{'command'}) { + push @args, $opts{'command'}; + } + + return @args; +} + +sub create_container_from_opts { + my (%opts) = @_; + if (!$opts{'image'}) { + return 1, "Image is required"; + } + + my @args = build_run_args(%opts); + return docker_command(join(' ', @args), '', 0); +} + +sub get_container_template { + my ($container) = @_; + my ($code, $raw) = docker_command('inspect ' . $container, '{{json .}}'); + if ($code != 0) { + return $code, $raw; + } + + my $decoded = decode_json($raw); + return 0, { + 'name' => substr($decoded->{'Name'} || '', 1), + 'image' => $decoded->{'Config'}{'Image'}, + 'env' => join("\n", @{$decoded->{'Config'}{'Env'} || []}), + 'ports' => join("\n", map { + my $k = $_; + my @bindings = @{$decoded->{'HostConfig'}{'PortBindings'}{$k} || []}; + join("\n", map { ($_->{'HostPort'} ? $_->{'HostPort'} : '') . ':' . $k } @bindings); + } keys %{$decoded->{'HostConfig'}{'PortBindings'} || {}}), + 'volumes' => join("\n", @{$decoded->{'HostConfig'}{'Binds'} || []}), + 'restart' => $decoded->{'HostConfig'}{'RestartPolicy'}{'Name'} || '', + 'healthcheck' => $decoded->{'Config'}{'Healthcheck'}{'Test'} ? join(" ", @{$decoded->{'Config'}{'Healthcheck'}{'Test'}}) : '' + }; +} + +sub clone_container { + my ($source, $newname) = @_; + my ($code, $template) = get_container_template($source); + if ($code != 0) { + return $code, $template; + } + + $template->{'name'} = $newname if $newname; + return create_container_from_opts(%{$template}); +} + +sub list_images { + my ($code, $result) = docker_command('image ls --all', '{{.Repository}}:{{.Tag}},{{.ID}},{{.Size}},{{.Digest}}'); + if ($code != 0) { + return $result; + } + + my @images; + foreach my $line (split(/\n/, $result)) { + my ($name, $id, $size, $digest) = split(/,/, $line); + push @images, { + 'name' => $name, + 'id' => $id, + 'size' => $size, + 'digest' => $digest + }; + } + return 0, @images; +} + +sub pull_image { + my ($image) = @_; + return docker_command('pull ' . $image, '', 0); +} + +sub push_image { + my ($image) = @_; + return docker_command('push ' . $image, '', 0); +} + +sub prune_images { + return docker_command('image prune -f', '', 0); +} + +sub tag_image { + my ($source, $target) = @_; + return docker_command('tag ' . $source . ' ' . $target, '', 0); +} + +sub build_image_from_dockerfile { + my ($tag, $dockerfile) = @_; + if (!$dockerfile) { + return 1, "Dockerfile content required"; + } + + my $dir = tempdir(CLEANUP => 1); + my $file = $dir . "/Dockerfile"; + open(my $fh, ">", $file) or return 1, "Unable to write temp Dockerfile"; + print $fh $dockerfile; + close($fh); + + return docker_command('build -t ' . $tag . ' ' . $dir, '', 0); +} + +sub image_history { + my ($image) = @_; + return docker_command('history ' . $image, '', 0); +} + +sub compose_command { + my ($file, $command) = @_; + my $compose = get_compose_bin(); + my $cmd = $compose . ' -f ' . $file . ' ' . $command; + my ($out, $fail); + my $code = execute_command($cmd, undef, \$out, \$fail, 0, 1); + if ($code != 0) { + return $code, $fail; + } + return 0, $out; +} + +sub list_volumes { + my ($code, $result) = docker_command('volume ls', '{{.Name}},{{.Driver}},{{.Mountpoint}}'); + if ($code != 0) { + return $result; + } + my @vols; + foreach my $line (split(/\n/, $result)) { + my ($name, $driver, $mp) = split(/,/, $line); + push @vols, { 'name' => $name, 'driver' => $driver, 'mountpoint' => $mp }; + } + return 0, @vols; +} + +sub inspect_volume { + my ($name) = @_; + return docker_command('volume inspect ' . $name, '', 0); +} + +sub create_volume { + my ($name, $driver) = @_; + my $cmd = 'volume create'; + $cmd = $cmd . ' --driver ' . $driver if $driver; + $cmd = $cmd . ' ' . $name if $name; + return docker_command($cmd, '', 0); +} + +sub prune_volumes { + return docker_command('volume prune -f', '', 0); +} + +sub list_networks { + my ($code, $result) = docker_command('network ls', '{{.Name}},{{.Driver}},{{.Scope}}'); + if ($code != 0) { + return $result; + } + my @nets; + foreach my $line (split(/\n/, $result)) { + my ($name, $driver, $scope) = split(/,/, $line); + push @nets, { 'name' => $name, 'driver' => $driver, 'scope' => $scope }; + } + return 0, @nets; +} + +sub create_network { + my ($name, $driver) = @_; + my $cmd = 'network create'; + $cmd = $cmd . ' --driver ' . $driver if $driver; + $cmd = $cmd . ' ' . $name if $name; + return docker_command($cmd, '', 0); +} + +sub prune_networks { + return docker_command('network prune -f', '', 0); +} + +sub connect_network { + my ($network, $container) = @_; + return docker_command('network connect ' . $network . ' ' . $container, '', 0); +} + +sub disconnect_network { + my ($network, $container) = @_; + return docker_command('network disconnect ' . $network . ' ' . $container, '', 0); +} + +sub exec_in_container { + my ($container, $cmd) = @_; + return docker_command('exec ' . $container . ' ' . $cmd, '', 0); +} + +sub copy_to_container { + my ($host_path, $container, $target_path) = @_; + return docker_command('cp ' . $host_path . ' ' . $container . ':' . $target_path, '', 0); +} + +sub copy_from_container { + my ($container, $source_path, $host_path) = @_; + return docker_command('cp ' . $container . ':' . $source_path . ' ' . $host_path, '', 0); +} + +sub check_image_update { + my ($image) = @_; + my ($code, $result) = docker_command('pull ' . $image, '', 0); + if ($code != 0) { + return $code, $result; + } + return 0, $result; +} + +sub scan_image { + my ($image) = @_; + my ($code, $result) = docker_command('scan ' . $image, '', 0); + if ($code == 0) { + return $code, $result; + } + if (&has_command('trivy')) { + my $cmd = 'trivy image ' . $image; + return docker_command($cmd, '', 0); + } + return 1, "No scanner found (docker scan or trivy)"; +} sub get_status { my ($code, $result) = docker_command('info'); @@ -241,4 +529,4 @@ sub get_status_icon return '' . ($up ? "✓" : "✗") . ''; -} \ No newline at end of file +} diff --git a/docker/index.cgi b/docker/index.cgi index 0a17234..2642e40 100644 --- a/docker/index.cgi +++ b/docker/index.cgi @@ -5,24 +5,43 @@ use warnings; use Data::Dumper; require './docker-lib.pl'; +&ReadParse(); + +our (%config, %in); ui_print_header(undef, &text('index_title'), "", undef, 1, 1); if (!&has_command('docker')) { - &ui_print_endpage(&text('not_installed')); #, "$config{'syslog_conf'}", "../config.cgi?$module_name")); + &ui_print_endpage(&text('not_installed')); } -our (%config); - if ($config{'docker_context'}) { print ui_alert_box('Using docker context: ' . html_escape($config{'docker_context'}) . ' ' . &help_search_link("docker context", "google"), 'warn'); } -my @tabs = ( [ 'info', &text('tab_info') ], - [ 'containers', &text('tab_containers') ] ); +if ($in{'msg'}) { + print ui_alert_box(html_escape($in{'msg'}), 'info'); +} + +if ($in{'err'}) { + print ui_alert_box(html_escape($in{'err'}), 'danger'); +} + +my $active_tab = $in{'tab'} || 'info'; -print ui_tabs_start(\@tabs, 'info', 'containers', 1); +my @tabs = ( + [ 'info', &text('tab_info') ], + [ 'containers', &text('tab_containers') ], + [ 'images', &text('tab_images') ], + [ 'compose', &text('tab_compose') ], + [ 'storage', &text('tab_storage') ], + [ 'registry', &text('tab_registry') ], + [ 'security', &text('tab_security') ], + [ 'contexts', &text('tab_contexts') ] +); + +print ui_tabs_start(\@tabs, 'info', $active_tab, 1); # INFO TAB print ui_tabs_start_tab('mode', 'info'); @@ -30,10 +49,8 @@ my($status_fail, $status) = get_status(); if ($status_fail) { print ui_alert_box($status, 'danger'); } else { - #print circular_grid($status); # Ugly recursive output print "
" . html_escape($status) . "
"; } - print ui_tabs_end_tab('mode', 'info'); # CONTAINERS TAB @@ -45,49 +62,23 @@ if ($fail) { print ui_alert_box($fail, 'danger'); } else { print &ui_form_start(""); + print &ui_hidden("tab", "containers"); print &ui_submit(text('label_refresh')); print &ui_form_end(),"
\n"; if (scalar(@containers) == 0) { - print "No containers defined"; + print "No containers defined"; } else { - # Provide some basic responsive support across all themes print ''; + .visible-xs,.visible-sm,.visible-md,.visible-lg { display: none !important } + @media(max-width: 767px) { .visible-xs { display:block !important } } + @media(max-width: 767px) { .hidden-xs { display:none !important } } + @media(min-width: 768px) and (max-width:991px) { .hidden-sm { display:none !important } } + @media(min-width: 992px) and (max-width:1199px) { .hidden-md { display:none !important } } + @media(min-width: 1200px) { .hidden-lg { display:none !important } } + '; print &ui_columns_start([ &text('label_name'), @@ -106,20 +97,21 @@ if ($fail) { ]); foreach my $u (@containers) { - my ($status) = $u->{'status'}; + my $status = $u->{'status'}; + my $healthy = starts_with($status, "Up") && $status =~ /healthy/ ? "healthy " : ""; - # columns will be consolidated/hidden across various screen sizes print ui_columns_row([ &ui_link('container.cgi?tab=inspect&container=' . urlize($u->{'id'}), html_escape($u->{'name'})) . "", html_escape($u->{'image'}), - get_status_icon($status) . " " . html_escape($status), + $healthy . get_status_icon($status) . " " . html_escape($status), html_escape($stats{$u->{'id'}}{'cpu'}), "" . html_escape($stats{$u->{'id'}}{'memUsage'}) . " (" . html_escape($stats{$u->{'id'}}{'mem'}) . ")", (get_container_up($status) ? sprintf("%s", urlize($u->{'name'}), &text('label_stop')) . ' | ' . sprintf("%s", urlize($u->{'name'}), &text('label_restart')) : sprintf("%s", urlize($u->{'name'}), &text('label_start'))) . ' | ' . - &ui_link('container.cgi?tab=log&container=' . urlize($u->{'id'}), &text('label_viewlog')) + &ui_link('container.cgi?tab=log&container=' . urlize($u->{'id'}), &text('label_viewlog')) . ' | ' . + &ui_link('container.cgi?tab=exec&container=' . urlize($u->{'id'}), &text('label_exec')) ], [ "", # name "class='hidden-xs hidden-sm'", #image @@ -129,32 +121,253 @@ if ($fail) { "style='text-align: right; word-break: auto-phrase;' class='hidden-xs text-right'", #start/stop/restart ]); - # This row is shown on xs-devices only print ui_columns_row([ "CPU: " . html_escape($stats{$u->{'id'}}{'cpu'}) . "
MEM: " . html_escape($stats{$u->{'id'}}{'memUsage'}) . " (" . html_escape($stats{$u->{'id'}}{'mem'}) . ")", (get_container_up($status) ? sprintf("%s", urlize($u->{'name'}), &text('label_stop')) . ' | ' . sprintf("%s", urlize($u->{'name'}), &text('label_restart')) : sprintf("%s", urlize($u->{'name'}), &text('label_start'))) . ' | ' . - &ui_link('container.cgi?tab=log&container=' . urlize($u->{'id'}), &text('label_viewlog')) + &ui_link('container.cgi?tab=log&container=' . urlize($u->{'id'}), &text('label_viewlog')) . ' | ' . + &ui_link('container.cgi?tab=exec&container=' . urlize($u->{'id'}), &text('label_exec')) ], [ "class='display-none visible-xs'", # Perf "style='word-break: auto-phrase;' class='display-none visible-xs'", #start/stop/restart ]); + } + print ui_columns_end(); + } +} - +print ui_hr(); +print &ui_form_start("command.cgi"); +print &ui_hidden("c", "create"); +print &ui_hidden("return_tab", "containers"); +print &ui_table_start(text('label_create')); +print &ui_table_row(text('label_name'), &ui_textbox("name", "", 30)); +print &ui_table_row(text('label_image'), &ui_textbox("image", "", 40)); +print &ui_table_row(text('label_command'), &ui_textbox("cmd", "", 40)); +print &ui_table_row(text('label_env'), &ui_textarea("env", "", 3, 60)); +print &ui_table_row(text('label_ports'), &ui_textarea("ports", "", 3, 60)); +print &ui_table_row(text('label_volumes'), &ui_textarea("volumes", "", 3, 60)); +print &ui_table_row(text('label_restart'), &ui_select("restart", "", ["", "no", "on-failure", "always", "unless-stopped"])); +print &ui_table_row(text('label_health'), &ui_textbox("healthcheck", "", 40)); +print &ui_table_end(); +print &ui_submit(text('label_create')); +print &ui_form_end(); + +if (!$fail) { + print ui_hr(); + print &ui_form_start("command.cgi"); + print &ui_hidden("c", "clone"); + print &ui_hidden("return_tab", "containers"); + print &ui_table_start(text('label_clone')); + print &ui_table_row(text('label_source'), &ui_select("source", "", [ map { [ $_->{'name'}, $_->{'name'} ] } @containers ])); + print &ui_table_row(text('label_name'), &ui_textbox("name", "", 30)); + print &ui_table_end(); + print &ui_submit(text('label_clone')); + print &ui_form_end(); +} +print ui_tabs_end_tab('mode', 'containers'); + +# IMAGES TAB +print ui_tabs_start_tab('mode', 'images'); +my ($img_fail, @images) = list_images(); +if ($img_fail) { + print ui_alert_box($img_fail, 'danger'); +} else { + print &ui_columns_start([ + text('label_image'), + text('label_size'), + text('label_digest'), + text('label_actions'), + ]); + foreach my $img (@images) { + print ui_columns_row([ + html_escape($img->{'name'}), + html_escape($img->{'size'}), + html_escape($img->{'digest'}), + sprintf("Pull | Push | Check updates", urlize($img->{'name'}), urlize($img->{'name'}), urlize($img->{'name'})) + ]); } print ui_columns_end(); +} + +print ui_hr(); +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "images"); +print &ui_hidden("c", "pull_image"); +print &ui_table_start(text('label_pull')); +print &ui_table_row(text('label_image'), &ui_textbox("image", "", 50)); +print &ui_table_end(); +print &ui_submit(text('label_pull')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "images"); +print &ui_hidden("c", "push_image"); +print &ui_table_start(text('label_push')); +print &ui_table_row(text('label_image'), &ui_textbox("push_image", "", 50)); +print &ui_table_end(); +print &ui_submit(text('label_push')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "images"); +print &ui_hidden("c", "tag_image"); +print &ui_table_start(text('label_tag')); +print &ui_table_row(text('label_from'), &ui_textbox("from", "", 30)); +print &ui_table_row(text('label_to'), &ui_textbox("to", "", 30)); +print &ui_table_end(); +print &ui_submit(text('label_tag')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "images"); +print &ui_hidden("c", "build_image"); +print &ui_table_start(text('label_build_tag')); +print &ui_table_row(text('label_tag'), &ui_textbox("tag", "", 30)); +print &ui_table_row(text('label_dockerfile'), &ui_textarea("dockerfile", "", 6, 70)); +print &ui_table_end(); +print &ui_submit(text('label_build_tag')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "images"); +print &ui_hidden("c", "prune_images"); +print &ui_submit(text('label_prune_images')); +print &ui_form_end(); +print ui_tabs_end_tab('mode', 'images'); + +# COMPOSE TAB +print ui_tabs_start_tab('mode', 'compose'); +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "compose"); +print &ui_hidden("c", "compose"); +print &ui_table_start(text('label_compose')); +print &ui_table_row(text('label_compose_file'), &ui_textbox("compose_file", $config{'compose_file'} || "docker-compose.yml", 60)); +print &ui_table_row(text('label_compose_cmd'), &ui_select("compose_action", "", ["up -d", "down", "ps", "logs"])); +print &ui_table_end(); +print &ui_submit(text('label_run_compose')); +print &ui_form_end(); +print ui_tabs_end_tab('mode', 'compose'); + +# STORAGE TAB +print ui_tabs_start_tab('mode', 'storage'); +my ($vol_fail, @vols) = list_volumes(); +my ($net_fail, @nets) = list_networks(); +if ($vol_fail) { print ui_alert_box($vol_fail, 'danger'); } +else { + print ui_subheading(text('label_volumes')); + print &ui_columns_start([text('label_name'), text('label_driver'), text('label_mount'), text('label_actions')]); + foreach my $v (@vols) { + print ui_columns_row([ + html_escape($v->{'name'}), + html_escape($v->{'driver'}), + html_escape($v->{'mountpoint'}), + sprintf("Inspect", urlize($v->{'name'})) + ]); } + print ui_columns_end(); } -print ui_tabs_end_tab('mode', 'containers'); +print ui_hr(); +if ($net_fail) { print ui_alert_box($net_fail, 'danger'); } +else { + print ui_subheading(text('label_networks')); + print &ui_columns_start([text('label_name'), text('label_driver'), text('label_scope'), text('label_actions')]); + foreach my $n (@nets) { + print ui_columns_row([ + html_escape($n->{'name'}), + html_escape($n->{'driver'}), + html_escape($n->{'scope'}), + sprintf("Inspect", urlize($n->{'name'})) + ]); + } + print ui_columns_end(); +} + +print ui_hr(); +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "storage"); +print &ui_hidden("c", "volume_create"); +print &ui_table_start(text('label_volume_create')); +print &ui_table_row(text('label_name'), &ui_textbox("volume_name", "", 30)); +print &ui_table_row(text('label_driver'), &ui_textbox("volume_driver", "", 20)); +print &ui_table_end(); +print &ui_submit(text('label_create')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "storage"); +print &ui_hidden("c", "network_create"); +print &ui_table_start(text('label_network_create')); +print &ui_table_row(text('label_name'), &ui_textbox("network_name", "", 30)); +print &ui_table_row(text('label_driver'), &ui_textbox("network_driver", "", 20)); +print &ui_table_end(); +print &ui_submit(text('label_create')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "storage"); +print &ui_hidden("c", "volume_prune"); +print &ui_submit(text('label_prune_volumes')); +print &ui_form_end(); + +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "storage"); +print &ui_hidden("c", "network_prune"); +print &ui_submit(text('label_prune_networks')); +print &ui_form_end(); +print ui_tabs_end_tab('mode', 'storage'); + +# REGISTRY TAB +print ui_tabs_start_tab('mode', 'registry'); +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "registry"); +print &ui_hidden("c", "registry_login"); +print &ui_table_start(text('label_registry')); +print &ui_table_row(text('label_registry_server'), &ui_textbox("registry_server", $config{'registry_server'}, 40)); +print &ui_table_row(text('label_registry_user'), &ui_textbox("registry_user", $config{'registry_user'}, 30)); +print &ui_table_row(text('label_registry_pass'), &ui_password("registry_pass", $config{'registry_pass'}, 30)); +print &ui_table_end(); +print &ui_submit(text('label_save_login')); +print &ui_form_end(); +print ui_tabs_end_tab('mode', 'registry'); + +# SECURITY TAB +print ui_tabs_start_tab('mode', 'security'); +print &ui_form_start("command.cgi"); +print &ui_hidden("return_tab", "security"); +print &ui_hidden("c", "scan_image"); +print &ui_table_start(text('label_scan')); +print &ui_table_row(text('label_image'), &ui_textbox("image", "", 50)); +print &ui_table_end(); +print &ui_submit(text('label_scan')); +print &ui_form_end(); +print ui_tabs_end_tab('mode', 'security'); + +# CONTEXT TAB +print ui_tabs_start_tab('mode', 'contexts'); +my ($ctx_fail, @contexts) = get_contexts(); +if ($ctx_fail) { + print ui_alert_box($ctx_fail, 'danger'); +} else { + print &ui_form_start("command.cgi"); + print &ui_hidden("return_tab", "contexts"); + print &ui_hidden("c", "set_context"); + print &ui_table_start(text('label_context')); + print &ui_table_row(text('label_current_context'), $config{'docker_context'} || 'default'); + print &ui_table_row(text('label_available_contexts'), &ui_select("context", $config{'docker_context'}, [ map { [ $_->{'Name'}, $_->{'Name'} . ($_->{'Current'} ? " (current)" : "") ] } @contexts ])); + print &ui_table_end(); + print &ui_submit(text('label_switch_context')); + print &ui_form_end(); +} +print ui_tabs_end_tab('mode', 'contexts'); print ui_tabs_end(); ui_print_footer("/", &text(('index'))); -# if using authentic theme, enable codemirror for readability print ""; diff --git a/docker/lang/ar b/docker/lang/ar new file mode 100644 index 0000000..d8db3db --- /dev/null +++ b/docker/lang/ar @@ -0,0 +1,105 @@ +not_installed=لم يتم العثور على Docker في هذا النظام + +index_title=إدارة حاويات Docker +index_return=قائمة الحاويات + +view_container_title=الحاوية $1 + +tab_info=معلومات Docker +tab_containers=الحاويات +tab_log=السجلات +tab_inspect=فحص + +command_err=تعذر تنفيذ الأمر + +label_name=الاسم +label_label=الوسم +label_running=يعمل +label_runningfor=مدة التشغيل +label_cpu=المعالج +label_mem=الذاكرة + +label_start=بدء +label_stop=إيقاف +label_restart=إعادة تشغيل +label_refresh=تحديث +label_viewlog=عرض السجل + +tab_images=الصور +tab_compose=Compose +tab_storage=التخزين +tab_registry=المستودع +tab_security=الأمان +tab_contexts=السياقات + +label_exec=تنفيذ +label_download_log=تنزيل السجل +label_command=أمر +label_run=تشغيل +label_copy=نسخ +label_copy_ok=اكتملت النسخ +label_copy_to=نسخ من المضيف إلى الحاوية +label_copy_from=نسخ من الحاوية إلى المضيف +label_host_path=مسار المضيف +label_target_path=مسار الحاوية الهدف +label_source_path=مسار المصدر في الحاوية +label_host_dest=الوجهة على المضيف + +label_image=الصورة +label_size=الحجم +label_digest=الخلاصة +label_actions=إجراءات +label_pull=سحب الصورة +label_push=دفع الصورة +label_tag=وضع وسم للصورة +label_from=من +label_to=إلى +label_build_tag=بناء الصورة +label_dockerfile=محتوى Dockerfile +label_prune_images=حذف الصور غير المستخدمة +label_pruned=تم حذف الصور +label_built=تم بناء الصورة +label_tagged=تم وسم الصورة +label_pulled=تم سحب الصورة +label_pushed=تم دفع الصورة +label_checked=تم التحقق من التحديث +label_image_actions=إجراءات الصورة + +label_compose=ملف Compose +label_compose_file=مسار ملف Compose +label_compose_cmd=إجراء Compose +label_run_compose=تشغيل Compose +label_compose_ran=تم تنفيذ أمر Compose + +label_volumes=الأحجام +label_networks=الشبكات +label_volume_create=إنشاء حجم +label_network_create=إنشاء شبكة +label_mount=نقطة الربط +label_scope=النطاق +label_driver=المشغل +label_prune_volumes=حذف الأحجام غير المستخدمة +label_prune_networks=حذف الشبكات غير المستخدمة +label_volume_created=تم إنشاء الحجم +label_network_created=تم إنشاء الشبكة + +label_registry=بيانات اعتماد المستودع +label_registry_server=خادم المستودع +label_registry_user=اسم المستخدم +label_registry_pass=كلمة المرور +label_save_login=حفظ وتسجيل الدخول +label_registry_saved=تم حفظ بيانات الاعتماد + +label_scan=فحص أمني + +label_context=السياق +label_current_context=السياق الحالي +label_available_contexts=السياقات المتاحة +label_switch_context=تبديل السياق +label_context_set=تم تحديث السياق + +label_create=إنشاء +label_clone=استنساخ الحاوية +label_source=الحاوية المصدر +label_created=تم إنشاء الحاوية +label_cloned=تم استنساخ الحاوية diff --git a/docker/lang/cn b/docker/lang/cn new file mode 100644 index 0000000..8ed3642 --- /dev/null +++ b/docker/lang/cn @@ -0,0 +1,105 @@ +not_installed=此系统未找到 Docker + +index_title=Docker 容器管理 +index_return=容器列表 + +view_container_title=容器 $1 + +tab_info=Docker 信息 +tab_containers=容器 +tab_log=日志 +tab_inspect=检查 + +command_err=无法运行命令 + +label_name=名称 +label_label=标签 +label_running=运行中 +label_runningfor=已运行 +label_cpu=CPU +label_mem=内存 + +label_start=启动 +label_stop=停止 +label_restart=重启 +label_refresh=刷新 +label_viewlog=查看日志 + +tab_images=镜像 +tab_compose=Compose +tab_storage=存储 +tab_registry=仓库 +tab_security=安全 +tab_contexts=上下文 + +label_exec=执行 +label_download_log=下载日志 +label_command=命令 +label_run=运行 +label_copy=复制 +label_copy_ok=复制完成 +label_copy_to=从主机复制到容器 +label_copy_from=从容器复制到主机 +label_host_path=主机路径 +label_target_path=容器目标路径 +label_source_path=容器源路径 +label_host_dest=主机目标 + +label_image=镜像 +label_size=大小 +label_digest=摘要 +label_actions=操作 +label_pull=拉取镜像 +label_push=推送镜像 +label_tag=标记镜像 +label_from=源 +label_to=目标 +label_build_tag=构建镜像 +label_dockerfile=Dockerfile 内容 +label_prune_images=清理未用镜像 +label_pruned=镜像已清理 +label_built=镜像已构建 +label_tagged=镜像已标记 +label_pulled=镜像已拉取 +label_pushed=镜像已推送 +label_checked=已检查更新 +label_image_actions=镜像操作 + +label_compose=Compose 文件 +label_compose_file=Compose 文件路径 +label_compose_cmd=Compose 操作 +label_run_compose=运行 Compose +label_compose_ran=已运行 Compose 命令 + +label_volumes=卷 +label_networks=网络 +label_volume_create=创建卷 +label_network_create=创建网络 +label_mount=挂载点 +label_scope=范围 +label_driver=驱动 +label_prune_volumes=清理未用卷 +label_prune_networks=清理未用网络 +label_volume_created=卷已创建 +label_network_created=网络已创建 + +label_registry=仓库凭证 +label_registry_server=仓库服务器 +label_registry_user=用户名 +label_registry_pass=密码 +label_save_login=保存并登录 +label_registry_saved=已保存凭证 + +label_scan=安全扫描 + +label_context=上下文 +label_current_context=当前上下文 +label_available_contexts=可用上下文 +label_switch_context=切换上下文 +label_context_set=已更新上下文 + +label_create=创建 +label_clone=克隆容器 +label_source=源容器 +label_created=容器已创建 +label_cloned=容器已克隆 diff --git a/docker/lang/en b/docker/lang/en index 393cb8a..c943e73 100644 --- a/docker/lang/en +++ b/docker/lang/en @@ -23,4 +23,83 @@ label_start=Start label_stop=Stop label_restart=Restart label_refresh=Refresh -label_viewlog=View log \ No newline at end of file +label_viewlog=View log + +tab_images=Images +tab_compose=Compose +tab_storage=Storage +tab_registry=Registry +tab_security=Security +tab_contexts=Contexts + +label_exec=Exec +label_download_log=Download log +label_command=Command +label_run=Run +label_copy=Copy +label_copy_ok=Copy complete +label_copy_to=Copy from host to container +label_copy_from=Copy from container to host +label_host_path=Host path +label_target_path=Container target path +label_source_path=Container source path +label_host_dest=Host destination + +label_image=Image +label_size=Size +label_digest=Digest +label_actions=Actions +label_pull=Pull image +label_push=Push image +label_tag=Tag image +label_from=From +label_to=To +label_build_tag=Build image +label_dockerfile=Dockerfile contents +label_prune_images=Prune dangling images +label_pruned=Images pruned +label_built=Image built +label_tagged=Image tagged +label_pulled=Image pulled +label_pushed=Image pushed +label_checked=Image checked +label_image_actions=Image actions + +label_compose=Compose file +label_compose_file=Compose file path +label_compose_cmd=Compose action +label_run_compose=Run compose +label_compose_ran=Compose command ran + +label_volumes=Volumes +label_networks=Networks +label_volume_create=Create volume +label_network_create=Create network +label_mount=Mount +label_scope=Scope +label_driver=Driver +label_prune_volumes=Prune unused volumes +label_prune_networks=Prune unused networks +label_volume_created=Volume created +label_network_created=Network created + +label_registry=Registry credentials +label_registry_server=Registry server +label_registry_user=Username +label_registry_pass=Password +label_save_login=Save and login +label_registry_saved=Registry credentials saved + +label_scan=Security scan + +label_context=Context +label_current_context=Current context +label_available_contexts=Available contexts +label_switch_context=Switch context +label_context_set=Context updated + +label_create=Create +label_clone=Clone container +label_source=Source container +label_created=Container created +label_cloned=Container cloned diff --git a/docker/lang/es b/docker/lang/es new file mode 100644 index 0000000..ed56a62 --- /dev/null +++ b/docker/lang/es @@ -0,0 +1,105 @@ +not_installed=Docker no se encontró en este sistema + +index_title=Gestión de contenedores Docker +index_return=lista de contenedores + +view_container_title=Contenedor $1 + +tab_info=Información de Docker +tab_containers=Contenedores +tab_log=Registro +tab_inspect=Inspeccionar + +command_err=No se pudo ejecutar el comando + +label_name=Nombre +label_label=Etiqueta +label_running=En ejecución +label_runningfor=En ejecución desde +label_cpu=CPU +label_mem=MEM + +label_start=Iniciar +label_stop=Detener +label_restart=Reiniciar +label_refresh=Actualizar +label_viewlog=Ver registro + +tab_images=Imágenes +tab_compose=Compose +tab_storage=Almacenamiento +tab_registry=Registro +tab_security=Seguridad +tab_contexts=Contextos + +label_exec=Ejecutar +label_download_log=Descargar registro +label_command=Comando +label_run=Ejecutar +label_copy=Copiar +label_copy_ok=Copia completada +label_copy_to=Copiar del host al contenedor +label_copy_from=Copiar del contenedor al host +label_host_path=Ruta del host +label_target_path=Ruta destino en el contenedor +label_source_path=Ruta origen en el contenedor +label_host_dest=Destino en el host + +label_image=Imagen +label_size=Tamaño +label_digest=Digest +label_actions=Acciones +label_pull=Descargar imagen +label_push=Subir imagen +label_tag=Etiquetar imagen +label_from=Desde +label_to=Hacia +label_build_tag=Construir imagen +label_dockerfile=Contenido del Dockerfile +label_prune_images=Eliminar imágenes sin usar +label_pruned=Imágenes eliminadas +label_built=Imagen construida +label_tagged=Imagen etiquetada +label_pulled=Imagen descargada +label_pushed=Imagen subida +label_checked=Actualización comprobada +label_image_actions=Acciones de imagen + +label_compose=Archivo Compose +label_compose_file=Ruta del archivo Compose +label_compose_cmd=Acción de Compose +label_run_compose=Ejecutar Compose +label_compose_ran=Comando Compose ejecutado + +label_volumes=Volúmenes +label_networks=Redes +label_volume_create=Crear volumen +label_network_create=Crear red +label_mount=Punto de montaje +label_scope=Ámbito +label_driver=Controlador +label_prune_volumes=Eliminar volúmenes sin usar +label_prune_networks=Eliminar redes sin usar +label_volume_created=Volumen creado +label_network_created=Red creada + +label_registry=Credenciales del registro +label_registry_server=Servidor de registro +label_registry_user=Usuario +label_registry_pass=Contraseña +label_save_login=Guardar e iniciar sesión +label_registry_saved=Credenciales guardadas + +label_scan=Escaneo de seguridad + +label_context=Contexto +label_current_context=Contexto actual +label_available_contexts=Contextos disponibles +label_switch_context=Cambiar contexto +label_context_set=Contexto actualizado + +label_create=Crear +label_clone=Clonar contenedor +label_source=Contenedor origen +label_created=Contenedor creado +label_cloned=Contenedor clonado diff --git a/docker/lang/fr b/docker/lang/fr new file mode 100644 index 0000000..af9f13e --- /dev/null +++ b/docker/lang/fr @@ -0,0 +1,105 @@ +not_installed=Docker na pas été trouvé sur ce système + +index_title=Gestion des conteneurs Docker +index_return=liste des conteneurs + +view_container_title=Conteneur $1 + +tab_info=Infos Docker +tab_containers=Conteneurs +tab_log=Journal +tab_inspect=Inspecter + +command_err=La commande na pas pu être exécutée + +label_name=Nom +label_label=Libellé +label_running=Actif +label_runningfor=En cours depuis +label_cpu=CPU +label_mem=MEM + +label_start=Démarrer +label_stop=Arrêter +label_restart=Redémarrer +label_refresh=Actualiser +label_viewlog=Voir le journal + +tab_images=Images +tab_compose=Compose +tab_storage=Stockage +tab_registry=Registry +tab_security=Sécurité +tab_contexts=Contextes + +label_exec=Exécuter +label_download_log=Télécharger le journal +label_command=Commande +label_run=Exécuter +label_copy=Copier +label_copy_ok=Copie terminée +label_copy_to=Copier de lhôte vers le conteneur +label_copy_from=Copier du conteneur vers lhôte +label_host_path=Chemin hôte +label_target_path=Chemin cible du conteneur +label_source_path=Chemin source du conteneur +label_host_dest=Destination hôte + +label_image=Image +label_size=Taille +label_digest=Digest +label_actions=Actions +label_pull=Récupérer limage +label_push=Envoyer limage +label_tag=Taguer limage +label_from=Depuis +label_to=Vers +label_build_tag=Construire limage +label_dockerfile=Contenu du Dockerfile +label_prune_images=Nettoyer les images inutilisées +label_pruned=Images nettoyées +label_built=Image construite +label_tagged=Image taguée +label_pulled=Image récupérée +label_pushed=Image envoyée +label_checked=Mise à jour vérifiée +label_image_actions=Actions sur limage + +label_compose=Fichier Compose +label_compose_file=Chemin du fichier Compose +label_compose_cmd=Action Compose +label_run_compose=Exécuter Compose +label_compose_ran=Commande Compose exécutée + +label_volumes=Volumes +label_networks=Réseaux +label_volume_create=Créer un volume +label_network_create=Créer un réseau +label_mount=Point de montage +label_scope=Portée +label_driver=Driver +label_prune_volumes=Nettoyer les volumes inutilisés +label_prune_networks=Nettoyer les réseaux inutilisés +label_volume_created=Volume créé +label_network_created=Réseau créé + +label_registry=Identifiants du registre +label_registry_server=Serveur de registre +label_registry_user=Nom dutilisateur +label_registry_pass=Mot de passe +label_save_login=Enregistrer et se connecter +label_registry_saved=Identifiants de registre enregistrés + +label_scan=Analyse de sécurité + +label_context=Contexte +label_current_context=Contexte actuel +label_available_contexts=Contextes disponibles +label_switch_context=Changer de contexte +label_context_set=Contexte mis à jour + +label_create=Créer +label_clone=Cloner un conteneur +label_source=Conteneur source +label_created=Conteneur créé +label_cloned=Conteneur cloné diff --git a/docker/lang/it b/docker/lang/it index fa7b84b..b1bee26 100644 --- a/docker/lang/it +++ b/docker/lang/it @@ -23,4 +23,83 @@ label_start=Inizio label_stop=Fermare label_restart=Ricomincia label_refresh=Ricaricare -label_viewlog=Vista del registro \ No newline at end of file +label_viewlog=Vista del registro + +tab_images=Images +tab_compose=Compose +tab_storage=Storage +tab_registry=Registry +tab_security=Security +tab_contexts=Contexts + +label_exec=Exec +label_download_log=Download log +label_command=Command +label_run=Run +label_copy=Copy +label_copy_ok=Copy complete +label_copy_to=Copy from host to container +label_copy_from=Copy from container to host +label_host_path=Host path +label_target_path=Container target path +label_source_path=Container source path +label_host_dest=Host destination + +label_image=Image +label_size=Size +label_digest=Digest +label_actions=Actions +label_pull=Pull image +label_push=Push image +label_tag=Tag image +label_from=From +label_to=To +label_build_tag=Build image +label_dockerfile=Dockerfile contents +label_prune_images=Prune dangling images +label_pruned=Images pruned +label_built=Image built +label_tagged=Image tagged +label_pulled=Image pulled +label_pushed=Image pushed +label_checked=Image checked +label_image_actions=Image actions + +label_compose=Compose file +label_compose_file=Compose file path +label_compose_cmd=Compose action +label_run_compose=Run compose +label_compose_ran=Compose command ran + +label_volumes=Volumes +label_networks=Networks +label_volume_create=Create volume +label_network_create=Create network +label_mount=Mount +label_scope=Scope +label_driver=Driver +label_prune_volumes=Prune unused volumes +label_prune_networks=Prune unused networks +label_volume_created=Volume created +label_network_created=Network created + +label_registry=Registry credentials +label_registry_server=Registry server +label_registry_user=Username +label_registry_pass=Password +label_save_login=Save and login +label_registry_saved=Registry credentials saved + +label_scan=Security scan + +label_context=Context +label_current_context=Current context +label_available_contexts=Available contexts +label_switch_context=Switch context +label_context_set=Context updated + +label_create=Create +label_clone=Clone container +label_source=Source container +label_created=Container created +label_cloned=Container cloned diff --git a/docker/lang/ja b/docker/lang/ja new file mode 100644 index 0000000..1571d83 --- /dev/null +++ b/docker/lang/ja @@ -0,0 +1,105 @@ +not_installed=このシステムで Docker が見つかりません + +index_title=Docker コンテナ管理 +index_return=コンテナ一覧 + +view_container_title=コンテナ $1 + +tab_info=Docker 情報 +tab_containers=コンテナ +tab_log=ログ +tab_inspect=検査 + +command_err=コマンドを実行できませんでした + +label_name=名前 +label_label=ラベル +label_running=実行中 +label_runningfor=稼働時間 +label_cpu=CPU +label_mem=メモリ + +label_start=開始 +label_stop=停止 +label_restart=再起動 +label_refresh=更新 +label_viewlog=ログを表示 + +tab_images=イメージ +tab_compose=Compose +tab_storage=ストレージ +tab_registry=レジストリ +tab_security=セキュリティ +tab_contexts=コンテキスト + +label_exec=Exec +label_download_log=ログをダウンロード +label_command=コマンド +label_run=実行 +label_copy=コピー +label_copy_ok=コピー完了 +label_copy_to=ホストからコンテナへコピー +label_copy_from=コンテナからホストへコピー +label_host_path=ホストのパス +label_target_path=コンテナの保存先パス +label_source_path=コンテナのソースパス +label_host_dest=ホストの保存先 + +label_image=イメージ +label_size=サイズ +label_digest=ダイジェスト +label_actions=操作 +label_pull=イメージを取得 +label_push=イメージをプッシュ +label_tag=イメージにタグ +label_from=元 +label_to=先 +label_build_tag=イメージをビルド +label_dockerfile=Dockerfile の内容 +label_prune_images=未使用イメージを削除 +label_pruned=イメージを削除 +label_built=イメージを作成 +label_tagged=イメージにタグ付け +label_pulled=イメージを取得 +label_pushed=イメージをプッシュ +label_checked=更新を確認 +label_image_actions=イメージ操作 + +label_compose=Compose ファイル +label_compose_file=Compose ファイルのパス +label_compose_cmd=Compose アクション +label_run_compose=Compose を実行 +label_compose_ran=Compose コマンドを実行 + +label_volumes=ボリューム +label_networks=ネットワーク +label_volume_create=ボリュームを作成 +label_network_create=ネットワークを作成 +label_mount=マウントポイント +label_scope=スコープ +label_driver=ドライバー +label_prune_volumes=未使用ボリュームを削除 +label_prune_networks=未使用ネットワークを削除 +label_volume_created=ボリュームを作成しました +label_network_created=ネットワークを作成しました + +label_registry=レジストリ認証情報 +label_registry_server=レジストリサーバー +label_registry_user=ユーザー名 +label_registry_pass=パスワード +label_save_login=保存してログイン +label_registry_saved=認証情報を保存しました + +label_scan=セキュリティスキャン + +label_context=コンテキスト +label_current_context=現在のコンテキスト +label_available_contexts=利用可能なコンテキスト +label_switch_context=コンテキストを切り替え +label_context_set=コンテキストを更新 + +label_create=作成 +label_clone=コンテナを複製 +label_source=元コンテナ +label_created=コンテナを作成しました +label_cloned=コンテナを複製しました diff --git a/docker/lang/pl b/docker/lang/pl index 54fb295..c22edf4 100644 --- a/docker/lang/pl +++ b/docker/lang/pl @@ -24,3 +24,82 @@ label_stop=Stop label_restart=Uruchom ponownie label_refresh=Odświerz label_viewlog=Zobacz dziennik + +tab_images=Images +tab_compose=Compose +tab_storage=Storage +tab_registry=Registry +tab_security=Security +tab_contexts=Contexts + +label_exec=Exec +label_download_log=Download log +label_command=Command +label_run=Run +label_copy=Copy +label_copy_ok=Copy complete +label_copy_to=Copy from host to container +label_copy_from=Copy from container to host +label_host_path=Host path +label_target_path=Container target path +label_source_path=Container source path +label_host_dest=Host destination + +label_image=Image +label_size=Size +label_digest=Digest +label_actions=Actions +label_pull=Pull image +label_push=Push image +label_tag=Tag image +label_from=From +label_to=To +label_build_tag=Build image +label_dockerfile=Dockerfile contents +label_prune_images=Prune dangling images +label_pruned=Images pruned +label_built=Image built +label_tagged=Image tagged +label_pulled=Image pulled +label_pushed=Image pushed +label_checked=Image checked +label_image_actions=Image actions + +label_compose=Compose file +label_compose_file=Compose file path +label_compose_cmd=Compose action +label_run_compose=Run compose +label_compose_ran=Compose command ran + +label_volumes=Volumes +label_networks=Networks +label_volume_create=Create volume +label_network_create=Create network +label_mount=Mount +label_scope=Scope +label_driver=Driver +label_prune_volumes=Prune unused volumes +label_prune_networks=Prune unused networks +label_volume_created=Volume created +label_network_created=Network created + +label_registry=Registry credentials +label_registry_server=Registry server +label_registry_user=Username +label_registry_pass=Password +label_save_login=Save and login +label_registry_saved=Registry credentials saved + +label_scan=Security scan + +label_context=Context +label_current_context=Current context +label_available_contexts=Available contexts +label_switch_context=Switch context +label_context_set=Context updated + +label_create=Create +label_clone=Clone container +label_source=Source container +label_created=Container created +label_cloned=Container cloned diff --git a/docker/lang/pt b/docker/lang/pt new file mode 100644 index 0000000..26cc6cd --- /dev/null +++ b/docker/lang/pt @@ -0,0 +1,105 @@ +not_installed=O Docker não foi encontrado neste sistema + +index_title=Gestão de contêineres Docker +index_return=lista de contêineres + +view_container_title=Contêiner $1 + +tab_info=Informações do Docker +tab_containers=Contêineres +tab_log=Log +tab_inspect=Inspecionar + +command_err=Não foi possível executar o comando + +label_name=Nome +label_label=Rótulo +label_running=Em execução +label_runningfor=Em execução há +label_cpu=CPU +label_mem=MEM + +label_start=Iniciar +label_stop=Parar +label_restart=Reiniciar +label_refresh=Atualizar +label_viewlog=Ver log + +tab_images=Imagens +tab_compose=Compose +tab_storage=Armazenamento +tab_registry=Registro +tab_security=Segurança +tab_contexts=Contextos + +label_exec=Executar +label_download_log=Baixar log +label_command=Comando +label_run=Executar +label_copy=Copiar +label_copy_ok=Cópia concluída +label_copy_to=Copiar do host para o contêiner +label_copy_from=Copiar do contêiner para o host +label_host_path=Caminho do host +label_target_path=Caminho alvo no contêiner +label_source_path=Caminho de origem no contêiner +label_host_dest=Destino no host + +label_image=Imagem +label_size=Tamanho +label_digest=Digest +label_actions=Ações +label_pull=Baixar imagem +label_push=Enviar imagem +label_tag=Marcar imagem +label_from=De +label_to=Para +label_build_tag=Construir imagem +label_dockerfile=Conteúdo do Dockerfile +label_prune_images=Remover imagens não usadas +label_pruned=Imagens removidas +label_built=Imagem construída +label_tagged=Imagem marcada +label_pulled=Imagem baixada +label_pushed=Imagem enviada +label_checked=Atualização verificada +label_image_actions=Ações da imagem + +label_compose=Arquivo Compose +label_compose_file=Caminho do arquivo Compose +label_compose_cmd=Ação do Compose +label_run_compose=Executar Compose +label_compose_ran=Comando Compose executado + +label_volumes=Volumes +label_networks=Redes +label_volume_create=Criar volume +label_network_create=Criar rede +label_mount=Ponto de montagem +label_scope=Escopo +label_driver=Driver +label_prune_volumes=Remover volumes não usados +label_prune_networks=Remover redes não usadas +label_volume_created=Volume criado +label_network_created=Rede criada + +label_registry=Credenciais do registro +label_registry_server=Servidor do registro +label_registry_user=Usuário +label_registry_pass=Senha +label_save_login=Salvar e entrar +label_registry_saved=Credenciais salvas + +label_scan=Varredura de segurança + +label_context=Contexto +label_current_context=Contexto atual +label_available_contexts=Contextos disponíveis +label_switch_context=Alterar contexto +label_context_set=Contexto atualizado + +label_create=Criar +label_clone=Clonar contêiner +label_source=Contêiner de origem +label_created=Contêiner criado +label_cloned=Contêiner clonado diff --git a/docker/module.info.ar b/docker/module.info.ar new file mode 100644 index 0000000..5a54ea8 --- /dev/null +++ b/docker/module.info.ar @@ -0,0 +1,4 @@ +name=حاويات Docker +category=servers +desc=عرض حاويات Docker +longdesc=عرض وتنفيذ إجراءات أساسية على حاويات Docker في المضيف diff --git a/docker/module.info.cn b/docker/module.info.cn new file mode 100644 index 0000000..866af2f --- /dev/null +++ b/docker/module.info.cn @@ -0,0 +1,4 @@ +name=Docker 容器 +category=servers +desc=查看 Docker 容器 +longdesc=查看并对主机上的 Docker 容器执行基本操作 diff --git a/docker/module.info.es b/docker/module.info.es new file mode 100644 index 0000000..923c608 --- /dev/null +++ b/docker/module.info.es @@ -0,0 +1,4 @@ +name=Contenedores Docker +category=servers +desc=Ver contenedores Docker +longdesc=Ver y realizar acciones básicas en los contenedores Docker del host diff --git a/docker/module.info.fr b/docker/module.info.fr new file mode 100644 index 0000000..6bf6179 --- /dev/null +++ b/docker/module.info.fr @@ -0,0 +1,4 @@ +name=Conteneurs Docker +category=servers +desc=Voir les conteneurs Docker +longdesc=Afficher et effectuer des actions de base sur les conteneurs Docker de l'hôte diff --git a/docker/module.info.ja b/docker/module.info.ja new file mode 100644 index 0000000..4070a54 --- /dev/null +++ b/docker/module.info.ja @@ -0,0 +1,4 @@ +name=Docker コンテナ +category=servers +desc=Docker コンテナの表示 +longdesc=ホスト上の Docker コンテナを表示し基本操作を行います diff --git a/docker/module.info.pt b/docker/module.info.pt new file mode 100644 index 0000000..04df536 --- /dev/null +++ b/docker/module.info.pt @@ -0,0 +1,4 @@ +name=Contêineres Docker +category=servers +desc=Ver contêineres Docker +longdesc=Visualizar e executar ações básicas nos contêineres Docker do host