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:
+## 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 0000000..b1fb51c
Binary files /dev/null and b/docker.wbm.gz differ
diff --git a/docker/command.cgi b/docker/command.cgi
index b8397be..e995a36 100644
--- a/docker/command.cgi
+++ b/docker/command.cgi
@@ -7,23 +7,170 @@ require './docker-lib.pl';
&ReadParse();
&error_setup(text('command_err'));
-our (%in);
+our (%in, %config);
my $command = $in{'c'};
+my $return_tab = $in{'return_tab'} || "";
my $err;
+
+sub finish_redirect {
+ my ($msg, $err) = @_;
+ my @params;
+ push @params, "tab=$return_tab" if $return_tab;
+ push @params, "msg=" . &urlize($msg) if $msg;
+ push @params, "err=" . &urlize($err) if $err;
+ my $dest = "index.cgi";
+ if (scalar(@params) > 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 "
";
+ }
+ &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 "";
+ }
+ &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 "";
+ }
+ &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 "";
+ }
+}
+
+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'}) . "",
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'}),
"CPU: " . 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