Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ Experimental rootless docker support has been added via [docker contexts](https:

<img width="480" alt="Screenshot showing monitor support options" src="https://github.com/user-attachments/assets/b50d10ae-de5b-4329-8d49-b13e6efae09c">

## 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.
Expand Down
Binary file added docker.wbm.gz
Binary file not shown.
161 changes: 154 additions & 7 deletions docker/command.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -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 "<pre class='comment'>" . html_escape($result) . "</pre>";
}
&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 "<pre class='comment'>" . html_escape($result) . "</pre>";
}
&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 "<pre class='comment'>" . html_escape($result) . "</pre>";
}
&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("");
if ($return_tab) {
finish_redirect("", "");
} else {
&redirect("");
}
77 changes: 73 additions & 4 deletions docker/container.cgi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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");
Expand All @@ -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(),"<br>\n";

print "<p><a href='container.cgi?tab=log&download=1&container=" . urlize($container) . "'>" . text('label_download_log') . "</a></p>";

print '
<script type="text/javascript">
Expand Down Expand Up @@ -85,7 +95,7 @@ print '
}
</script>';

print "<pre id='container-log'>" . $result . "</pre>";
print "<pre id='container-log'>" . $log_result . "</pre>";

print ui_tabs_end_tab('mode', 'log');

Expand All @@ -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 "<pre class='comment'>" . html_escape($exec_result) . "</pre>";
}
}

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'));
Expand Down
Loading