Files
peardock/index.html
T
Raven Scott 7b5da2da86
Release rolling / release (push) Successful in 7m32s
Add ttl.sh as ephemeral anonymous push target
Expose ttl.sh in Flatten and Push Image target lists with TTL presets,
auto-retag to ttl.sh/name:duration, and skip vault auth for public temp pushes.
2026-07-18 07:49:33 -04:00

5915 lines
332 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PearDock</title>
<meta name="description" content="Decentralized Docker management on the Holepunch stack">
<meta name="theme-color" content="#0a0c10">
<meta name="msapplication-TileColor" content="#0f1117">
<meta name="msapplication-config" content="assets/favicons/browserconfig.xml">
<link rel="icon" href="assets/favicons/favicon.ico" sizes="any">
<link rel="icon" type="image/png" sizes="16x16" href="assets/favicons/favicon-16x16.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/favicons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="48x48" href="assets/favicons/favicon-48x48.png">
<link rel="apple-touch-icon" href="assets/favicons/apple-touch-icon.png">
<link rel="manifest" href="assets/favicons/site.webmanifest">
<!-- Apply color theme before paint to avoid dark→light flash -->
<script>
(function () {
try {
var raw = JSON.parse(localStorage.getItem('peardock.settings.v1') || '{}');
var pref = String(raw.theme || 'dark').toLowerCase();
if (pref !== 'light' && pref !== 'system') pref = 'dark';
var resolved = pref;
if (pref === 'system') {
resolved =
window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches
? 'light'
: 'dark';
}
document.documentElement.setAttribute('data-theme', resolved === 'light' ? 'light' : 'dark');
document.documentElement.setAttribute('data-theme-pref', pref);
if (resolved === 'light') {
var meta = document.querySelector('meta[name="theme-color"]');
if (meta) meta.setAttribute('content', '#f3f5f8');
var tile = document.querySelector('meta[name="msapplication-TileColor"]');
if (tile) tile.setAttribute('content', '#f3f5f8');
}
} catch (e) {
document.documentElement.setAttribute('data-theme', 'dark');
document.documentElement.setAttribute('data-theme-pref', 'dark');
}
})();
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<link rel="stylesheet" href="ui/styles.css">
<link rel="stylesheet" href="ui/modern.css">
<link rel="stylesheet" href="ui/ops.css">
<link rel="stylesheet" href="ui/theme-light.css">
</head>
<body>
<div id="titlebar">
<div class="titlebar-left">
<pear-ctrl></pear-ctrl>
<div class="app-brand">
<div class="app-brand-mark" aria-hidden="true">
<img src="assets/logo/peardock-icon-64.png" alt="" width="22" height="22">
</div>
<div class="app-brand-text">peardock <span>P2P Docker</span></div>
</div>
</div>
<div class="titlebar-right">
<div id="active-peer-chip" title="Active peer">
<span class="dot" id="active-peer-dot"></span>
<span id="active-peer-label">No peer</span>
</div>
<div id="connection-health-badge" class="connection-health-badge" title="Connection health">
<span class="health-dot health-dot--unknown" id="health-dot"></span>
<span id="health-latency" class="health-latency"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></span>
<span id="health-docker" class="health-docker">Docker <span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></span>
<span id="health-role" class="health-role"></span>
</div>
<!-- Recent activity (job history) -->
<div id="activity-tray" class="activity-tray">
<button type="button" id="activity-tray-toggle" class="notification-tray-toggle" title="Recent activity" aria-label="Recent activity" aria-expanded="false">
<i class="fas fa-list-check"></i>
</button>
<div id="activity-panel" class="activity-panel hidden" role="dialog" aria-label="Recent activity">
<div class="activity-panel-header">
<h5 class="mb-0">Recent activity</h5>
<button type="button" class="notification-action-btn" id="activity-panel-close" title="Close" aria-label="Close">
<i class="fas fa-times"></i>
</button>
</div>
<div id="activity-panel-list" class="activity-panel-list">
<div class="text-muted small p-3">No jobs yet</div>
</div>
</div>
</div>
<!-- Notification Tray - Global across all views -->
<div id="notification-tray" class="notification-tray">
<button id="notification-tray-toggle" class="notification-tray-toggle" aria-label="Notifications">
<i class="fas fa-bell"></i>
<span id="notification-badge" class="notification-badge">0</span>
</button>
<div id="notification-panel" class="notification-panel hidden">
<div class="notification-panel-header">
<h5 class="notification-panel-title">Notifications</h5>
<div class="notification-panel-actions">
<button id="mark-all-read-btn" class="notification-action-btn" title="Mark all as read">
<i class="fas fa-check-double"></i>
</button>
<button id="clear-all-btn" class="notification-action-btn" title="Clear all">
<i class="fas fa-trash"></i>
</button>
<button id="close-panel-btn" class="notification-action-btn" title="Close">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="notification-filters">
<button class="notification-filter-btn active" data-filter="all">All</button>
<button class="notification-filter-btn" data-filter="success">Success</button>
<button class="notification-filter-btn" data-filter="danger">Error</button>
<button class="notification-filter-btn" data-filter="warning">Warning</button>
<button class="notification-filter-btn" data-filter="info">Info</button>
</div>
<div id="notification-list" class="notification-list">
<div class="notification-empty">
<i class="fas fa-bell-slash"></i>
<p>No notifications</p>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="sidebar">
<button id="collapse-sidebar-btn" title="Collapse sidebar" aria-label="Collapse sidebar">
<i class="fas fa-chevron-left"></i>
</button>
<div class="content">
<div class="sidebar-nav-scroll">
<div class="sidebar-section-label">Navigate <kbd class="text-muted" style="font-size:0.65rem">Ctrl+K</kbd></div>
<nav id="main-nav">
<ul class="nav-menu">
<li class="nav-group-label">Overview</li>
<li class="nav-item">
<a href="#" class="nav-link active" data-view="dashboard" title="Dashboard">
<i class="fas fa-gauge-high"></i>
<span class="nav-label">Dashboard</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="fleet" title="Fleet">
<i class="fas fa-server"></i>
<span class="nav-label">Fleet</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="events" title="Events">
<i class="fas fa-bolt"></i>
<span class="nav-label">Events</span>
</a>
</li>
<li class="nav-group-label">Workloads</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="deploy" title="Deploy">
<i class="fas fa-rocket"></i>
<span class="nav-label">Deploy</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="containers" title="Containers">
<i class="fas fa-cube"></i>
<span class="nav-label">Containers</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="stacks" title="Stacks">
<i class="fas fa-boxes-stacked"></i>
<span class="nav-label">Stacks</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="swarm" id="nav-swarm-link" title="Swarm" data-swarm-nav="1">
<i class="fas fa-project-diagram"></i>
<span class="nav-label">Swarm</span>
</a>
</li>
<li class="nav-group-label">Build & storage</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="images" title="Images">
<i class="fas fa-layer-group"></i>
<span class="nav-label">Images</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="registry" title="Registry">
<i class="fas fa-warehouse"></i>
<span class="nav-label">Registry</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="volumes" title="Volumes">
<i class="fas fa-hard-drive"></i>
<span class="nav-label">Volumes</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="networks" title="Networks">
<i class="fas fa-diagram-project"></i>
<span class="nav-label">Networks</span>
</a>
</li>
<li class="nav-group-label">Host & access</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="host" title="Host">
<i class="fas fa-microchip"></i>
<span class="nav-label">Host</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="tunnels" title="Tunnels">
<i class="fas fa-satellite-dish"></i>
<span class="nav-label">Tunnels</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="access" title="Access">
<i class="fas fa-user-shield"></i>
<span class="nav-label">Access</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link" data-view="settings" title="Settings">
<i class="fas fa-gear"></i>
<span class="nav-label">Settings</span>
</a>
</li>
</ul>
</nav>
</div>
<!-- Fixed footer: quick-add peer (full list under Settings → Peers) -->
<div class="sidebar-footer">
<hr class="sidebar-divider">
<div class="connections-panel connections-panel--compact">
<button type="button" id="open-add-connection-btn" class="btn btn-sm btn-primary open-add-connection-btn w-100" title="Add peer" aria-label="Add peer">
<i class="fas fa-plus"></i>
<span class="open-add-connection-label">Add peer</span>
</button>
</div>
</div>
</div>
</div>
<div id="content">
<div id="welcome-page">
<h1>Welcome to peardock</h1>
<p class="welcome-lead">Peer-to-peer Docker control plane over HyperDHT and protomux-rpc. No central server. No cloud account.</p>
<div class="welcome-steps">
<div class="welcome-step">
<div class="welcome-step-num">1</div>
<div>
<strong>Start the server</strong>
<span>On a host with Docker: <span class="welcome-mono">npm run server</span></span>
</div>
</div>
<div class="welcome-step">
<div class="welcome-step-num">2</div>
<div>
<strong>Copy the public key</strong>
<span>Printed at startup — 64 hex characters. Public key alone is <strong>read-only</strong>. Never share the seed except for admin on your own devices.</span>
</div>
</div>
<div class="welcome-step">
<div class="welcome-step-num">3</div>
<div>
<strong>Connect from here</strong>
<span>Paste a public key (viewer), public key + SERVER_SEED (admin), or a pd1. invite (operator/admin without the seed).</span>
</div>
</div>
</div>
<button type="button" id="welcome-add-peer-btn" class="btn btn-primary btn-lg mt-4">
<i class="fas fa-plug me-2"></i>Add peer
</button>
</div>
<!-- Boot: re-dialing saved peers (not the welcome empty-state) -->
<div id="restoring-page" class="hidden" hidden aria-live="polite" aria-busy="true">
<div class="restoring-card">
<div class="restoring-orb" aria-hidden="true">
<span class="restoring-orb-glow"></span>
<span class="job-spinner job-spinner--xl restoring-spinner" role="status" aria-label="Restoring connections">
<span class="job-spinner-ring"></span>
</span>
</div>
<p class="restoring-kicker">
<img src="assets/logo/peardock-icon-32.png" alt="" width="16" height="16" class="restoring-kicker-icon">
PearDock
</p>
<h1 class="restoring-title">Restoring connections</h1>
<p class="restoring-lead">Reconnecting to your saved servers over HyperDHT…</p>
<div class="restoring-status-pill">
<span class="restoring-status-dot" aria-hidden="true"></span>
<p id="restoring-status" class="restoring-status-text">Starting…</p>
</div>
<div class="restoring-progress" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" id="restoring-progress" aria-label="Connection restore progress">
<div class="restoring-progress-track">
<div class="restoring-progress-bar" id="restoring-progress-bar"></div>
</div>
<div class="restoring-progress-meta">
<span id="restoring-progress-count">0 of 0</span>
<span class="restoring-progress-hint">peers</span>
</div>
</div>
</div>
</div>
<!-- First-connect checklist (shown once after successful connect) -->
<div id="first-connect-checklist" class="first-connect-checklist hidden" role="dialog" aria-labelledby="first-connect-title">
<div class="first-connect-card">
<div class="first-connect-head">
<h3 id="first-connect-title"><i class="fas fa-circle-check me-2 text-success"></i>You're connected</h3>
<button type="button" class="btn-close btn-close-white" id="first-connect-dismiss" aria-label="Dismiss"></button>
</div>
<p class="text-muted small mb-3">A short tour of what to do next:</p>
<ol class="first-connect-steps">
<li><button type="button" class="first-connect-link" data-view="dashboard">Open the dashboard</button> for live host health</li>
<li><button type="button" class="first-connect-link" data-view="containers">Browse containers</button> or <button type="button" class="first-connect-link" data-view="deploy">deploy a template</button></li>
<li><button type="button" class="first-connect-link" data-view="settings" data-settings-tab="peers">Peers</button> in Settings to manage saved hosts · <button type="button" class="first-connect-link" data-view="settings">Settings</button> for prefs</li>
<li>Press <kbd>Ctrl</kbd>+<kbd>K</kbd> anytime for the command palette</li>
</ol>
<button type="button" class="btn btn-primary btn-sm" id="first-connect-got-it">Got it</button>
</div>
</div>
<!-- Dashboard View — modern fit-to-viewport shell -->
<div id="dashboard-view" class="view hidden">
<div class="dashboard-layout dash">
<header class="dash-header">
<div class="dash-header-text">
<p class="dash-kicker">Overview</p>
<h2 class="dash-title">Dashboard</h2>
<p class="dash-subtitle">Live engine health, storage, and activity on the active peer</p>
</div>
<div class="dash-header-actions">
<button type="button" class="dash-icon-btn" onclick="window.loadDashboard && window.loadDashboard()" title="Refresh dashboard">
<i class="fas fa-rotate"></i>
</button>
<button type="button" class="dash-chip-btn" onclick="window.navigateToView('deploy')">
<i class="fas fa-rocket"></i><span>Deploy</span>
</button>
</div>
</header>
<!-- KPI strip — clickable shortcuts into resource views -->
<section class="dash-kpis" aria-label="Workload summary">
<button type="button" class="dash-kpi dash-kpi--ok dash-kpi--link" data-dash-nav="containers" data-container-status="running" title="View running containers" aria-label="View running containers">
<div class="dash-kpi-icon"><i class="fas fa-play"></i></div>
<div class="dash-kpi-meta">
<span class="dash-kpi-label">Running</span>
<span class="dash-kpi-value" id="stat-running-containers">0</span>
</div>
<div class="dash-kpi-glow" aria-hidden="true"></div>
</button>
<button type="button" class="dash-kpi dash-kpi--stop dash-kpi--link" data-dash-nav="containers" data-container-status="exited" title="View stopped containers" aria-label="View stopped containers">
<div class="dash-kpi-icon"><i class="fas fa-stop"></i></div>
<div class="dash-kpi-meta">
<span class="dash-kpi-label">Stopped</span>
<span class="dash-kpi-value" id="stat-stopped-containers">0</span>
</div>
<div class="dash-kpi-glow" aria-hidden="true"></div>
</button>
<button type="button" class="dash-kpi dash-kpi--info dash-kpi--link" data-dash-nav="images" title="View images" aria-label="View images">
<div class="dash-kpi-icon"><i class="fas fa-layer-group"></i></div>
<div class="dash-kpi-meta">
<span class="dash-kpi-label">Images</span>
<span class="dash-kpi-value" id="stat-total-images">0</span>
</div>
<div class="dash-kpi-glow" aria-hidden="true"></div>
</button>
<button type="button" class="dash-kpi dash-kpi--teal dash-kpi--link" data-dash-nav="networks" title="View networks" aria-label="View networks">
<div class="dash-kpi-icon"><i class="fas fa-diagram-project"></i></div>
<div class="dash-kpi-meta">
<span class="dash-kpi-label">Networks</span>
<span class="dash-kpi-value" id="stat-total-networks">0</span>
</div>
<div class="dash-kpi-glow" aria-hidden="true"></div>
</button>
</section>
<!-- Main panels -->
<section class="dash-panels">
<article class="dash-card">
<div class="dash-card-head">
<div class="dash-card-title"><i class="fas fa-gears"></i><span>Engine</span></div>
</div>
<div id="docker-info-content" class="dash-card-body">
<div class="dash-skeleton">
<div class="dash-skel-line"></div>
<div class="dash-skel-line"></div>
<div class="dash-skel-line short"></div>
</div>
</div>
</article>
<article class="dash-card">
<div class="dash-card-head">
<div class="dash-card-title"><i class="fas fa-microchip"></i><span>Host</span></div>
</div>
<div id="system-resources-content" class="dash-card-body">
<div class="dash-skeleton">
<div class="dash-skel-line"></div>
<div class="dash-skel-line"></div>
<div class="dash-skel-line short"></div>
</div>
</div>
</article>
<article class="dash-card">
<div class="dash-card-head">
<div class="dash-card-title"><i class="fas fa-hard-drive"></i><span>Disk</span></div>
<button type="button" class="dash-icon-btn dash-icon-btn--sm" onclick="window.refreshSystemDf && window.refreshSystemDf()" title="Refresh disk usage">
<i class="fas fa-rotate"></i>
</button>
</div>
<div id="system-df-content" class="dash-card-body">
<div class="dash-empty">Connect a peer to load disk usage</div>
</div>
</article>
<article class="dash-card dash-card--events">
<div class="dash-card-head">
<div class="dash-card-title"><i class="fas fa-bolt"></i><span>Live events</span></div>
<button type="button" class="dash-icon-btn dash-icon-btn--sm" onclick="window.clearEventTimeline && window.clearEventTimeline()" title="Clear events">
<i class="fas fa-eraser"></i>
</button>
</div>
<div id="docker-events-timeline" class="dash-card-body events-timeline dash-events">
<div class="dash-empty">Docker events will stream here…</div>
</div>
</article>
</section>
<!-- Quick actions dock -->
<footer class="dash-actions">
<div class="dash-actions-label"><i class="fas fa-bolt"></i> Quick actions</div>
<div class="dash-actions-row">
<button type="button" class="dash-action" onclick="window.navigateToView('containers')">
<i class="fas fa-cube"></i><span>Containers</span>
</button>
<button type="button" class="dash-action" onclick="window.navigateToView('images')">
<i class="fas fa-layer-group"></i><span>Images</span>
</button>
<button type="button" class="dash-action" onclick="window.navigateToView('networks')">
<i class="fas fa-diagram-project"></i><span>Networks</span>
</button>
<button type="button" class="dash-action" onclick="window.navigateToView('volumes')">
<i class="fas fa-hard-drive"></i><span>Volumes</span>
</button>
<button type="button" class="dash-action dash-action--warn" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('containers')" title="Prune stopped containers">
<i class="fas fa-broom"></i><span>Prune containers</span>
</button>
<button type="button" class="dash-action dash-action--warn" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('images')" title="Prune unused images">
<i class="fas fa-broom"></i><span>Prune images</span>
</button>
<button type="button" class="dash-action dash-action--warn" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('builder')" title="Prune build cache">
<i class="fas fa-hammer"></i><span>Prune builder</span>
</button>
<button type="button" class="dash-action dash-action--danger" data-min-role="admin" onclick="window.systemPrune && window.systemPrune()" title="Docker system prune (unused data)">
<i class="fas fa-trash-can"></i><span>System prune</span>
</button>
</div>
</footer>
</div>
</div>
<!-- Deploy View -->
<div id="deploy-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-rocket"></i>Deploy</h2>
<p class="page-subtitle">Templates, compose stacks, and custom images</p>
</div>
</div>
<!-- Template Selection Section -->
<div class="info-card mb-4">
<h5 class="info-card-title"><i class="fas fa-cubes me-2"></i>Select Template</h5>
<div class="info-card-body" style="display: flex; flex-direction: column; height: calc(100vh - 250px);">
<div class="d-flex align-items-center gap-2 mb-2">
<input type="search" id="deploy-template-search-input" class="form-control bg-dark text-white" placeholder="Search templates (debounced)…" autocomplete="off" spellcheck="false">
</div>
<div class="small text-muted mb-2 deploy-template-count d-flex align-items-center gap-2" id="deploy-template-count">
<span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span>
<span>Loading templates…</span>
</div>
<div id="deploy-template-list-container" style="flex: 1; overflow-y: auto; overflow-x: hidden; min-height: 0;">
<div class="text-center text-muted py-4 d-flex align-items-center justify-content-center gap-2">
<span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span>
<span>Loading templates…</span>
</div>
</div>
</div>
</div>
<!-- Deploy Form Section -->
<div class="info-card" id="deploy-form-section" style="display: none;">
<h5 class="info-card-title"><i class="fas fa-cog me-2"></i>Container Configuration</h5>
<div class="info-card-body">
<form id="deploy-view-form" onsubmit="return false;">
<!-- Basic Settings (Always Visible) -->
<div class="mb-4">
<h6 class="text-primary mb-3"><i class="fas fa-cog"></i> Basic Settings</h6>
<div class="row">
<div class="col-md-6 mb-3">
<label for="deploy-container-name" class="form-label">Container Name <span class="text-danger">*</span></label>
<input type="text" id="deploy-container-name" class="form-control bg-dark text-white" placeholder="my-container" required>
<small class="text-muted">Alphanumeric, dashes, and underscores only</small>
</div>
<div class="col-md-6 mb-3">
<label for="deploy-image" class="form-label">Image <span class="text-danger">*</span></label>
<input type="text" id="deploy-image" class="form-control bg-dark text-white" placeholder="nginx:latest" required>
<small class="text-muted">Docker image name and tag</small>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="deploy-command" class="form-label">Command Override</label>
<input type="text" id="deploy-command" class="form-control bg-dark text-white" placeholder="Override default command">
<small class="text-muted">Override container CMD</small>
</div>
<div class="col-md-4 mb-3">
<label for="deploy-entrypoint" class="form-label">Entrypoint Override</label>
<input type="text" id="deploy-entrypoint" class="form-control bg-dark text-white" placeholder="Override default entrypoint">
<small class="text-muted">Override container ENTRYPOINT</small>
</div>
<div class="col-md-4 mb-3">
<label for="deploy-workdir" class="form-label">Working Directory</label>
<input type="text" id="deploy-workdir" class="form-control bg-dark text-white" placeholder="/app">
<small class="text-muted">Container working directory</small>
</div>
</div>
</div>
<!-- Accordion for Advanced Settings - Reuse same structure as modal -->
<div class="accordion accordion-flush" id="deployAccordion">
<!-- Networking -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#networkingCollapse">
<i class="fas fa-network-wired me-2"></i> Networking
</button>
</h2>
<div id="networkingCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-network-mode" class="form-label">Network Mode</label>
<select id="deploy-network-mode" class="form-select bg-dark text-white">
<option value="bridge">Bridge</option>
<option value="host">Host</option>
<option value="none">None</option>
<option value="container">Container</option>
</select>
</div>
<div class="col-md-6" id="deploy-custom-network-container" style="display: none;">
<label for="deploy-custom-network" class="form-label">Custom Network</label>
<input type="text" id="deploy-custom-network" class="form-control bg-dark text-white" placeholder="network-name">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-hostname" class="form-label">Hostname</label>
<input type="text" id="deploy-hostname" class="form-control bg-dark text-white" placeholder="container-hostname">
</div>
<div class="col-md-6">
<label for="deploy-domainname" class="form-label">Domain Name</label>
<input type="text" id="deploy-domainname" class="form-control bg-dark text-white" placeholder="example.com">
</div>
</div>
<div class="mb-3">
<label class="form-label">Port Mappings</label>
<div id="deploy-ports-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addPortMapping()">
<i class="fas fa-plus"></i> Add Port
</button>
<small class="text-muted d-block mt-1">Format: host:container/protocol (e.g., 8080:80/tcp)</small>
</div>
<div class="mb-3">
<label class="form-label">DNS Servers</label>
<div id="deploy-dns-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDnsServer()">
<i class="fas fa-plus"></i> Add DNS Server
</button>
</div>
<div class="mb-3">
<label class="form-label">Extra Hosts</label>
<div id="deploy-extra-hosts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addExtraHost()">
<i class="fas fa-plus"></i> Add Host
</button>
<small class="text-muted d-block mt-1">Format: hostname:ip (e.g., example.com:127.0.0.1)</small>
</div>
</div>
</div>
</div>
<!-- Volumes & Storage -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#volumesCollapse">
<i class="fas fa-hdd me-2"></i> Volumes & Storage
</button>
</h2>
<div id="volumesCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Volume Mounts</label>
<div id="deploy-volumes-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addVolumeMount()">
<i class="fas fa-plus"></i> Add Volume
</button>
<small class="text-muted d-block mt-1">Format: /host/path:/container/path:mode (mode: ro/rw)</small>
</div>
<div class="mb-3">
<label class="form-label">Tmpfs Mounts</label>
<div id="deploy-tmpfs-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addTmpfsMount()">
<i class="fas fa-plus"></i> Add Tmpfs
</button>
<small class="text-muted d-block mt-1">Format: /path:size (e.g., /tmp:100m)</small>
</div>
</div>
</div>
</div>
<!-- Resources -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#resourcesCollapse">
<i class="fas fa-server me-2"></i> Resources
</button>
</h2>
<div id="resourcesCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-cpu-limit" class="form-label">CPU Limit: <strong id="deploy-cpu-limit-val">Unlimited</strong></label>
<input type="range" class="form-range" id="deploy-cpu-limit" min="0" max="16" step="0.1" value="0">
<small class="text-muted">0 = unlimited · cores</small>
</div>
<div class="col-md-6">
<label for="deploy-cpu-reservation" class="form-label">CPU Reservation: <strong id="deploy-cpu-reservation-val">None</strong></label>
<input type="range" class="form-range" id="deploy-cpu-reservation" min="0" max="16" step="0.1" value="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="deploy-cpu-shares" class="form-label">CPU Shares</label>
<input type="number" id="deploy-cpu-shares" class="form-control bg-dark text-white" placeholder="1024" min="0">
</div>
<div class="col-md-4">
<label for="deploy-memory-limit" class="form-label">Memory Limit: <strong id="deploy-memory-limit-val">Unlimited</strong></label>
<input type="range" class="form-range" id="deploy-memory-limit" min="0" max="16384" step="64" value="0">
<small class="text-muted">0 = unlimited · MB</small>
</div>
<div class="col-md-4">
<label for="deploy-memory-reservation" class="form-label">Memory Reservation: <strong id="deploy-memory-reservation-val">None</strong></label>
<input type="range" class="form-range" id="deploy-memory-reservation" min="0" max="16384" step="64" value="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="deploy-memory-swap" class="form-label">Memory Swap (MB)</label>
<input type="number" id="deploy-memory-swap" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label class="form-label">Device Mappings</label>
<div id="deploy-devices-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDeviceMapping()">
<i class="fas fa-plus"></i> Add Device
</button>
<small class="text-muted d-block mt-1">Format: /host/device:/container/device:permissions</small>
</div>
</div>
</div>
</div>
<!-- Environment & Labels -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#envLabelsCollapse">
<i class="fas fa-tags me-2"></i> Environment & Labels
</button>
</h2>
<div id="envLabelsCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Environment Variables</label>
<div id="deploy-env"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addEnvVar()">
<i class="fas fa-plus"></i> Add Variable
</button>
</div>
<div class="mb-3">
<label class="form-label">Labels</label>
<div id="deploy-labels-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addLabel()">
<i class="fas fa-plus"></i> Add Label
</button>
<small class="text-muted d-block mt-1">Format: key=value</small>
</div>
</div>
</div>
</div>
<!-- Security -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#securityCollapse">
<i class="fas fa-shield-alt me-2"></i> Security
</button>
</h2>
<div id="securityCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-user" class="form-label">User/UID</label>
<input type="text" id="deploy-user" class="form-control bg-dark text-white" placeholder="1000 or username">
</div>
<div class="col-md-6">
<label for="deploy-group" class="form-label">Group/GID</label>
<input type="text" id="deploy-group" class="form-control bg-dark text-white" placeholder="1000 or groupname">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-privileged">
<label class="form-check-label" for="deploy-privileged">Privileged Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-readonly-rootfs">
<label class="form-check-label" for="deploy-readonly-rootfs">Read-only Root Filesystem</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Capabilities</label>
<div id="deploy-capabilities-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addCapability()">
<i class="fas fa-plus"></i> Add Capability
</button>
<small class="text-muted d-block mt-1">e.g., NET_ADMIN, SYS_ADMIN</small>
</div>
<div class="mb-3">
<label class="form-label">Security Options</label>
<div id="deploy-security-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addSecurityOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
<small class="text-muted d-block mt-1">e.g., apparmor=profile, seccomp=unconfined</small>
</div>
</div>
</div>
</div>
<!-- Runtime & Behavior -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#runtimeCollapse">
<i class="fas fa-play-circle me-2"></i> Runtime & Behavior
</button>
</h2>
<div id="runtimeCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-restart-policy" class="form-label">Restart Policy</label>
<select id="deploy-restart-policy" class="form-select bg-dark text-white">
<option value="no">No</option>
<option value="always">Always</option>
<option value="on-failure">On Failure</option>
<option value="unless-stopped">Unless Stopped</option>
</select>
</div>
<div class="col-md-6">
<label for="deploy-restart-max-retries" class="form-label">Max Retries (on-failure)</label>
<input type="number" id="deploy-restart-max-retries" class="form-control bg-dark text-white" placeholder="10" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-auto-remove">
<label class="form-check-label" for="deploy-auto-remove">Auto Remove</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-tty">
<label class="form-check-label" for="deploy-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-stdin-open">
<label class="form-check-label" for="deploy-stdin-open">Interactive (Stdin Open)</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-detach" checked>
<label class="form-check-label" for="deploy-detach">Detach Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-init">
<label class="form-check-label" for="deploy-init">Init Process</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Health & Logging -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#healthCollapse">
<i class="fas fa-heartbeat me-2"></i> Health & Logging
</button>
</h2>
<div id="healthCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="deploy-health-cmd" class="form-label">Health Check Command</label>
<input type="text" id="deploy-health-cmd" class="form-control bg-dark text-white" placeholder="CMD-SHELL curl -f http://localhost/ || exit 1">
</div>
<div class="row mb-3">
<div class="col-md-3">
<label for="deploy-health-interval" class="form-label">Interval (s)</label>
<input type="number" id="deploy-health-interval" class="form-control bg-dark text-white" placeholder="30" min="1">
</div>
<div class="col-md-3">
<label for="deploy-health-timeout" class="form-label">Timeout (s)</label>
<input type="number" id="deploy-health-timeout" class="form-control bg-dark text-white" placeholder="10" min="1">
</div>
<div class="col-md-3">
<label for="deploy-health-retries" class="form-label">Retries</label>
<input type="number" id="deploy-health-retries" class="form-control bg-dark text-white" placeholder="3" min="0">
</div>
<div class="col-md-3">
<label for="deploy-health-start-period" class="form-label">Start Period (s)</label>
<input type="number" id="deploy-health-start-period" class="form-control bg-dark text-white" placeholder="0" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-log-driver" class="form-label">Logging Driver</label>
<select id="deploy-log-driver" class="form-select bg-dark text-white">
<option value="">Default</option>
<option value="json-file">JSON File</option>
<option value="syslog">Syslog</option>
<option value="journald">Journald</option>
<option value="gelf">GELF</option>
<option value="fluentd">Fluentd</option>
<option value="awslogs">AWS Logs</option>
<option value="none">None</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Logging Options</label>
<div id="deploy-log-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addLogOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Advanced -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#advancedCollapse">
<i class="fas fa-cogs me-2"></i> Advanced
</button>
</h2>
<div id="advancedCollapse" class="accordion-collapse collapse" data-bs-parent="#deployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Sysctls</label>
<div id="deploy-sysctls-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addSysctl()">
<i class="fas fa-plus"></i> Add Sysctl
</button>
<small class="text-muted d-block mt-1">Format: key=value (e.g., net.ipv4.ip_forward=1)</small>
</div>
<div class="mb-3">
<label class="form-label">Ulimits</label>
<div id="deploy-ulimits-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addUlimit()">
<i class="fas fa-plus"></i> Add Ulimit
</button>
<small class="text-muted d-block mt-1">Format: name=soft:hard (e.g., nofile=1024:2048)</small>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="deploy-oom-kill-disable">
<label class="form-check-label" for="deploy-oom-kill-disable">Disable OOM Killer</label>
</div>
</div>
<div class="col-md-6">
<label for="deploy-pids-limit" class="form-label">PIDs Limit</label>
<input type="number" id="deploy-pids-limit" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label for="deploy-shm-size" class="form-label">Shared Memory Size (MB)</label>
<input type="number" id="deploy-shm-size" class="form-control bg-dark text-white" placeholder="64" min="0">
</div>
</div>
</div>
</div>
</div>
<!-- Preview Section -->
<div class="mt-4 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-show-preview" onchange="togglePreview()">
<label class="form-check-label" for="deploy-show-preview">Show JSON Preview</label>
</div>
<div id="deploy-preview-container" style="display: none;" class="mt-2">
<pre id="deploy-preview" class="bg-dark text-white p-3 rounded" style="max-height: 300px; overflow-y: auto; font-size: 12px;"></pre>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-secondary" onclick="resetDeployView()">
<i class="fas fa-undo me-2"></i>Reset
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-rocket me-2"></i>Deploy Container
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Containers View -->
<div id="containers-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-cube"></i>Containers</h2>
<p class="page-subtitle">Lifecycle, logs, stats, and terminals</p>
</div>
</div>
<!-- Container Filter and Search -->
<div class="view-toolbar mb-3">
<div class="row g-2 w-100 m-0">
<div class="col-md-3 px-1">
<input type="text" id="container-search" class="form-control" placeholder="Search containers by name or image…">
</div>
<div class="col-md-3 px-1">
<select id="container-status-filter" class="form-select">
<option value="all">All Status</option>
<option value="running" selected>Running</option>
<option value="exited">Stopped</option>
<option value="created">Created</option>
<option value="restarting">Restarting</option>
</select>
</div>
<div class="col-md-3 px-1">
<select id="container-sort" class="form-select">
<option value="name-asc">Sort by Name (A-Z)</option>
<option value="name-desc">Sort by Name (Z-A)</option>
<option value="cpu-desc" selected>Sort by CPU (High-Low)</option>
<option value="cpu-asc">Sort by CPU (Low-High)</option>
<option value="memory-desc">Sort by Memory (High-Low)</option>
<option value="memory-asc">Sort by Memory (Low-High)</option>
</select>
</div>
<div class="col-md-3 px-1">
<div class="d-flex gap-2 align-items-center flex-wrap">
<button type="button" id="clear-filters" class="btn btn-outline-secondary">Clear</button>
<!-- Column picker mounts after Clear via ui/tableColumns.js -->
<button type="button" class="btn btn-primary text-nowrap" id="add-container-btn" data-min-role="operator" onclick="navigateToView('add-container')">
<i class="fas fa-plus me-1"></i>Add container
</button>
</div>
</div>
</div>
</div>
<!-- Container bulk actions (enabled when rows are selected) -->
<div id="bulk-actions-toolbar" class="containers-action-bar mb-3">
<div class="containers-action-bar-inner">
<div class="containers-action-bar-meta">
<span id="selected-count" class="containers-selected-count">0</span>
<span class="text-secondary">selected</span>
<span class="containers-action-hint text-muted" id="containers-action-hint">Select containers to enable actions</span>
</div>
<div class="containers-action-btns" role="toolbar" aria-label="Container actions">
<button type="button" class="btn btn-sm btn-outline-success containers-action-btn" data-bulk-op="start" data-min-role="operator" onclick="bulkStartContainers()" title="Start selected" disabled>
<i class="fas fa-play"></i><span>Start</span>
</button>
<button type="button" class="btn btn-sm btn-outline-warning containers-action-btn" data-bulk-op="stop" data-min-role="operator" onclick="bulkStopContainers()" title="Stop selected" disabled>
<i class="fas fa-stop"></i><span>Stop</span>
</button>
<button type="button" class="btn btn-sm btn-outline-info containers-action-btn" data-bulk-op="restart" data-min-role="operator" onclick="bulkRestartContainers()" title="Restart selected" disabled>
<i class="fas fa-sync-alt"></i><span>Restart</span>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary containers-action-btn" data-bulk-op="pause" data-min-role="operator" onclick="bulkPauseContainers()" title="Pause selected" disabled>
<i class="fas fa-pause"></i><span>Pause</span>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary containers-action-btn" data-bulk-op="unpause" data-min-role="operator" onclick="bulkResumeContainers()" title="Resume (unpause) selected" disabled>
<i class="fas fa-play-circle"></i><span>Resume</span>
</button>
<button type="button" class="btn btn-sm btn-outline-danger containers-action-btn" data-bulk-op="kill" data-min-role="operator" onclick="bulkKillContainers()" title="Kill selected" disabled>
<i class="fas fa-skull"></i><span>Kill</span>
</button>
<span class="containers-action-sep" aria-hidden="true"></span>
<button type="button" class="btn btn-sm btn-outline-primary containers-action-btn" data-bulk-op="recreate" data-min-role="admin" onclick="bulkRecreateContainers()" title="Recreate selected (same config)" disabled>
<i class="fas fa-redo"></i><span>Recreate</span>
</button>
<button type="button" class="btn btn-sm btn-outline-danger containers-action-btn" data-bulk-op="remove" data-min-role="admin" onclick="bulkRemoveContainers()" title="Remove selected" disabled>
<i class="fas fa-trash"></i><span>Remove</span>
</button>
<span class="containers-action-sep" aria-hidden="true"></span>
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="window.pruneResource && window.pruneResource('containers')" data-min-role="admin" title="Prune stopped containers">
<i class="fas fa-broom"></i><span>Prune</span>
</button>
<button type="button" class="btn btn-sm btn-outline-info" id="check-image-updates-btn" title="Recheck registry digests for all containers">
<i class="fas fa-sync-alt"></i><span>Check updates</span>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary containers-action-btn" data-bulk-op="clear" onclick="clearContainerSelection()" title="Clear selection" disabled>
<i class="fas fa-times"></i><span>Clear</span>
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-striped pd-col-table" id="containers-table">
<thead>
<tr>
<th data-col="select" style="width: 40px;">
<input type="checkbox" id="select-all-containers" onchange="toggleSelectAllContainers(this)">
</th>
<th data-col="name">Name</th>
<th data-col="image">Image</th>
<th data-col="updates" class="text-center" style="width: 4.5rem;" title="Local image digest vs registry (tag)">
Updates
</th>
<th data-col="status">Status</th>
<th data-col="cpu">CPU (%)</th>
<th data-col="memory">Memory (MB)</th>
<th data-col="ip">IP Address</th>
<th data-col="actions">Actions</th>
</tr>
</thead>
<tbody id="container-list"></tbody>
</table>
</div>
</div>
</div>
<!-- Add container (blank create — separate from Deploy templates) -->
<div id="add-container-view" class="view hidden">
<div class="container-fluid add-container-page">
<div class="page-header">
<div>
<button type="button" class="btn btn-link text-secondary p-0 mb-1 addc-back-link" onclick="navigateToView('containers')">
<i class="fas fa-arrow-left me-1"></i>Back to containers
</button>
<h2><i class="fas fa-plus-circle"></i>Add container</h2>
<p class="page-subtitle">Create and start a container from an image</p>
</div>
</div>
<form id="add-container-form" class="add-container-form" onsubmit="return false;">
<div class="info-card mb-3">
<h6 class="text-primary mb-3"><i class="fas fa-cube me-2"></i>Image configuration</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="addc-name">Name <span class="text-danger">*</span></label>
<input type="text" id="addc-name" class="form-control bg-dark text-white" placeholder="my-container" required autocomplete="off" maxlength="63">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-image">Image <span class="text-danger">*</span></label>
<input type="text" id="addc-image" class="form-control bg-dark text-white" placeholder="nginx:alpine" required autocomplete="off">
<div class="form-text">Registry and tag included (e.g. ghcr.io/org/app:1.0)</div>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-always-pull" checked>
<label class="form-check-label" for="addc-always-pull">Always pull the image</label>
</div>
<div class="form-text">When on, pull from the registry before create. When off, use a local image if present.</div>
</div>
</div>
</div>
<div class="info-card mb-3">
<h6 class="text-primary mb-3"><i class="fas fa-network-wired me-2"></i>Network ports</h6>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="addc-publish-all">
<label class="form-check-label" for="addc-publish-all">Publish all exposed network ports to random host ports</label>
</div>
<div id="addc-ports" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-port">
<i class="fas fa-plus me-1"></i>Publish a new network port
</button>
</div>
<div class="info-card mb-3">
<h6 class="text-primary mb-3"><i class="fas fa-play me-2"></i>Actions</h6>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" role="switch" id="addc-auto-remove">
<label class="form-check-label" for="addc-auto-remove">Auto remove</label>
</div>
<div class="form-text mb-3">Automatically remove the container when it exits (one-shot jobs).</div>
<div class="d-flex flex-wrap gap-2 align-items-center">
<button type="button" class="btn btn-primary" id="addc-deploy-btn" data-min-role="operator">
<i class="fas fa-rocket me-2"></i>Deploy the container
</button>
<button type="button" class="btn btn-outline-secondary" id="addc-cancel-btn">Cancel</button>
<button type="button" class="btn btn-outline-secondary" id="addc-reset-btn">Reset</button>
</div>
</div>
<div class="info-card mb-3">
<h6 class="text-primary mb-3"><i class="fas fa-sliders-h me-2"></i>Advanced container settings</h6>
<div class="accordion accordion-flush" id="addcAccordion">
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-cmd-log" aria-expanded="false">
Command &amp; logging
</button>
</h2>
<div id="addc-cmd-log" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="addc-command">Command</label>
<input type="text" id="addc-command" class="form-control bg-dark text-white" placeholder="Override image CMD">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-entrypoint">Entrypoint</label>
<input type="text" id="addc-entrypoint" class="form-control bg-dark text-white" placeholder="Override image ENTRYPOINT">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-workdir">Working dir</label>
<input type="text" id="addc-workdir" class="form-control bg-dark text-white" placeholder="/app">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-user">User</label>
<input type="text" id="addc-user" class="form-control bg-dark text-white" placeholder="1000 or username">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-log-driver">Logging driver</label>
<select id="addc-log-driver" class="form-select bg-dark text-white">
<option value="">Default</option>
<option value="json-file">json-file</option>
<option value="local">local</option>
<option value="journald">journald</option>
<option value="syslog">syslog</option>
<option value="none">none</option>
</select>
</div>
<div class="col-12">
<label class="form-label" for="addc-health-cmd">Healthcheck command</label>
<input type="text" id="addc-health-cmd" class="form-control bg-dark text-white" placeholder="CMD-SHELL curl -f http://localhost/ || exit 1">
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-vols" aria-expanded="false">
Volumes
</button>
</h2>
<div id="addc-vols" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div id="addc-volumes" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-volume">
<i class="fas fa-plus me-1"></i>Map additional volume
</button>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-net" aria-expanded="false">
Network
</button>
</h2>
<div id="addc-net" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="addc-network-mode">Network mode</label>
<select id="addc-network-mode" class="form-select bg-dark text-white">
<option value="bridge">bridge</option>
<option value="host">host</option>
<option value="none">none</option>
<option value="container">container</option>
</select>
</div>
<div class="col-md-6" id="addc-custom-network-wrap">
<label class="form-label" for="addc-custom-network" id="addc-custom-network-label">Attach to network</label>
<input type="text" id="addc-custom-network" class="form-control bg-dark text-white" placeholder="optional network name">
<div class="form-text" id="addc-custom-network-hint">Named network to join after create (bridge mode)</div>
</div>
<div class="col-md-6">
<label class="form-label" for="addc-hostname">Hostname</label>
<input type="text" id="addc-hostname" class="form-control bg-dark text-white" placeholder="container-hostname">
</div>
<div class="col-md-6">
<label class="form-label" for="addc-domainname">Domain name</label>
<input type="text" id="addc-domainname" class="form-control bg-dark text-white" placeholder="example.com">
</div>
<div class="col-12">
<label class="form-label">DNS servers</label>
<div id="addc-dns" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-dns">
<i class="fas fa-plus me-1"></i>Add DNS server
</button>
</div>
<div class="col-12">
<label class="form-label">Hosts file entries</label>
<div id="addc-extra-hosts" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-extra-host">
<i class="fas fa-plus me-1"></i>Add hosts entry
</button>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-env-sec" aria-expanded="false">
Environment
</button>
</h2>
<div id="addc-env-sec" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div id="addc-env" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-env">
<i class="fas fa-plus me-1"></i>Add environment variable
</button>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-labels-sec" aria-expanded="false">
Labels
</button>
</h2>
<div id="addc-labels-sec" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div id="addc-labels" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-label">
<i class="fas fa-plus me-1"></i>Add label
</button>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-restart-sec" aria-expanded="false">
Restart policy
</button>
</h2>
<div id="addc-restart-sec" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="addc-restart-policy">Policy</label>
<select id="addc-restart-policy" class="form-select bg-dark text-white">
<option value="no">Never</option>
<option value="always">Always</option>
<option value="on-failure">On failure</option>
<option value="unless-stopped">Unless stopped</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" for="addc-restart-max-retries">Max retries (on-failure)</label>
<input type="number" id="addc-restart-max-retries" class="form-control bg-dark text-white" min="0" placeholder="0">
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item bg-transparent border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#addc-runtime-sec" aria-expanded="false">
Runtime &amp; resources
</button>
</h2>
<div id="addc-runtime-sec" class="accordion-collapse collapse" data-bs-parent="#addcAccordion">
<div class="accordion-body">
<div class="row g-3">
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-privileged">
<label class="form-check-label" for="addc-privileged">Privileged mode</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-init">
<label class="form-check-label" for="addc-init">Init</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-readonly-rootfs">
<label class="form-check-label" for="addc-readonly-rootfs">Read-only rootfs</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-tty">
<label class="form-check-label" for="addc-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="addc-stdin-open">
<label class="form-check-label" for="addc-stdin-open">Interactive (stdin)</label>
</div>
</div>
<div class="col-md-4">
<input type="checkbox" id="addc-detach" class="d-none" checked>
</div>
<div class="col-md-4">
<label class="form-label" for="addc-cpu-limit">CPU limit (cores)</label>
<input type="number" id="addc-cpu-limit" class="form-control bg-dark text-white" min="0" step="0.1" placeholder="Unlimited">
</div>
<div class="col-md-4">
<label class="form-label" for="addc-memory-limit">Memory limit (MB)</label>
<input type="number" id="addc-memory-limit" class="form-control bg-dark text-white" min="0" placeholder="Unlimited">
</div>
<div class="col-md-4">
<label class="form-label" for="addc-memory-reservation">Memory reservation (MB)</label>
<input type="number" id="addc-memory-reservation" class="form-control bg-dark text-white" min="0" placeholder="None">
</div>
<div class="col-md-4">
<label class="form-label" for="addc-shm-size">Shared memory (MB)</label>
<input type="number" id="addc-shm-size" class="form-control bg-dark text-white" min="0" placeholder="64">
</div>
<div class="col-12">
<label class="form-label">Capabilities (CapAdd)</label>
<div id="addc-capabilities" class="mb-2"></div>
<button type="button" class="btn btn-sm btn-outline-secondary" id="addc-add-capability">
<i class="fas fa-plus me-1"></i>Add capability
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="addc-sticky-actions d-flex flex-wrap gap-2 align-items-center mb-4">
<button type="button" class="btn btn-primary" id="addc-deploy-btn-bottom" data-min-role="operator" onclick="document.getElementById('addc-deploy-btn')?.click()">
<i class="fas fa-rocket me-2"></i>Deploy the container
</button>
<button type="button" class="btn btn-outline-secondary" onclick="navigateToView('containers')">Cancel</button>
</div>
</form>
</div>
</div>
<!-- Images View — local engine images only (remote registries: Registry tab) -->
<div id="images-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-layer-group"></i>Images</h2>
<p class="page-subtitle">Local Docker images on this host · remote catalogs live under <a href="#/registry" class="link-info" onclick="event.preventDefault(); navigateToView('registry')">Registry</a></p>
</div>
<div class="btn-group flex-wrap">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#pullImageModal" data-min-role="operator">
<i class="fas fa-download me-2"></i>Pull
</button>
<button class="btn btn-outline-primary" type="button" data-min-role="operator" onclick="window.openPushImageModal && window.openPushImageModal({ id: '', repoTags: [] })">
<i class="fas fa-upload me-2"></i>Push
</button>
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#buildImageModal" data-min-role="admin">
<i class="fas fa-hammer me-2"></i>Build
</button>
<button class="btn btn-outline-secondary" type="button" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('images')" title="Prune unused images">
<i class="fas fa-broom me-2"></i>Prune
</button>
<button class="btn btn-outline-secondary" type="button" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('builder')" title="Prune build cache">
<i class="fas fa-database me-2"></i>Builder cache
</button>
</div>
</div>
<div id="images-panel-local">
<!-- Image Filters -->
<div class="view-toolbar mb-3 d-flex flex-wrap gap-2 align-items-center justify-content-between">
<div class="btn-group" role="group" aria-label="Image filters">
<button type="button" class="btn btn-outline-primary image-filter-btn active" data-filter="all" onclick="filterImages('all')">
<i class="fas fa-list me-2"></i>All
<span id="filter-count-all" class="badge ms-2">0</span>
</button>
<button type="button" class="btn btn-outline-primary image-filter-btn" data-filter="used" onclick="filterImages('used')">
<i class="fas fa-check-circle me-2"></i>Used
<span id="filter-count-used" class="badge ms-2">0</span>
</button>
<button type="button" class="btn btn-outline-primary image-filter-btn" data-filter="unused" onclick="filterImages('unused')">
<i class="fas fa-times-circle me-2"></i>Unused
<span id="filter-count-unused" class="badge ms-2">0</span>
</button>
</div>
<input type="search" id="image-search" class="form-control form-control-sm bg-dark text-white list-search-input" placeholder="Filter local images…" spellcheck="false" autocomplete="off">
</div>
<!-- Bulk Actions Toolbar for Images -->
<div id="bulk-actions-images-toolbar" class="view-toolbar mb-3" style="display: none;">
<div class="d-flex align-items-center justify-content-between">
<div>
<span id="selected-images-count" class="text-primary fw-bold">0</span>
<span class="text-secondary"> image(s) selected</span>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-outline-danger" onclick="bulkRemoveImages()" data-min-role="admin" title="Remove Selected">
<i class="fas fa-trash me-1"></i>Remove
</button>
<button class="btn btn-sm btn-outline-secondary" onclick="clearImageSelection()" title="Clear Selection">
<i class="fas fa-times me-1"></i>Clear
</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-striped pd-col-table" id="images-table">
<thead>
<tr>
<th data-col="select" style="width: 40px;">
<input type="checkbox" id="select-all-images" onchange="toggleSelectAllImages(this)">
</th>
<th data-col="repository">Repository</th>
<th data-col="tags">Tags</th>
<th data-col="id">Image ID</th>
<th data-col="size">Size</th>
<th data-col="created">Created</th>
<th data-col="usage">Usage</th>
<th data-col="actions">Actions</th>
</tr>
</thead>
<tbody id="images-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Registry View — vault, Hub search, remote catalog / tag browser -->
<div id="registry-view" class="view hidden">
<div class="container-fluid">
<div class="page-header d-flex flex-wrap justify-content-between align-items-start gap-2">
<div>
<h2><i class="fas fa-warehouse"></i>Registry</h2>
<p class="page-subtitle">Credentials, Docker Hub search, and remote repository browser (tags, manifests, cleanup)</p>
</div>
<div class="d-flex flex-wrap gap-2 align-items-center">
<select id="registry-active-credential" class="form-select form-select-sm bg-dark text-white registry-cred-select" style="min-width: 14rem;" title="Credential for remote API calls">
<option value="">Public / session auto</option>
</select>
<button type="button" class="btn btn-sm btn-outline-secondary" id="registry-refresh-btn" title="Refresh session &amp; vault">
<i class="fas fa-sync"></i>
</button>
</div>
</div>
<div id="registry-session-status" class="alert alert-secondary small mb-3">Connect a peer to manage registries.</div>
<ul class="nav nav-pills flex-wrap gap-1 mb-3" id="registry-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button type="button" class="nav-link active" data-registry-tab="browser" role="tab">
<i class="fas fa-folder-tree me-1"></i>Browser
</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-registry-tab="hub" role="tab">
<i class="fas fa-magnifying-glass me-1"></i>Hub search
</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-registry-tab="credentials" role="tab">
<i class="fas fa-key me-1"></i>Credentials
</button>
</li>
</ul>
<!-- Browser: catalog + open repo + tags -->
<div id="registry-panel-browser" data-registry-panel="browser">
<div class="row g-3 mb-3">
<div class="col-lg-5">
<div class="card bg-dark border-secondary h-100">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<span><i class="fas fa-server me-2"></i>Repositories</span>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-info" id="registry-catalog-btn" title="List via V2 _catalog">
<i class="fas fa-list me-1"></i>Catalog
</button>
<button type="button" class="btn btn-outline-secondary" id="registry-catalog-more-btn" title="Load more" disabled>
More
</button>
</div>
</div>
<div class="card-body">
<div class="input-group input-group-sm mb-2">
<input type="text" id="registry-server-override" class="form-control bg-dark text-white" placeholder="Registry host (optional; uses credential)" spellcheck="false" autocomplete="off">
</div>
<div class="input-group input-group-sm mb-2">
<input type="text" id="registry-open-repo" class="form-control bg-dark text-white font-monospace" placeholder="Open repo (e.g. library/nginx or org/app)" spellcheck="false" autocomplete="off">
<button type="button" class="btn btn-primary" id="registry-open-repo-btn">Open</button>
</div>
<input type="search" id="registry-repo-filter" class="form-control form-control-sm bg-dark text-white mb-2" placeholder="Filter catalog…" spellcheck="false" autocomplete="off">
<div id="registry-catalog-status" class="small text-muted mb-2"></div>
<div id="registry-catalog-list" class="list-group list-group-flush small registry-scroll-list" style="max-height: 22rem; overflow: auto;"></div>
</div>
</div>
</div>
<div class="col-lg-7">
<div class="card bg-dark border-secondary h-100">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<span>
<i class="fas fa-tags me-2"></i>Tags
<code id="registry-active-repo" class="ms-2 small text-info"></code>
</span>
<div class="btn-group btn-group-sm">
<button type="button" class="btn btn-outline-info" id="registry-tags-refresh-btn" disabled title="Reload tags">
<i class="fas fa-sync"></i>
</button>
<button type="button" class="btn btn-outline-info" id="registry-tags-enrich-btn" disabled title="Fetch digests for visible tags">
Digests
</button>
<button type="button" class="btn btn-outline-danger" id="registry-tags-delete-selected-btn" data-min-role="admin" disabled title="Delete selected tags remotely">
<i class="fas fa-trash me-1"></i>Tags
</button>
<button type="button" class="btn btn-outline-danger" id="registry-repo-delete-btn" data-min-role="admin" disabled title="Delete entire repository (all tags/manifests)">
<i class="fas fa-trash-alt me-1"></i>Repo
</button>
</div>
</div>
<div class="card-body">
<div id="registry-tags-empty" class="text-muted small py-4 text-center">
Open a repository from the catalog or by name to list remote tags.
</div>
<div id="registry-tags-toolbar" class="d-none mb-2 d-flex flex-wrap gap-2 align-items-center justify-content-between">
<input type="search" id="registry-tag-filter" class="form-control form-control-sm bg-dark text-white" style="max-width: 16rem;" placeholder="Filter tags…" spellcheck="false" autocomplete="off">
<span id="registry-tag-count" class="small text-muted"></span>
</div>
<div class="table-responsive registry-scroll-list" style="max-height: 22rem; overflow: auto;">
<table class="table table-dark table-sm table-hover mb-0 d-none" id="registry-tags-table">
<thead>
<tr>
<th style="width: 2rem;"><input type="checkbox" id="registry-tags-select-all" title="Select all visible"></th>
<th>Tag</th>
<th>Digest</th>
<th>Size</th>
<th style="width: 9rem;">Actions</th>
</tr>
</thead>
<tbody id="registry-tags-body"></tbody>
</table>
</div>
<div id="registry-manifest-panel" class="mt-3 d-none">
<h6 class="text-muted small text-uppercase">Manifest</h6>
<pre id="registry-manifest-json" class="bg-black border border-secondary rounded p-2 small mb-0" style="max-height: 14rem; overflow: auto; white-space: pre-wrap;"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Hub search -->
<div id="registry-panel-hub" class="hidden" data-registry-panel="hub">
<div class="card bg-dark border-secondary">
<div class="card-header"><i class="fas fa-magnifying-glass me-2"></i>Docker Hub search</div>
<div class="card-body">
<p class="small text-muted">Search public Hub images via the engine API, then pull or open tags in the browser.</p>
<div class="input-group mb-3" style="max-width: 36rem;">
<input type="search" id="registry-hub-term" class="form-control bg-dark text-white" placeholder="Search Hub…" spellcheck="false" autocomplete="off">
<button type="button" class="btn btn-outline-info" id="registry-hub-search-btn">Search</button>
</div>
<div id="registry-hub-results" class="list-group list-group-flush small" style="max-height: 28rem; overflow: auto;"></div>
</div>
</div>
</div>
<!-- Credentials -->
<div id="registry-panel-credentials" class="hidden" data-registry-panel="credentials">
<div class="row g-4">
<div class="col-lg-6">
<div class="card bg-dark border-secondary h-100">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-key me-2"></i>Vault credentials</span>
</div>
<div class="card-body">
<p class="small text-muted">Encrypted at rest on the server. Use for private pull/push and remote API calls.</p>
<div id="registry-cred-list" class="mb-3"></div>
<hr class="border-secondary">
<h6 class="text-muted">Add registry</h6>
<form id="registry-store-form" class="row g-2">
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="label" placeholder="Label (optional)" autocomplete="off">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="serveraddress" placeholder="Registry URL (default Docker Hub)" autocomplete="off">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="username" placeholder="Username" required autocomplete="username">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="password" type="password" placeholder="Password / token" required autocomplete="current-password">
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="requireAuth" id="registry-require-auth">
<label class="form-check-label small" for="registry-require-auth">Require successful auth before storing</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-sm btn-primary" type="submit" data-min-role="admin">
<i class="fas fa-lock me-1"></i>Store encrypted
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card bg-dark border-secondary">
<div class="card-header"><i class="fas fa-sign-in-alt me-2"></i>Session login</div>
<div class="card-body">
<p class="small text-muted">Log in for this session only (does not persist unless you also store).</p>
<form id="registry-login-form" class="row g-2">
<div class="col-12">
<input class="form-control form-control-sm bg-dark text-white" name="serveraddress" placeholder="Registry URL (default Docker Hub)">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="username" placeholder="Username" required>
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="password" type="password" placeholder="Password / token" required>
</div>
<div class="col-12">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="alsoStore" id="registry-also-store">
<label class="form-check-label small" for="registry-also-store">Also store in vault</label>
</div>
</div>
<div class="col-12">
<button class="btn btn-sm btn-outline-primary" type="submit" data-min-role="admin">
<i class="fas fa-plug me-1"></i>Log in
</button>
</div>
</form>
</div>
</div>
<div class="card bg-dark border-secondary mt-3">
<div class="card-header"><i class="fas fa-circle-info me-2"></i>Remote cleanup notes</div>
<div class="card-body small text-muted">
<ul class="mb-0 ps-3">
<li>Deleting a tag resolves its manifest digest and issues <code>DELETE /v2/…/manifests/&lt;digest&gt;</code>.</li>
<li>Docker Hub and many SaaS registries disallow remote delete — use their UI or enable delete on self-hosted registries (Harbor, distribution, etc.).</li>
<li>After deletes, run the registrys garbage collection if required for disk reclaim.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Networks View -->
<div id="networks-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-diagram-project"></i>Networks</h2>
<p class="page-subtitle">Bridge, overlay, and container networks</p>
</div>
<div class="btn-group">
<button class="btn btn-primary" type="button" id="networks-create-btn" data-action="create-network" data-min-role="operator">
<i class="fas fa-plus me-2"></i>Create Network
</button>
<button class="btn btn-outline-secondary" type="button" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('networks')" title="Prune unused networks">
<i class="fas fa-broom me-2"></i>Prune
</button>
</div>
</div>
<div class="view-toolbar mb-3">
<input type="search" id="network-search" class="form-control form-control-sm bg-dark text-white list-search-input" placeholder="Search networks…" spellcheck="false" autocomplete="off">
</div>
<div class="table-responsive">
<table class="table table-dark table-striped pd-col-table" id="networks-table">
<thead>
<tr>
<th data-col="name">Name</th>
<th data-col="driver">Driver</th>
<th data-col="scope">Scope</th>
<th data-col="subnet">Subnet</th>
<th data-col="gateway">Gateway</th>
<th data-col="containers">Containers</th>
<th data-col="actions">Actions</th>
</tr>
</thead>
<tbody id="networks-list"></tbody>
</table>
</div>
</div>
</div>
<!-- Container Details View -->
<div id="container-details-view" class="view hidden">
<div class="container-fluid container-details-layout">
<div class="container-details-header">
<div class="container-details-header-left">
<button type="button" class="btn btn-outline-secondary btn-sm container-details-back" onclick="navigateToView('containers')" title="Back to containers">
<i class="fas fa-arrow-left"></i><span>Back</span>
</button>
<h2 class="mb-0" id="container-details-title">
<i class="fas fa-box"></i><span class="container-details-title-text">Container Details</span>
</h2>
</div>
<span class="detail-badge container-details-state-badge" id="container-details-state-badge" hidden></span>
<div class="container-details-actions" id="container-details-actions" role="toolbar" aria-label="Container actions">
<button type="button" class="btn btn-sm btn-outline-success" data-detail-action="start" data-min-role="operator" title="Start">
<i class="fas fa-play"></i><span>Start</span>
</button>
<button type="button" class="btn btn-sm btn-outline-warning" data-detail-action="stop" data-min-role="operator" title="Stop">
<i class="fas fa-stop"></i><span>Stop</span>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" data-detail-action="kill" data-min-role="operator" title="Kill">
<i class="fas fa-skull"></i><span>Kill</span>
</button>
<button type="button" class="btn btn-sm btn-outline-info" data-detail-action="restart" data-min-role="operator" title="Restart">
<i class="fas fa-sync-alt"></i><span>Restart</span>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" data-detail-action="pause" data-min-role="operator" title="Pause">
<i class="fas fa-pause"></i><span>Pause</span>
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" data-detail-action="resume" data-min-role="operator" title="Resume">
<i class="fas fa-play-circle"></i><span>Resume</span>
</button>
<span class="containers-action-sep container-details-action-sep" aria-hidden="true"></span>
<button type="button" class="btn btn-sm btn-outline-primary" data-detail-action="recreate" data-min-role="admin" title="Recreate with same config">
<i class="fas fa-redo"></i><span>Recreate</span>
</button>
<button type="button" class="btn btn-sm btn-outline-primary" data-detail-action="duplicate" data-min-role="admin" title="Duplicate / Edit">
<i class="fas fa-clone"></i><span>Duplicate/Edit</span>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" data-detail-action="remove" data-min-role="admin" title="Remove">
<i class="fas fa-trash"></i><span>Remove</span>
</button>
</div>
</div>
<!-- Container Details Tabs -->
<ul class="nav nav-tabs container-details-tabs" id="container-details-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview-pane" type="button" role="tab" title="Overview">
<i class="fas fa-info-circle"></i><span>Overview</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="logs-tab" data-bs-toggle="tab" data-bs-target="#logs-pane" type="button" role="tab" title="Logs">
<i class="fas fa-list-alt"></i><span>Logs</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="terminal-tab" data-bs-toggle="tab" data-bs-target="#terminal-pane" type="button" role="tab" title="Terminal">
<i class="fas fa-terminal"></i><span>Terminal</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="stats-tab" data-bs-toggle="tab" data-bs-target="#stats-pane" type="button" role="tab" title="Stats">
<i class="fas fa-chart-line"></i><span>Stats</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="processes-tab" data-bs-toggle="tab" data-bs-target="#processes-pane" type="button" role="tab" title="Processes">
<i class="fas fa-microchip"></i><span>Processes</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="config-tab" data-bs-toggle="tab" data-bs-target="#config-pane" type="button" role="tab" title="Configuration">
<i class="fas fa-cog"></i><span>Configuration</span>
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="networking-tab" data-bs-toggle="tab" data-bs-target="#networking-pane" type="button" role="tab" title="Networking">
<i class="fas fa-network-wired"></i><span>Networking</span>
</button>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content container-details-tab-content" id="container-details-tab-content">
<!-- Overview Pane -->
<div class="tab-pane fade show active" id="overview-pane" role="tabpanel">
<div id="container-overview-content" class="detail-tab-body detail-scroll-body">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text mt-2"></div>
</div>
</div>
<!-- Logs Pane -->
<div class="tab-pane fade" id="logs-pane" role="tabpanel">
<div class="detail-fill-panel">
<!-- Logs Toolbar -->
<div class="logs-toolbar">
<div class="logs-toolbar-left">
<div class="logs-search-container">
<i class="fas fa-search logs-search-icon"></i>
<input type="text" id="logs-search-input" class="logs-search-input" placeholder="Search logs...">
<button class="logs-clear-search" id="logs-clear-search" title="Clear search" style="display: none;">
<i class="fas fa-times"></i>
</button>
</div>
<div class="logs-filter-buttons">
<button class="logs-filter-btn active" data-filter="all" title="All logs">
<i class="fas fa-list"></i> All
</button>
<button class="logs-filter-btn" data-filter="error" title="Errors only">
<i class="fas fa-exclamation-circle"></i> Error
</button>
<button class="logs-filter-btn" data-filter="warn" title="Warnings only">
<i class="fas fa-exclamation-triangle"></i> Warn
</button>
<button class="logs-filter-btn" data-filter="info" title="Info only">
<i class="fas fa-info-circle"></i> Info
</button>
</div>
</div>
<div class="logs-toolbar-right">
<div class="logs-controls">
<label class="logs-toggle-label" title="Auto-scroll">
<input type="checkbox" id="logs-auto-scroll" checked>
<span><i class="fas fa-arrow-down"></i> Auto-scroll</span>
</label>
<button class="logs-control-btn" id="logs-pause-btn" title="Pause/Resume">
<i class="fas fa-pause"></i> Pause
</button>
<button class="logs-control-btn" id="logs-clear-btn" title="Clear logs">
<i class="fas fa-trash"></i> Clear
</button>
<button class="logs-control-btn" id="logs-copy-btn" title="Copy all logs">
<i class="fas fa-copy"></i> Copy
</button>
<button class="logs-control-btn" id="logs-download-btn" title="Download logs">
<i class="fas fa-download"></i> Download
</button>
</div>
</div>
</div>
<!-- Logs Container -->
<div id="container-logs-content" class="logs-container">
<div class="text-muted text-center logs-loading">Loading logs...</div>
</div>
</div>
</div>
<!-- Terminal Pane -->
<div class="tab-pane fade" id="terminal-pane" role="tabpanel">
<div class="detail-fill-panel">
<!-- Terminal Toolbar -->
<div class="terminal-toolbar">
<div class="terminal-toolbar-left">
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="terminal-font-decrease" title="Decrease font size">
<i class="fas fa-minus"></i>
</button>
<span class="terminal-font-size" id="terminal-font-size-display">14</span>
<button class="terminal-control-btn" id="terminal-font-increase" title="Increase font size">
<i class="fas fa-plus"></i>
</button>
<button class="terminal-control-btn" id="terminal-font-reset" title="Reset font size">
<i class="fas fa-undo"></i>
</button>
</div>
<div class="terminal-controls-divider"></div>
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="terminal-copy-btn" title="Copy selection">
<i class="fas fa-copy"></i> Copy
</button>
<button class="terminal-control-btn" id="terminal-clear-btn" title="Clear terminal">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</div>
<div class="terminal-toolbar-right">
<div class="terminal-controls-group">
<select class="terminal-theme-select" id="terminal-theme-select" title="Select theme">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="solarized-dark">Solarized Dark</option>
<option value="solarized-light">Solarized Light</option>
<option value="monokai">Monokai</option>
</select>
<button class="terminal-control-btn" id="terminal-settings-btn" title="Terminal settings">
<i class="fas fa-cog"></i>
</button>
</div>
</div>
</div>
<!-- Terminal Container -->
<div id="container-terminal-content" class="terminal-container-wrapper">
<div id="container-terminal-xterm" class="terminal-xterm-container"></div>
</div>
</div>
</div>
<!-- Stats Pane -->
<div class="tab-pane fade" id="stats-pane" role="tabpanel">
<div id="container-stats-content" class="detail-tab-body detail-stats-shell">
<div class="detail-stats-toolbar">
<div class="detail-stats-toolbar-left">
<span class="detail-stats-live" id="detail-stats-live" title="Live stream status">
<i class="fas fa-circle"></i> Live
</span>
<div class="detail-stats-timeframe" role="group" aria-label="Chart timeframe">
<button type="button" class="detail-stats-tf active" data-tf="60">1m</button>
<button type="button" class="detail-stats-tf" data-tf="300">5m</button>
<button type="button" class="detail-stats-tf" data-tf="900">15m</button>
<button type="button" class="detail-stats-tf" data-tf="1800">30m</button>
<button type="button" class="detail-stats-tf" data-tf="0">All</button>
</div>
</div>
<div class="detail-stats-toolbar-right">
<label class="detail-stats-check" title="Auto-scale Y axes to data">
<input type="checkbox" id="detail-stats-autoscale" checked>
<span>Auto-scale</span>
</label>
<button type="button" class="btn btn-sm btn-outline-secondary" id="detail-stats-pause-btn" title="Pause chart updates">
<i class="fas fa-pause"></i> Pause
</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="detail-stats-export-btn" title="Export CSV for current timeframe">
<i class="fas fa-download"></i> CSV
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="detail-stats-refresh-btn" title="Reload history from server">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<div class="detail-stats-kpis">
<div class="detail-stats-kpi detail-stats-kpi--cpu">
<div class="detail-stats-kpi-top">
<i class="fas fa-microchip"></i>
<span>CPU</span>
</div>
<div class="detail-stats-kpi-value" id="detail-cpu">0%</div>
<div class="detail-stats-kpi-sub" id="detail-cpu-sub">avg — · peak —</div>
<div class="detail-stats-bar"><i id="detail-cpu-bar" style="width:0%"></i></div>
</div>
<div class="detail-stats-kpi detail-stats-kpi--mem">
<div class="detail-stats-kpi-top">
<i class="fas fa-memory"></i>
<span>Memory</span>
</div>
<div class="detail-stats-kpi-value" id="detail-memory">0 MB</div>
<div class="detail-stats-kpi-sub" id="detail-memory-sub">of — · —%</div>
<div class="detail-stats-bar"><i id="detail-memory-bar" style="width:0%"></i></div>
</div>
<div class="detail-stats-kpi detail-stats-kpi--net">
<div class="detail-stats-kpi-top">
<i class="fas fa-network-wired"></i>
<span>Network</span>
</div>
<div class="detail-stats-kpi-value" id="detail-net">↓0 · ↑0</div>
<div class="detail-stats-kpi-sub" id="detail-net-sub">B/s in window</div>
</div>
<div class="detail-stats-kpi detail-stats-kpi--disk">
<div class="detail-stats-kpi-top">
<i class="fas fa-hdd"></i>
<span>Block I/O</span>
</div>
<div class="detail-stats-kpi-value" id="detail-disk">R0 · W0</div>
<div class="detail-stats-kpi-sub" id="detail-disk-sub">B/s in window</div>
</div>
</div>
<div class="detail-stats-summary" id="detail-stats-summary">
<div class="detail-stats-sum-item"><span>Samples</span><strong id="detail-stats-samples">0</strong></div>
<div class="detail-stats-sum-item"><span>Window</span><strong id="detail-stats-window"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
<div class="detail-stats-sum-item"><span>CPU p95</span><strong id="detail-stats-cpu-p95"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
<div class="detail-stats-sum-item"><span>Mem peak</span><strong id="detail-stats-mem-peak"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
<div class="detail-stats-sum-item"><span>Updated</span><strong id="detail-stats-updated"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span></strong></div>
</div>
<div id="stats-charts-container" class="detail-stats-charts">
<div class="detail-stats-chart-card">
<div class="detail-stats-chart-head">
<h6><i class="fas fa-chart-area"></i> CPU usage</h6>
<span class="detail-stats-chart-unit">%</span>
</div>
<div class="detail-stats-chart-wrap">
<canvas id="cpu-chart-canvas"></canvas>
</div>
</div>
<div class="detail-stats-chart-card">
<div class="detail-stats-chart-head">
<h6><i class="fas fa-chart-area"></i> Memory</h6>
<span class="detail-stats-chart-unit">MB</span>
</div>
<div class="detail-stats-chart-wrap">
<canvas id="memory-chart-canvas"></canvas>
</div>
</div>
<div class="detail-stats-chart-card">
<div class="detail-stats-chart-head">
<h6><i class="fas fa-exchange-alt"></i> Network throughput</h6>
<span class="detail-stats-chart-unit">B/s</span>
</div>
<div class="detail-stats-chart-wrap">
<canvas id="net-chart-canvas"></canvas>
</div>
</div>
<div class="detail-stats-chart-card">
<div class="detail-stats-chart-head">
<h6><i class="fas fa-database"></i> Block I/O</h6>
<span class="detail-stats-chart-unit">B/s</span>
</div>
<div class="detail-stats-chart-wrap">
<canvas id="disk-chart-canvas"></canvas>
</div>
</div>
</div>
</div>
</div>
<!-- Processes Pane (docker top) -->
<div class="tab-pane fade" id="processes-pane" role="tabpanel">
<div class="detail-tab-body detail-fill-panel">
<div class="detail-panel-toolbar">
<h5 class="mb-0 detail-panel-title"><i class="fas fa-microchip me-2"></i>Running Processes</h5>
<div class="procs-toolbar-actions">
<span class="procs-result-count" id="procs-result-count" aria-live="polite"></span>
<button class="btn btn-sm btn-outline-secondary" type="button" id="procs-filter-reset-btn" title="Clear all filters" disabled>
<i class="fas fa-times"></i><span>Clear</span>
</button>
<button class="btn btn-sm btn-outline-primary" type="button" id="refresh-container-top-btn" title="Refresh process list">
<i class="fas fa-sync-alt"></i><span>Refresh</span>
</button>
</div>
</div>
<form class="procs-filter-form" id="procs-filter-form" autocomplete="off" spellcheck="false" onsubmit="return false;">
<div class="procs-filter-row procs-filter-row--primary">
<div class="procs-search-field">
<i class="fas fa-search procs-search-icon" aria-hidden="true"></i>
<input
type="search"
id="procs-filter-query"
class="procs-filter-input procs-filter-input--search"
placeholder="Search processes (PID, user, command, …)"
aria-label="Search all process fields"
>
<button type="button" class="procs-search-clear" id="procs-filter-query-clear" title="Clear search" hidden>
<i class="fas fa-times"></i>
</button>
<span class="procs-debounce-hint" id="procs-debounce-hint" hidden>
<i class="fas fa-circle-notch fa-spin"></i>
</span>
</div>
<button type="button" class="procs-filter-toggle" id="procs-filter-advanced-toggle" aria-expanded="false" aria-controls="procs-filter-advanced">
<i class="fas fa-sliders-h"></i>
<span>Filters</span>
<i class="fas fa-chevron-down procs-filter-chevron"></i>
</button>
</div>
<div class="procs-filter-advanced" id="procs-filter-advanced" hidden>
<div class="procs-filter-grid">
<label class="procs-field">
<span class="procs-field-label"><i class="fas fa-user"></i> User / UID</span>
<input type="text" id="procs-filter-user" class="procs-filter-input" placeholder="root, 1000…" aria-label="Filter by user or UID">
</label>
<label class="procs-field">
<span class="procs-field-label"><i class="fas fa-hashtag"></i> PID</span>
<input type="text" id="procs-filter-pid" class="procs-filter-input" placeholder="Exact or partial" inputmode="numeric" aria-label="Filter by PID">
</label>
<label class="procs-field">
<span class="procs-field-label"><i class="fas fa-sitemap"></i> PPID</span>
<input type="text" id="procs-filter-ppid" class="procs-filter-input" placeholder="Parent PID" inputmode="numeric" aria-label="Filter by PPID">
</label>
<label class="procs-field procs-field--wide">
<span class="procs-field-label"><i class="fas fa-terminal"></i> Command</span>
<input type="text" id="procs-filter-cmd" class="procs-filter-input" placeholder="Matches command / args" aria-label="Filter by command">
</label>
<label class="procs-field">
<span class="procs-field-label"><i class="fas fa-sort"></i> Sort by</span>
<select id="procs-filter-sort" class="procs-filter-input procs-filter-select" aria-label="Sort column">
<option value="">Default order</option>
</select>
</label>
<label class="procs-field">
<span class="procs-field-label"><i class="fas fa-exchange-alt"></i> Direction</span>
<select id="procs-filter-dir" class="procs-filter-input procs-filter-select" aria-label="Sort direction">
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</label>
<label class="procs-field procs-field--check">
<span class="procs-field-label"><i class="fas fa-filter"></i> Match</span>
<label class="procs-check">
<input type="checkbox" id="procs-filter-case" checked>
<span>Case-insensitive</span>
</label>
</label>
</div>
<p class="procs-filter-hint">
<i class="fas fa-info-circle"></i>
Filters apply client-side and are debounced. Refresh reloads processes from the container.
</p>
</div>
</form>
<div id="container-top-content" class="table-responsive detail-scroll-body">
<div class="text-muted small">Open this tab on a running container to list processes.</div>
</div>
</div>
</div>
<!-- Configuration Pane -->
<div class="tab-pane fade" id="config-pane" role="tabpanel">
<div id="container-config-content" class="detail-tab-body detail-scroll-body">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text mt-2"></div>
</div>
</div>
<!-- Networking Pane -->
<div class="tab-pane fade" id="networking-pane" role="tabpanel">
<div id="container-networking-content" class="detail-tab-body detail-scroll-body">
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text mt-2"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Volumes View -->
<div id="stacks-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-boxes-stacked"></i>Stacks</h2>
<p class="page-subtitle">Compose stacks on the remote host</p>
</div>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#deploy-stack-modal" data-min-role="operator">
<i class="fas fa-plus me-2"></i>Deploy Stack
</button>
</div>
<div class="view-toolbar mb-3">
<input type="search" id="stack-search" class="form-control form-control-sm bg-dark text-white list-search-input" placeholder="Search stacks…" spellcheck="false" autocomplete="off">
</div>
<div id="stacks-list" class="table-responsive">
<table class="table table-dark table-hover pd-col-table" id="stacks-table">
<thead>
<tr>
<th data-col="name">Stack Name</th>
<th data-col="services">Services</th>
<th data-col="containers">Containers</th>
<th data-col="status">Status</th>
<th data-col="actions">Actions</th>
</tr>
</thead>
<tbody id="stacks-list-body">
<tr>
<td colspan="5" class="text-center text-muted">No stacks found. Deploy a stack to get started.</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Swarm view (on by default; ENABLE_SWARM=0 to disable) -->
<div id="swarm-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-project-diagram"></i>Swarm</h2>
<p class="page-subtitle">Services, nodes, and tasks (on by default; set ENABLE_SWARM=0 to disable)</p>
</div>
<button class="btn btn-outline-primary" type="button" id="swarm-refresh-btn"><i class="fas fa-sync me-1"></i>Refresh</button>
</div>
<div id="swarm-status-banner" class="alert alert-secondary small mb-3">Checking Swarm…</div>
<ul class="nav nav-tabs mb-3" id="swarm-tabs" role="tablist">
<li class="nav-item"><button class="nav-link active" data-swarm-tab="services" type="button">Services</button></li>
<li class="nav-item"><button class="nav-link" data-swarm-tab="nodes" type="button">Nodes</button></li>
<li class="nav-item"><button class="nav-link" data-swarm-tab="tasks" type="button">Tasks</button></li>
<li class="nav-item"><button class="nav-link" data-swarm-tab="secrets" type="button">Secrets</button></li>
<li class="nav-item"><button class="nav-link" data-swarm-tab="configs" type="button">Configs</button></li>
</ul>
<div id="swarm-panel-services" class="swarm-panel">
<div class="table-responsive">
<table class="table table-dark table-hover table-sm">
<thead><tr><th>Name</th><th>Image</th><th>Replicas</th><th>ID</th><th></th></tr></thead>
<tbody id="swarm-services-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
</table>
</div>
</div>
<div id="swarm-panel-nodes" class="swarm-panel hidden">
<div class="table-responsive">
<table class="table table-dark table-hover table-sm">
<thead><tr><th>Hostname</th><th>Role</th><th>Status</th><th>Availability</th><th>ID</th></tr></thead>
<tbody id="swarm-nodes-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
</table>
</div>
</div>
<div id="swarm-panel-tasks" class="swarm-panel hidden">
<div class="table-responsive">
<table class="table table-dark table-hover table-sm">
<thead><tr><th>Service</th><th>Node</th><th>Desired</th><th>Current</th><th>ID</th></tr></thead>
<tbody id="swarm-tasks-body"><tr class="pd-loading-row"><td colspan="5" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
</table>
</div>
</div>
<div id="swarm-panel-secrets" class="swarm-panel hidden">
<div class="settings-section mb-3" data-min-role="admin">
<h3 class="h6">Create secret</h3>
<div class="row g-2">
<div class="col-md-3"><input id="swarm-secret-name" class="form-control form-control-sm bg-dark text-white" placeholder="Name"></div>
<div class="col-md-7"><input id="swarm-secret-data" class="form-control form-control-sm bg-dark text-white font-monospace" placeholder="Secret value"></div>
<div class="col-md-2"><button type="button" class="btn btn-sm btn-primary w-100" id="swarm-secret-create">Create</button></div>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-hover table-sm">
<thead><tr><th>Name</th><th>Created</th><th>ID</th></tr></thead>
<tbody id="swarm-secrets-body"><tr class="pd-loading-row"><td colspan="3" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
</table>
</div>
</div>
<div id="swarm-panel-configs" class="swarm-panel hidden">
<div class="settings-section mb-3" data-min-role="admin">
<h3 class="h6">Create config</h3>
<div class="row g-2">
<div class="col-md-3"><input id="swarm-config-name" class="form-control form-control-sm bg-dark text-white" placeholder="Name"></div>
<div class="col-md-7"><textarea id="swarm-config-data" class="form-control form-control-sm bg-dark text-white font-monospace" rows="2" placeholder="Config payload"></textarea></div>
<div class="col-md-2"><button type="button" class="btn btn-sm btn-primary w-100" id="swarm-config-create">Create</button></div>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-hover table-sm">
<thead><tr><th>Name</th><th>Created</th><th>ID</th></tr></thead>
<tbody id="swarm-configs-body"><tr class="pd-loading-row"><td colspan="3" class="text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Loading…</span></span></td></tr></tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Deploy Stack Modal — scrollable + viewport-capped so it never clips under the titlebar -->
<div class="modal fade" id="deploy-stack-modal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-scrollable deploy-stack-modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title">Deploy Docker Compose Stack</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="deploy-stack-form">
<div class="mb-3">
<label for="stack-name" class="form-label">Stack Name</label>
<input type="text" class="form-control bg-dark text-white" id="stack-name" required>
</div>
<div class="mb-3">
<label for="compose-content" class="form-label">Docker Compose YAML</label>
<textarea class="form-control bg-dark text-white font-monospace deploy-stack-compose-ta" id="compose-content" rows="8" required placeholder="version: '3'
services:
web:
image: nginx
ports:
- '80:80'
db:
image: postgres
environment:
POSTGRES_PASSWORD: password"></textarea>
</div>
<div class="mb-3">
<label for="stack-env-file" class="form-label">Env file (optional)</label>
<textarea class="form-control bg-dark text-white font-monospace deploy-stack-env-ta" id="stack-env-file" rows="3" placeholder="KEY=value&#10;OTHER=1" spellcheck="false"></textarea>
<div class="form-text">Passed as compose <code>--env-file</code> content on deploy / re-deploy.</div>
</div>
<hr class="border-secondary">
<h6 class="text-muted">GitOps sync (optional)</h6>
<p class="small text-muted">Clone a public/private git repo and deploy its compose file. Requires <code>git</code> on the server host.</p>
<div class="row g-2 mb-2">
<div class="col-md-8">
<label class="form-label" for="stack-git-url">Repository URL</label>
<input type="url" class="form-control bg-dark text-white font-monospace" id="stack-git-url" placeholder="https://github.com/org/repo.git" spellcheck="false">
</div>
<div class="col-md-4">
<label class="form-label" for="stack-git-ref">Branch / tag</label>
<input type="text" class="form-control bg-dark text-white" id="stack-git-ref" placeholder="main" value="main">
</div>
</div>
<div class="mb-2">
<label class="form-label" for="stack-git-path">Compose path in repo</label>
<input type="text" class="form-control bg-dark text-white font-monospace" id="stack-git-path" placeholder="docker-compose.yml" value="docker-compose.yml">
</div>
<button type="button" class="btn btn-outline-info btn-sm" id="stack-gitops-btn" data-min-role="operator">
<i class="fas fa-code-branch me-1"></i>Sync from Git &amp; deploy
</button>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="deploy-stack-btn">Deploy Stack</button>
</div>
</div>
</div>
</div>
<div id="volumes-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-hard-drive"></i>Volumes</h2>
<p class="page-subtitle">Persistent storage volumes</p>
</div>
<div class="btn-group">
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createVolumeModal" data-min-role="operator">
<i class="fas fa-plus me-2"></i>Create Volume
</button>
<button class="btn btn-outline-secondary" type="button" data-min-role="admin" onclick="window.pruneResource && window.pruneResource('volumes')" title="Prune unused volumes">
<i class="fas fa-broom me-2"></i>Prune
</button>
</div>
</div>
<div class="view-toolbar mb-3">
<input type="search" id="volume-search" class="form-control form-control-sm bg-dark text-white list-search-input" placeholder="Search volumes…" spellcheck="false" autocomplete="off">
</div>
<div class="table-responsive">
<table class="table table-dark table-striped pd-col-table" id="volumes-table">
<thead>
<tr>
<th data-col="name">Name</th>
<th data-col="driver">Driver</th>
<th data-col="mountpoint">Mountpoint</th>
<th data-col="usage">Usage</th>
<th data-col="actions">Actions</th>
</tr>
</thead>
<tbody id="volumes-list"></tbody>
</table>
</div>
</div>
</div>
<!-- Fleet multi-host overview -->
<div id="fleet-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-server"></i>Fleet</h2>
<p class="page-subtitle">Concurrent multi-host health and Docker status</p>
</div>
<div class="d-flex flex-wrap gap-2 align-items-center">
<select id="fleet-env-filter" class="form-select form-select-sm bg-dark text-white" style="width: auto; min-width: 9rem;" title="Filter by environment tag">
<option value="">All environments</option>
<option value="prod">prod</option>
<option value="staging">staging</option>
<option value="dev">dev</option>
<option value="lab">lab</option>
</select>
<button class="btn btn-outline-primary" type="button" id="fleet-refresh-btn">
<i class="fas fa-sync me-2"></i>Refresh
</button>
</div>
</div>
<p class="small text-muted mb-2">Tag peers with an environment (prod/staging/dev) on each card for multi-host grouping.</p>
<div id="fleet-cards" class="row g-3"></div>
</div>
</div>
<!-- Access: peers, invites, vault -->
<div id="access-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-user-shield"></i>Access</h2>
<p class="page-subtitle">Peer invites, revoke list, registry vault</p>
</div>
</div>
<div class="row g-4">
<div class="col-lg-6">
<div class="card bg-dark border-secondary">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-users me-2"></i>Peers</span>
<button class="btn btn-sm btn-primary" id="access-invite-btn" type="button" data-bs-toggle="modal" data-bs-target="#invitePeerModal" data-min-role="admin" data-admin-invites>
<i class="fas fa-ticket me-1"></i>Create invite
</button>
</div>
<div class="card-body">
<div id="access-peers-list" class="small text-muted">Connect as admin to manage peers.</div>
<div data-min-role="admin" data-admin-invites>
<hr>
<h6 class="text-muted">Active invites <span class="small">(admin only)</span></h6>
<div id="access-invites-list" class="small"></div>
</div>
</div>
</div>
<div class="card bg-dark border-secondary mt-4" data-min-role="admin">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-ban me-2"></i>Revoked peers</span>
<button type="button" class="btn btn-sm btn-outline-warning" id="access-clear-revoked-btn" data-min-role="admin" title="Remove all peers from the revoke list">
<i class="fas fa-trash me-1"></i>Clear all
</button>
</div>
<div class="card-body">
<p class="small text-muted mb-2">Revoked peer public keys cannot connect. Unrevoke to allow them again (they keep no role until re-invited or re-registered).</p>
<div id="access-revoked-list" class="small text-muted">No revoked peers.</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card bg-dark border-secondary">
<div class="card-header"><i class="fas fa-key me-2"></i>Registry vault</div>
<div class="card-body">
<p class="small text-muted mb-2">Full registry manager lives under <strong>Registry</strong> (browser, credentials, Hub search). Quick store here:</p>
<form id="vault-store-form" class="mb-3">
<div class="row g-2">
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="label" placeholder="Label (optional)">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="serveraddress" placeholder="Registry (default Docker Hub)">
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="username" placeholder="Username" required>
</div>
<div class="col-md-6">
<input class="form-control form-control-sm bg-dark text-white" name="password" type="password" placeholder="Password" required>
</div>
<div class="col-12">
<button class="btn btn-sm btn-primary" type="submit">Store encrypted</button>
<button class="btn btn-sm btn-outline-secondary" type="button" onclick="navigateToView('registry')">Open registry manager</button>
</div>
</div>
</form>
<div id="access-vault-list" class="small"></div>
<hr class="border-secondary">
<h6 class="text-muted">Registry browser (Hub search)</h6>
<p class="small text-muted mb-2">Search Docker Hub from the active peer, then pull. Use vault credentials for private registries via pull.</p>
<div class="input-group input-group-sm mb-2">
<input type="search" id="access-hub-term" class="form-control bg-dark text-white" placeholder="Search Hub…" spellcheck="false">
<button type="button" class="btn btn-outline-info" id="access-hub-search-btn">Search</button>
</div>
<div id="access-hub-results" class="list-group list-group-flush small" style="max-height: 200px; overflow: auto;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Events -->
<div id="events-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-bolt"></i>Events</h2>
<p class="page-subtitle">Live Docker engine activity on the active peer</p>
</div>
<div class="page-header-actions">
<button class="btn btn-outline-primary" type="button" id="events-refresh-btn"><i class="fas fa-sync me-1"></i>Refresh</button>
<button class="btn btn-outline-secondary" type="button" id="events-pause-btn">Pause</button>
</div>
</div>
<div class="mb-2">
<input type="search" id="events-filter" class="form-control bg-dark text-white" placeholder="Filter events…">
</div>
<div id="events-full-list" class="list-group list-group-flush" style="font-family: var(--font-mono, monospace); font-size: 0.85rem;"></div>
</div>
</div>
<!-- Host / engine -->
<div id="host-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-microchip"></i>Host</h2>
<p class="page-subtitle">Engine info, capacity, and process metrics</p>
</div>
<button class="btn btn-outline-primary" type="button" id="host-refresh-btn"><i class="fas fa-sync me-1"></i>Refresh</button>
</div>
<div class="row g-3">
<div class="col-lg-6">
<div class="settings-section">
<h3>Engine</h3>
<div id="host-engine-summary" class="small text-muted">Connect a peer to load host data.</div>
</div>
</div>
<div class="col-lg-6">
<div class="settings-section">
<h3>Metrics</h3>
<div id="host-metrics-summary" class="small text-muted"><span class="pd-job-loading d-inline-flex align-items-center gap-2"><span class="job-spinner" role="status" aria-label="Loading"><span class="job-spinner-ring"></span></span><span>Waiting for peer…</span></span></div>
</div>
</div>
<div class="col-12">
<div class="settings-section">
<h3>Scheduled maintenance</h3>
<p class="small text-muted mb-2">
Interval jobs on the <strong>server</strong> (min 1 minute). Useful for periodic prune / Docker ping — not external webhooks.
</p>
<div class="row g-2 align-items-end mb-2">
<div class="col-md-3">
<label class="form-label small" for="schedule-kind">Job</label>
<select id="schedule-kind" class="form-select form-select-sm bg-dark text-white">
<option value="dockerPing">Docker ping</option>
<option value="pruneContainers">Prune containers</option>
<option value="pruneImages">Prune images</option>
<option value="systemPrune">System prune</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label small" for="schedule-minutes">Every (min)</label>
<input type="number" id="schedule-minutes" class="form-control form-control-sm bg-dark text-white" min="1" value="60">
</div>
<div class="col-md-3">
<div class="form-check mt-4">
<input class="form-check-input" type="checkbox" id="schedule-volumes">
<label class="form-check-label small" for="schedule-volumes">Include volumes (system prune)</label>
</div>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-sm btn-outline-primary" id="schedule-add-btn" data-min-role="admin">Add</button>
<button type="button" class="btn btn-sm btn-outline-secondary" id="host-schedules-refresh"></button>
</div>
</div>
<div id="schedules-list" class="small"></div>
</div>
</div>
<div class="col-12">
<div class="settings-section">
<h3>Raw info</h3>
<pre id="host-info-raw" class="small mb-0" style="max-height: 20rem; overflow: auto; color: #94a3b8;"></pre>
</div>
</div>
</div>
</div>
</div>
<!-- Holesail tunnels -->
<div id="tunnels-view" class="view hidden">
<div class="container-fluid">
<div class="page-header">
<div>
<h2><i class="fas fa-network-wired"></i>Tunnels</h2>
<p class="page-subtitle">Share local ports over Holesail (P2P) without opening the firewall</p>
</div>
<div class="page-header-actions">
<button class="btn btn-outline-primary" type="button" id="tunnels-refresh-btn"><i class="fas fa-sync me-1"></i>Refresh</button>
</div>
</div>
<div id="tunnels-status-banner" class="alert alert-secondary small mb-3" role="status">
Connect a peer to manage tunnels. Holesail is on by default (set <code>ENABLE_HOLESAIL=0</code> on the server to disable).
</div>
<div class="row g-3">
<div class="col-lg-5">
<div class="settings-section">
<h3>Create tunnel</h3>
<p class="small text-muted">Exposes a host port via an <code>hs://</code> key. Anyone with the key can reach that service through Holesail.</p>
<div class="mb-2">
<label class="form-label" for="tunnel-name">Name</label>
<input type="text" id="tunnel-name" class="form-control bg-dark text-white" placeholder="web-ui" maxlength="80">
</div>
<div class="row g-2 mb-2">
<div class="col-7">
<label class="form-label" for="tunnel-host">Host</label>
<input type="text" id="tunnel-host" class="form-control bg-dark text-white font-monospace" value="127.0.0.1" spellcheck="false">
</div>
<div class="col-5">
<label class="form-label" for="tunnel-port">Port</label>
<input type="number" id="tunnel-port" class="form-control bg-dark text-white" min="1" max="65535" placeholder="8080">
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label class="form-label" for="tunnel-protocol">Protocol</label>
<select id="tunnel-protocol" class="form-select bg-dark text-white">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
<div class="col-6 d-flex align-items-end">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="tunnel-secure" checked>
<label class="form-check-label" for="tunnel-secure">Secure key</label>
</div>
</div>
</div>
<button type="button" class="btn btn-primary" id="tunnel-create-btn" data-min-role="operator">
<i class="fas fa-plus me-1"></i>Create tunnel
</button>
</div>
</div>
<div class="col-lg-7">
<div class="settings-section">
<h3>Active tunnels</h3>
<div id="tunnels-list" class="list-group list-group-flush">
<div class="text-muted small p-2">No tunnels yet</div>
</div>
</div>
<div class="settings-section mt-3">
<h3>How to connect</h3>
<ol class="small text-muted mb-0 ps-3">
<li>Create a tunnel to a published container port (or any allowed host port).</li>
<li>Copy the <code>hs://</code> URL.</li>
<li>On another machine: <code>npx holesail &lt;url&gt;</code> (binds a local port).</li>
<li>Open <code>http://127.0.0.1:&lt;local-port&gt;</code> for HTTP services.</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<!-- Settings -->
<div id="settings-view" class="view hidden">
<div class="container-fluid settings-page">
<div class="settings-page-scroll">
<div class="page-header">
<h2><i class="fas fa-gear"></i>Settings</h2>
<p class="page-subtitle">Client preferences, backup &amp; restore, catalogs, connections, and about</p>
</div>
<ul class="nav nav-pills settings-subtabs flex-wrap gap-1 mb-3" id="settings-tabs" role="tablist">
<li class="nav-item" role="presentation">
<button type="button" class="nav-link active" data-settings-tab="appearance" role="tab">Appearance</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="behavior" role="tab">Behavior</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="jobs" role="tab">Jobs tray</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="alerts" role="tab">Alerts &amp; Notifications</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="tunnels" role="tab">Tunnels</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="templates" role="tab">Templates</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="peers" role="tab">Peers</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="connections" role="tab">Connections</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="terminal" role="tab">Terminal</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="backup" role="tab">Backup</button>
</li>
<li class="nav-item" role="presentation">
<button type="button" class="nav-link" data-settings-tab="about" role="tab">About</button>
</li>
</ul>
<!-- Appearance -->
<div class="settings-panel" id="settings-panel-appearance" data-settings-panel="appearance">
<div class="settings-section">
<h3>Theme</h3>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-theme">Color mode</label>
<select id="settings-theme" class="form-select bg-dark text-white">
<option value="dark" selected>Dark (default)</option>
<option value="light">Light</option>
<option value="system">Match system</option>
</select>
<div class="form-text">Dark is the default. Light uses a professional cool-slate palette. Match system follows your OS setting.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-chrome">Chrome surface</label>
<select id="settings-chrome" class="form-select bg-dark text-white">
<option value="glass" selected>Glass (blur)</option>
<option value="solid">Solid</option>
<option value="flat">Flat minimal</option>
</select>
<div class="form-text">Titlebar and sidebar material.</div>
</div>
</div>
</div>
<div class="settings-section mt-4">
<h3>Accent color</h3>
<p class="text-muted small mb-2">Highlights, focus rings, active nav, and progress bars. Applies immediately.</p>
<div id="settings-accent-swatches" class="pd-accent-swatches" aria-label="Accent color"></div>
<label class="form-label visually-hidden" for="settings-accent">Accent color</label>
<select id="settings-accent" class="form-select bg-dark text-white mt-2 pd-accent-select">
<option value="teal" selected>Teal (default)</option>
<option value="cyan">Cyan</option>
<option value="emerald">Emerald</option>
<option value="blue">Blue</option>
<option value="indigo">Indigo</option>
<option value="violet">Violet</option>
<option value="fuchsia">Fuchsia</option>
<option value="rose">Rose</option>
<option value="amber">Amber</option>
<option value="orange">Orange</option>
<option value="sky">Sky</option>
<option value="lime">Lime</option>
</select>
<div class="form-text">Use the swatches or the list — both stay in sync.</div>
</div>
<div class="settings-section mt-4">
<h3>Layout &amp; density</h3>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-density">Table density</label>
<select id="settings-density" class="form-select bg-dark text-white">
<option value="comfortable">Comfortable</option>
<option value="compact">Compact</option>
<option value="spacious">Spacious</option>
</select>
<div class="form-text">Row and card spacing for dense fleets or touch targets.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-ui-scale">UI scale</label>
<select id="settings-ui-scale" class="form-select bg-dark text-white">
<option value="sm">Compact</option>
<option value="md" selected>Default</option>
<option value="lg">Large</option>
</select>
<div class="form-text">Base type size across the app.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-radius">Corner radius</label>
<select id="settings-radius" class="form-select bg-dark text-white">
<option value="sharp">Sharp</option>
<option value="default" selected>Default</option>
<option value="round">Round</option>
</select>
<div class="form-text">Cards, buttons, and inputs.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-sidebar">Sidebar</label>
<select id="settings-sidebar" class="form-select bg-dark text-white">
<option value="expanded">Expanded by default</option>
<option value="collapsed">Collapsed rail by default</option>
</select>
<div class="form-text">Applied on launch and when changed.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-reduce-motion">Motion</label>
<select id="settings-reduce-motion" class="form-select bg-dark text-white">
<option value="0">Allow subtle animation</option>
<option value="1">Reduce motion</option>
</select>
<div class="form-text">Also respects OS “prefers-reduced-motion”.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-refresh-stamp">List refresh stamp</label>
<select id="settings-refresh-stamp" class="form-select bg-dark text-white">
<option value="1">Show “Updated …” time</option>
<option value="0">Hide</option>
</select>
<div class="form-text">Appears on list headers after refresh / auto-poll.</div>
</div>
</div>
</div>
</div>
<!-- Behavior -->
<div class="settings-panel hidden" id="settings-panel-behavior" data-settings-panel="behavior">
<div class="settings-section">
<h3>Safety &amp; refresh</h3>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-confirm">Confirm destructive actions</label>
<select id="settings-confirm" class="form-select bg-dark text-white">
<option value="1">Always (recommended)</option>
<option value="0">Skip (not recommended)</option>
</select>
<div class="form-text">Stop / remove / prune and similar actions.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-refresh">Auto-refresh lists (seconds)</label>
<input type="number" id="settings-refresh" class="form-control bg-dark text-white" min="0" max="120" value="10">
<div class="form-text">0 disables. Polls containers, images, host, tunnels, etc.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-reconnect-max">Max reconnect tries</label>
<select id="settings-reconnect-max" class="form-select bg-dark text-white">
<option value="0">Unlimited</option>
<option value="1">1 try</option>
<option value="2">2 tries</option>
<option value="3">3 tries</option>
<option value="4">4 tries</option>
<option value="5">5 tries</option>
<option value="6">6 tries</option>
<option value="7">7 tries</option>
<option value="8" selected>8 tries (max)</option>
</select>
<div class="form-text">After the limit, auto-retry stops until you use Fleet → Reconnect. Cap is 8. Feedback volume is under <strong>Jobs tray</strong>.</div>
</div>
</div>
</div>
<div class="settings-section mt-4">
<h3>Hidden containers</h3>
<p class="text-muted small mb-3">
Hide containers from the UI when they have matching labels. Applies globally (Containers list, dashboard counts, bulk selection).
Leave value empty to match any value for that label key.
</p>
<ul id="settings-hidden-label-filters" class="list-group list-group-flush mb-2 settings-hidden-label-filters"></ul>
<div class="row g-2 align-items-end">
<div class="col-md-4">
<label class="form-label" for="settings-hidden-label-name">Label name</label>
<input type="text" id="settings-hidden-label-name" class="form-control bg-dark text-white font-monospace" placeholder="com.example.hide" spellcheck="false" autocomplete="off">
</div>
<div class="col-md-4">
<label class="form-label" for="settings-hidden-label-value">Label value <span class="text-muted fw-normal">(optional)</span></label>
<input type="text" id="settings-hidden-label-value" class="form-control bg-dark text-white font-monospace" placeholder="true" spellcheck="false" autocomplete="off">
</div>
<div class="col-md-4">
<button type="button" class="btn btn-outline-primary w-100" id="settings-hidden-label-add" title="Add hide filter">
<i class="fas fa-plus me-1"></i>Add filter
</button>
</div>
</div>
<div class="form-text mt-2">Applied immediately. Matching containers disappear from lists until the filter is removed.</div>
</div>
</div>
<!-- Jobs tray -->
<div class="settings-panel hidden" id="settings-panel-jobs" data-settings-panel="jobs">
<div class="settings-section">
<h3>Bottom job drawer</h3>
<p class="small text-muted mb-3">
Long Docker ops (deploy, pull, compose) always use the floating Jobs tray bottom-right.
Configure how long cards stay open and whether short status lines open the tray.
</p>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-job-dismiss">Success auto-dismiss (ms)</label>
<input type="number" id="settings-job-dismiss" class="form-control bg-dark text-white" min="0" max="60000" step="500" value="3000">
<div class="form-text">How long a successful job card stays. <code>0</code> = keep until the next job.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-job-error-dismiss">Error auto-dismiss (ms)</label>
<input type="number" id="settings-job-error-dismiss" class="form-control bg-dark text-white" min="0" max="120000" step="500" value="0">
<div class="form-text">Failed cards stay by default. Set a delay to auto-hide errors too.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-job-activity">Short status activities</label>
<select id="settings-job-activity" class="form-select bg-dark text-white">
<option value="1" selected>Show in Jobs tray (default)</option>
<option value="0">Bell only (no floating drawer)</option>
</select>
<div class="form-text">Prune, delete, rename, and similar one-line progress.</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-job-bell">Also log job results to bell</label>
<select id="settings-job-bell" class="form-select bg-dark text-white">
<option value="0" selected>No (tray only)</option>
<option value="1">Yes — copy success/failure into notifications</option>
</select>
<div class="form-text">Useful if you auto-dismiss the tray quickly and want history.</div>
</div>
</div>
</div>
<div class="settings-section mt-4">
<h3>Connection recovery feedback</h3>
<p class="small text-muted mb-3">
When a peer drops, the client still redials in the background (see Behavior → max tries).
These options only control how noisy that process is — Fleet status always updates.
</p>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="settings-reconnect-feedback">Reconnect feedback level</label>
<select id="settings-reconnect-feedback" class="form-select bg-dark text-white">
<option value="silent">Silent — background only, no tray or bell</option>
<option value="minimal" selected>Minimal — no huge tray; bell on first loss &amp; final failure</option>
<option value="standard">Standard — Jobs tray “Reconnecting…” + periodic notices</option>
</select>
<div class="form-text">Minimal is recommended: retries stay quiet while you work.</div>
</div>
<div class="col-md-3">
<label class="form-label" for="settings-reconnect-notify-success">Notify when reconnected</label>
<select id="settings-reconnect-notify-success" class="form-select bg-dark text-white">
<option value="1" selected>Yes</option>
<option value="0">No</option>
</select>
<div class="form-text">Ignored when feedback is Silent.</div>
</div>
<div class="col-md-3">
<label class="form-label" for="settings-reconnect-notify-failure">Notify when max tries fail</label>
<select id="settings-reconnect-notify-failure" class="form-select bg-dark text-white">
<option value="1" selected>Yes</option>
<option value="0">No</option>
</select>
<div class="form-text">Ignored when feedback is Silent.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="settings-launch-connect-notify">Notify launch connect failures</label>
<select id="settings-launch-connect-notify" class="form-select bg-dark text-white">
<option value="1" selected>Yes — bell for each peer that fails at startup</option>
<option value="0">No — fail quietly (Fleet still shows offline)</option>
</select>
<div class="form-text">Launch never blocks on offline peers; this only controls the bell.</div>
</div>
</div>
</div>
</div>
<!-- Alerts & Notifications (local bell + server webhooks) -->
<div class="settings-panel hidden" id="settings-panel-alerts" data-settings-panel="alerts">
<div class="settings-section">
<h3>Local bell &amp; history</h3>
<p class="small text-muted mb-3">In-app notification center on this client (not webhooks).</p>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="settings-badge-mode">Bell badge</label>
<select id="settings-badge-mode" class="form-select bg-dark text-white">
<option value="alerts">Alerts only (danger &amp; warning)</option>
<option value="all">All unread notifications</option>
<option value="none">Never show a badge</option>
</select>
<div class="form-text">All items still appear in the notification center.</div>
</div>
<div class="col-md-6">
<label class="form-label" for="settings-force-toast">Top-center toasts</label>
<select id="settings-force-toast" class="form-select bg-dark text-white">
<option value="0">Off (default — job tray / bell only)</option>
<option value="1">On for success &amp; errors</option>
</select>
<div class="form-text">Usually leave off to avoid double feedback.</div>
</div>
</div>
<div class="mt-3 d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="settings-notif-mark-read">
Mark all notifications read
</button>
<button type="button" class="btn btn-outline-danger btn-sm" id="settings-notif-clear">
Clear notification history
</button>
</div>
</div>
<div class="settings-section mt-4">
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
<div>
<h3 class="mb-0">Server alerts &amp; webhooks <span class="badge text-bg-secondary">Experimental</span></h3>
<p class="small text-muted mb-0">
<strong>Experimental.</strong> Runs on the PearDock <strong>server</strong> — Discord, Slack, Teams, ntfy, Gotify, Telegram, or generic HTTPS.
Fires even when this UI is closed. Requires an admin session. Channels, rules, and secrets are backed up in client packages when included.
</p>
</div>
<div class="d-flex align-items-center gap-2">
<span id="alerts-engine-status" class="small text-muted"></span>
<button type="button" class="btn btn-outline-secondary btn-sm" id="alerts-refresh" title="Reload from server">
<i class="fas fa-sync-alt"></i>
</button>
</div>
</div>
<div id="alerts-offline-hint" class="alert alert-warning py-2 small hidden">
Connect to a server to configure webhook channels and rules.
</div>
<p class="small text-muted mb-2">Changes apply live on the server — no restart and no Save button.</p>
<div class="row g-3 mt-1">
<div class="col-md-3">
<label class="form-label" for="alerts-enabled">Alert engine</label>
<select id="alerts-enabled" class="form-select bg-dark text-white alerts-live-field" data-min-role="admin">
<option value="1">Enabled</option>
<option value="0">Disabled</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="alerts-min-severity">Global min severity</label>
<select id="alerts-min-severity" class="form-select bg-dark text-white alerts-live-field" data-min-role="admin">
<option value="info">Info+</option>
<option value="warning">Warning+</option>
<option value="critical">Critical only</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="alerts-poll-ms">Health poll (sec)</label>
<input type="number" id="alerts-poll-ms" class="form-control bg-dark text-white alerts-live-field" data-min-role="admin" min="15" max="3600" value="60">
</div>
<div class="col-md-2">
<label class="form-label" for="alerts-rate-limit">Max / minute</label>
<input type="number" id="alerts-rate-limit" class="form-control bg-dark text-white alerts-live-field" data-min-role="admin" min="1" max="300" value="40">
</div>
<div class="col-md-2">
<label class="form-label" for="alerts-include-hostname">Include hostname</label>
<select id="alerts-include-hostname" class="form-select bg-dark text-white alerts-live-field" data-min-role="admin">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="alerts-quiet-enabled">Quiet hours</label>
<select id="alerts-quiet-enabled" class="form-select bg-dark text-white alerts-live-field" data-min-role="admin">
<option value="0">Off</option>
<option value="1">On (critical still fires)</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="alerts-quiet-start">Quiet start</label>
<input type="text" id="alerts-quiet-start" class="form-control bg-dark text-white alerts-live-field" data-min-role="admin" placeholder="22:00" value="22:00">
</div>
<div class="col-md-2">
<label class="form-label" for="alerts-quiet-end">Quiet end</label>
<input type="text" id="alerts-quiet-end" class="form-control bg-dark text-white alerts-live-field" data-min-role="admin" placeholder="07:00" value="07:00">
</div>
</div>
<div id="alerts-status" class="small text-muted mt-2" role="status" aria-live="polite"></div>
</div>
<div class="settings-section mt-4">
<h3>Webhook channels</h3>
<p class="small text-muted">Delivery targets. Secrets are stored only on the server (redacted in the UI after save).</p>
<div id="alerts-channels-list" class="list-group mb-3"></div>
<div class="card bg-dark border-secondary">
<div class="card-body">
<h4 class="h6 card-title">Add / edit channel <span class="small text-muted fw-normal">(auto-applies)</span></h4>
<input type="hidden" id="alert-ch-id" value="">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label" for="alert-ch-name">Name</label>
<input type="text" id="alert-ch-name" class="form-control bg-dark text-white" placeholder="Ops Discord">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-ch-type">Type</label>
<select id="alert-ch-type" class="form-select bg-dark text-white">
<option value="discord">Discord</option>
<option value="slack">Slack</option>
<option value="teams">Microsoft Teams</option>
<option value="generic">Generic webhook</option>
<option value="ntfy">ntfy</option>
<option value="gotify">Gotify</option>
<option value="telegram">Telegram</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="alert-ch-enabled">Enabled</label>
<select id="alert-ch-enabled" class="form-select bg-dark text-white">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="alert-ch-min-sev">Min severity</label>
<select id="alert-ch-min-sev" class="form-select bg-dark text-white">
<option value="info">Info+</option>
<option value="warning">Warning+</option>
<option value="critical">Critical only</option>
</select>
</div>
<div class="col-md-8">
<label class="form-label" for="alert-ch-url">Webhook / server URL</label>
<input type="url" id="alert-ch-url" class="form-control bg-dark text-white" placeholder="https://…">
<div class="form-text" id="alert-ch-url-help"></div>
</div>
<div class="col-md-4">
<label class="form-label" for="alert-ch-username">Display name</label>
<input type="text" id="alert-ch-username" class="form-control bg-dark text-white" value="PearDock">
</div>
<div class="col-md-4 hidden" id="alert-ch-token-row">
<label class="form-label" for="alert-ch-token">Token</label>
<input type="password" id="alert-ch-token" class="form-control bg-dark text-white" autocomplete="off">
</div>
<div class="col-md-4 hidden" id="alert-ch-topic-row">
<label class="form-label" for="alert-ch-topic">ntfy topic</label>
<input type="text" id="alert-ch-topic" class="form-control bg-dark text-white" placeholder="peardock">
</div>
<div class="col-12 hidden" id="alert-ch-telegram-row">
<div class="row g-2">
<div class="col-md-6">
<label class="form-label" for="alert-ch-bot-token">Telegram bot token</label>
<input type="password" id="alert-ch-bot-token" class="form-control bg-dark text-white" autocomplete="off">
</div>
<div class="col-md-6">
<label class="form-label" for="alert-ch-chat-id">Telegram chat ID</label>
<input type="text" id="alert-ch-chat-id" class="form-control bg-dark text-white">
</div>
</div>
</div>
</div>
<div class="mt-3 d-flex gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="alert-ch-reset">Clear form</button>
</div>
</div>
</div>
</div>
<div class="settings-section mt-4">
<h3>Alert rules</h3>
<p class="small text-muted">
What triggers a notification. Built-in rules cover container die/OOM, healthchecks, Docker daemon down,
compose stack degradation, restarts, and optional disk pressure. Fully editable.
</p>
<div id="alerts-rules-list" class="list-group mb-3"></div>
<div class="card bg-dark border-secondary">
<div class="card-body">
<h4 class="h6 card-title">Add / edit rule <span class="small text-muted fw-normal">(auto-applies)</span></h4>
<input type="hidden" id="alert-rule-id" value="">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label" for="alert-rule-name">Name</label>
<input type="text" id="alert-rule-name" class="form-control bg-dark text-white" placeholder="Container died">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-kind">Kind</label>
<select id="alert-rule-kind" class="form-select bg-dark text-white">
<option value="docker_event">Docker event</option>
<option value="container_health">Container health</option>
<option value="docker_daemon">Docker daemon</option>
<option value="stack_health">Stack / compose health</option>
<option value="resource">Host resource (disk)</option>
<option value="peardock">Manual / PearDock</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="alert-rule-severity">Severity</label>
<select id="alert-rule-severity" class="form-select bg-dark text-white">
<option value="info">Info</option>
<option value="warning">Warning</option>
<option value="critical">Critical</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-enabled">Enabled</label>
<select id="alert-rule-enabled" class="form-select bg-dark text-white">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-types">Event types</label>
<input type="text" id="alert-rule-types" class="form-control bg-dark text-white" placeholder="container" value="container">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-actions">Actions</label>
<input type="text" id="alert-rule-actions" class="form-control bg-dark text-white" placeholder="die, oom, kill">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-health">Health statuses</label>
<input type="text" id="alert-rule-health" class="form-control bg-dark text-white" placeholder="unhealthy">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-disk">Disk % threshold</label>
<input type="number" id="alert-rule-disk" class="form-control bg-dark text-white" min="1" max="100" placeholder="90">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-cooldown">Cooldown (sec)</label>
<input type="number" id="alert-rule-cooldown" class="form-control bg-dark text-white" min="0" value="300">
</div>
<div class="col-md-3">
<label class="form-label" for="alert-rule-recover">Notify on recover</label>
<select id="alert-rule-recover" class="form-select bg-dark text-white">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="alert-rule-name-regex">Name regex filter</label>
<input type="text" id="alert-rule-name-regex" class="form-control bg-dark text-white" placeholder="^prod-">
</div>
<div class="col-md-4">
<label class="form-label" for="alert-rule-name-include">Name must include</label>
<input type="text" id="alert-rule-name-include" class="form-control bg-dark text-white" placeholder="api, worker">
</div>
<div class="col-md-4">
<label class="form-label" for="alert-rule-name-exclude">Name must exclude</label>
<input type="text" id="alert-rule-name-exclude" class="form-control bg-dark text-white" placeholder="dev-, test-">
</div>
</div>
<div class="mt-3 d-flex gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="alert-rule-reset">Clear form</button>
</div>
</div>
</div>
</div>
<div class="settings-section mt-4">
<h3>Recent deliveries</h3>
<p class="small text-muted">Last alerts fired by this server (success and failures).</p>
<div id="alerts-history-list" class="list-group" style="max-height: 22rem; overflow: auto;"></div>
</div>
</div>
<!-- Tunnels -->
<div class="settings-panel hidden" id="settings-panel-tunnels" data-settings-panel="tunnels">
<div class="settings-section">
<h3>Holesail client defaults</h3>
<p class="small text-muted mb-3">
Client-side defaults for local connect. Server tunnels are controlled by the peer
(<code>ENABLE_HOLESAIL</code>, allowlist, max tunnels).
</p>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-tunnel-open-browser">Open browser on local connect</label>
<select id="settings-tunnel-open-browser" class="form-select bg-dark text-white">
<option value="1">Yes</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-tunnel-offer-local">Offer local connect after create</label>
<select id="settings-tunnel-offer-local" class="form-select bg-dark text-white">
<option value="1">Yes (modal)</option>
<option value="0">No</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-tunnel-secure">Default secure tunnel</label>
<select id="settings-tunnel-secure" class="form-select bg-dark text-white">
<option value="1">Secure (hs://s000…)</option>
<option value="0">Open (hs://0000…)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-tunnel-protocol">Default protocol</label>
<select id="settings-tunnel-protocol" class="form-select bg-dark text-white">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</div>
</div>
</div>
</div>
<!-- Templates -->
<div class="settings-panel hidden" id="settings-panel-templates" data-settings-panel="templates">
<div class="settings-section">
<h3>Deploy template lists</h3>
<p class="small text-muted mb-2">
format JSON catalogs (<code>templates.json</code>). Lists are merged and de-duplicated
(same image / stack repo / title).
</p>
<ul id="settings-template-urls" class="list-group list-group-flush mb-2 settings-template-urls"></ul>
<div class="input-group input-group-sm mb-2">
<input type="url" id="settings-template-url-input" class="form-control bg-dark text-white font-monospace" placeholder="https://example.com/templates.json" spellcheck="false" autocomplete="off">
<button type="button" class="btn btn-outline-primary" id="settings-template-url-add" title="Add list URL">
<i class="fas fa-plus me-1"></i>Add
</button>
</div>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" id="settings-template-url-reset" title="Restore default catalog">
Restore default
</button>
<button type="button" class="btn btn-outline-info btn-sm" id="settings-template-reload" title="Fetch all lists now">
<i class="fas fa-sync me-1"></i>Reload templates
</button>
</div>
<div id="settings-template-lists-status" class="small text-muted mt-2"></div>
</div>
</div>
<!-- Peers (client-local HyperDHT connections) -->
<div class="settings-panel hidden" id="settings-panel-peers" data-settings-panel="peers">
<div class="settings-section">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
<div>
<h3 class="mb-1">Saved peers</h3>
<p class="small text-muted mb-0">HyperDHT public keys for this client — click a peer to make it active.</p>
</div>
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn-primary btn-sm" id="peers-view-add-btn">
<i class="fas fa-plus me-1"></i>Add peer
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="peers-view-refresh-btn" title="Refresh status">
<i class="fas fa-sync"></i>
</button>
</div>
</div>
<div class="d-flex justify-content-between align-items-center mb-2 flex-wrap gap-2">
<span id="peers-view-count" class="small text-muted">0 peers</span>
</div>
<ul id="connection-list" class="list-group peers-connection-list"></ul>
<div id="peers-view-empty" class="text-muted small py-3 d-none">
No peers saved yet. Use <strong>Add peer</strong> to paste a 64-character public key from <code>npm run server</code>.
</div>
</div>
<div class="settings-section">
<h3 class="h6">Tips</h3>
<ul class="small text-muted mb-0">
<li>The active peer is highlighted and drives dashboard, containers, and tunnels.</li>
<li>Use <strong>Fleet</strong> for multi-host health side-by-side.</li>
<li>Server-side allowlist / invites live under <strong>Access</strong> when connected as admin.</li>
</ul>
</div>
<div class="settings-section">
<h3>Reset</h3>
<p class="small text-muted mb-2">
Clear all configured peers from this client. You will need to re-add public keys to connect again.
</p>
<button type="button" id="settings-reset-peers-btn" class="btn btn-outline-danger btn-sm">
<i class="fas fa-trash-can me-1"></i>Reset all peers
</button>
</div>
</div>
<!-- Connections -->
<div class="settings-panel hidden" id="settings-panel-connections" data-settings-panel="connections">
<div class="settings-section">
<h3>Active peer</h3>
<div id="settings-peer-info" class="small text-muted">Not connected</div>
<p class="small text-muted mt-2 mb-0">
Manage the full peer list under <button type="button" class="btn btn-link btn-sm p-0 align-baseline" onclick="window.peardockOps?.showSettingsTab?.('peers')">Settings → Peers</button>.
</p>
</div>
<div class="settings-section">
<h3>Server features</h3>
<p class="small text-muted mb-2">Read-only snapshot from the connected peer (env on the server).</p>
<div id="settings-server-features" class="small text-muted">Connect a peer to inspect feature flags.</div>
</div>
<div class="settings-section">
<h3>Onboarding</h3>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="settings-first-connect" checked>
<label class="form-check-label" for="settings-first-connect">Show first-connect tip after adding a peer</label>
</div>
</div>
</div>
<!-- Terminal -->
<div class="settings-panel hidden" id="settings-panel-terminal" data-settings-panel="terminal">
<div class="settings-section">
<h3>Terminal themes</h3>
<p class="small text-muted mb-3">Defaults for container exec / docker terminals in this client.</p>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" for="settings-term-theme">Container terminal theme</label>
<select id="settings-term-theme" class="form-select bg-dark text-white">
<option value="auto" selected>Auto (match UI color mode)</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="monokai">Monokai</option>
<option value="solarized-dark">Solarized dark</option>
<option value="solarized-light">Solarized light</option>
</select>
<div class="form-text">Auto tracks Appearance → Color mode (including light).</div>
</div>
<div class="col-md-4">
<label class="form-label" for="settings-docker-term-theme">Docker CLI terminal theme</label>
<select id="settings-docker-term-theme" class="form-select bg-dark text-white">
<option value="auto" selected>Auto (match UI color mode)</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="monokai">Monokai</option>
<option value="solarized-dark">Solarized dark</option>
<option value="solarized-light">Solarized light</option>
</select>
</div>
</div>
</div>
</div>
<!-- Backup & restore -->
<div class="settings-panel hidden" id="settings-panel-backup" data-settings-panel="backup">
<div class="settings-section">
<h3>Backup &amp; restore</h3>
<p class="small text-muted mb-3">
Package client preferences, saved peers, templates, and related local data into a
portable JSON archive. Backups may include peer public keys and admin seeds — store them securely.
</p>
<div class="row g-3">
<div class="col-md-8">
<label class="form-label" for="settings-backup-label">Label <span class="text-muted fw-normal">(optional)</span></label>
<input type="text" id="settings-backup-label" class="form-control bg-dark text-white" maxlength="120" placeholder="e.g. before-migrate" autocomplete="off" spellcheck="false">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="settings-backup-pin">
<label class="form-check-label" for="settings-backup-pin">Pin (skip rotation)</label>
</div>
</div>
</div>
<div class="mt-3">
<div class="form-label mb-2">Include in package</div>
<div class="row g-2 settings-backup-include" id="settings-backup-include">
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-settings" data-backup-section="settings" checked>
<label class="form-check-label" for="settings-backup-inc-settings">Settings &amp; preferences</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-peers" data-backup-section="peers" checked>
<label class="form-check-label" for="settings-backup-inc-peers">Saved peers</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-templates" data-backup-section="templates" checked>
<label class="form-check-label" for="settings-backup-inc-templates">Saved templates</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-tableColumns" data-backup-section="tableColumns" checked>
<label class="form-check-label" for="settings-backup-inc-tableColumns">Table column layouts</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-peerEnvs" data-backup-section="peerEnvs" checked>
<label class="form-check-label" for="settings-backup-inc-peerEnvs">Fleet peer environments</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-misc" data-backup-section="misc" checked>
<label class="form-check-label" for="settings-backup-inc-misc">UI flags (sidebar, tips)</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-notifications" data-backup-section="notifications" checked>
<label class="form-check-label" for="settings-backup-inc-notifications">Notification history</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-identity" data-backup-section="identity" checked>
<label class="form-check-label" for="settings-backup-inc-identity">Client identity (DHT seed)</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-backupPolicy" data-backup-section="backupPolicy" checked>
<label class="form-check-label" for="settings-backup-inc-backupPolicy">Backup retention policy</label>
</div>
</div>
<div class="col-md-4 col-sm-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="settings-backup-inc-alerts" data-backup-section="alerts" checked>
<label class="form-check-label" for="settings-backup-inc-alerts">Server alerts &amp; webhooks</label>
</div>
</div>
</div>
<p class="form-text text-muted small mt-2 mb-0">
Packages include settings, peers, templates, notifications, alerts/webhooks (channels, rules, secrets),
and optionally the client DHT identity seed. Store backup files securely.
</p>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<button type="button" class="btn btn-primary btn-sm" id="settings-backup-create-btn">
<i class="fas fa-box-archive me-1"></i>Create backup
</button>
<button type="button" class="btn btn-outline-primary btn-sm" id="settings-backup-download-btn" title="Create and download a packaged .json archive">
<i class="fas fa-download me-1"></i>Download package
</button>
<button type="button" class="btn btn-outline-secondary btn-sm" id="settings-backup-restore-file-btn">
<i class="fas fa-upload me-1"></i>Restore from file…
</button>
<input type="file" id="settings-backup-file-input" class="d-none" accept=".json,application/json">
</div>
<div id="settings-backup-status" class="small text-muted mt-2" role="status" aria-live="polite"></div>
</div>
<div class="settings-section mt-4">
<h3>Retention &amp; auto rotation</h3>
<p class="small text-muted mb-3">
Local history is rotated automatically when you create a backup. Pinned backups are never removed by age or count limits.
Retention and auto-backup options apply as soon as you change them.
</p>
<div class="row g-3">
<div class="col-md-3">
<label class="form-label" for="settings-backup-max">Max backups</label>
<input type="number" id="settings-backup-max" class="form-control bg-dark text-white" min="0" max="50" value="10">
<div class="form-text">0 = soft cap (50). Oldest unpinned drop first.</div>
</div>
<div class="col-md-3">
<label class="form-label" for="settings-backup-age">Max age (days)</label>
<input type="number" id="settings-backup-age" class="form-control bg-dark text-white" min="0" max="3650" value="90">
<div class="form-text">0 = no age pruning. Pinned exempt.</div>
</div>
<div class="col-md-3">
<label class="form-label" for="settings-backup-auto">Auto backup</label>
<select id="settings-backup-auto" class="form-select bg-dark text-white">
<option value="0">Off</option>
<option value="1">On</option>
</select>
<div class="form-text">Creates a local package on an interval.</div>
</div>
<div class="col-md-3">
<label class="form-label" for="settings-backup-interval">Interval (hours)</label>
<input type="number" id="settings-backup-interval" class="form-control bg-dark text-white" min="1" max="720" value="24">
<div class="form-text">Checked while the client is open.</div>
</div>
</div>
<div class="d-flex flex-wrap gap-2 mt-3">
<button type="button" class="btn btn-outline-secondary btn-sm" id="settings-backup-rotate-btn" title="Apply retention rules now">
<i class="fas fa-arrows-rotate me-1"></i>Rotate now
</button>
<button type="button" class="btn btn-outline-danger btn-sm" id="settings-backup-clear-history-btn">
<i class="fas fa-trash-can me-1"></i>Clear history
</button>
</div>
<div id="settings-backup-policy-summary" class="small text-muted mt-2"></div>
</div>
<div class="settings-section mt-4">
<div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-2">
<h3 class="mb-0">Local history</h3>
<button type="button" class="btn btn-outline-secondary btn-sm" id="settings-backup-refresh-btn" title="Refresh list">
<i class="fas fa-sync"></i>
</button>
</div>
<div id="settings-backup-history-empty" class="small text-muted py-2 d-none">
No local backups yet. Create one or restore from a downloaded package.
</div>
<ul id="settings-backup-history" class="list-group list-group-flush settings-backup-history"></ul>
</div>
<div class="settings-section mt-4">
<h3 class="h6">Restore mode</h3>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" for="settings-backup-restore-mode">When restoring</label>
<select id="settings-backup-restore-mode" class="form-select bg-dark text-white">
<option value="replace">Replace (overwrite selected sections)</option>
<option value="merge">Merge (keep existing keys, package wins on conflict)</option>
</select>
</div>
</div>
<p class="small text-muted mt-2 mb-0">
After a restore that includes peers or settings, reconnect peers and re-open views if the UI looks stale.
</p>
</div>
</div>
<!-- About -->
<div class="settings-panel hidden" id="settings-panel-about" data-settings-panel="about">
<div class="settings-section">
<h3>About peardock</h3>
<p class="small text-muted mb-1">Decentralized Docker control plane over HyperDHT + protomux-rpc.</p>
<p class="small text-muted mb-1">Version: <code id="settings-app-version">2.0.1</code> · License: AGPL-3.0</p>
<p class="small text-muted mb-3">PearDock and Holesail tunnels are licensed under AGPL-3.0.</p>
<h4 class="h6 text-white">Publisher</h4>
<p class="small text-muted mb-1">
Owned and engineered by <strong class="text-white">HoneyPeer, LLC</strong>
(DeKalb County, Georgia, USA).
</p>
<p class="small text-muted mb-1">
Legal:
<a href="mailto:[email protected]" class="link-info">[email protected]</a>
</p>
<p class="small text-muted mb-3">
Lead developer:
<a href="https://raven-scott.fyi" target="_blank" rel="noopener noreferrer" class="link-info">Raven Scott</a>
·
<a href="https://raven-scott.fyi" target="_blank" rel="noopener noreferrer" class="link-info">raven-scott.fyi</a>
</p>
<h4 class="h6 text-white">Keyboard</h4>
<ul class="small text-muted mb-2">
<li><kbd>Ctrl</kbd>/<kbd></kbd>+<kbd>K</kbd> — command palette</li>
<li><kbd>?</kbd> — shortcuts help</li>
<li><kbd>/</kbd> — focus list filter</li>
<li><kbd>g</kbd> then a letter — jump to a view</li>
</ul>
<button type="button" class="btn btn-outline-secondary btn-sm mb-0" id="settings-open-shortcuts">
<i class="fas fa-keyboard me-1"></i>Open shortcuts cheatsheet
</button>
</div>
</div>
</div><!-- /.settings-page-scroll -->
<div class="settings-footer" role="status" aria-live="polite" aria-label="Settings save status">
<div id="settings-live-status" class="settings-live-status small text-muted">
<i class="fas fa-bolt me-1" aria-hidden="true"></i>Changes apply automatically
</div>
</div>
</div>
</div>
</div>
<div id="job-drawer" aria-live="polite"></div>
<!-- Create Network Modal (smart defaults) -->
<div class="modal fade" id="createNetworkSmartModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title">Create network</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name <span class="pd-suggested" id="net-name-badge" style="display:none">Suggested</span></label>
<input type="text" id="smart-net-name" class="form-control bg-dark text-white" required>
</div>
<div class="mb-3">
<label class="form-label">Driver</label>
<select id="smart-net-driver" class="form-select bg-dark text-white">
<option value="bridge">bridge</option>
<option value="overlay">overlay</option>
<option value="macvlan">macvlan</option>
<option value="ipvlan">ipvlan</option>
<option value="host">host</option>
<option value="null">null</option>
</select>
</div>
<div class="row g-2">
<div class="col-md-7">
<label class="form-label">Subnet <span class="pd-suggested" id="net-subnet-badge">Suggested</span></label>
<input type="text" id="smart-net-subnet" class="form-control bg-dark text-white" placeholder="172.20.0.0/16">
</div>
<div class="col-md-5">
<label class="form-label">Gateway</label>
<input type="text" id="smart-net-gateway" class="form-control bg-dark text-white" placeholder="172.20.0.1">
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-info mt-2" id="smart-net-resuggest">Regenerate free subnet</button>
<div class="form-text form-hint-suggested mt-2">IPAM is auto-detected from existing networks so ranges do not collide.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="smart-net-create-btn">Create</button>
</div>
</div>
</div>
</div>
<!-- Duplicate Container Modal -->
<div class="modal fade" id="duplicateModal" tabindex="-1" aria-labelledby="duplicateModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="duplicateModalLabel">
<i class="fas fa-clone me-2"></i>Duplicate Container
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<form id="duplicate-container-form">
<!-- Basic Settings (Always Visible) -->
<div class="mb-4">
<h6 class="text-primary mb-3"><i class="fas fa-cog"></i> Basic Settings</h6>
<div class="row">
<div class="col-md-6 mb-3">
<label for="duplicate-container-name" class="form-label">Container Name <span class="text-danger">*</span></label>
<input type="text" id="duplicate-container-name" class="form-control bg-dark text-white" placeholder="my-container-copy" required>
<small class="text-muted">Defaults to a free name (original-copy). Reusing an existing name will offer Replace.</small>
</div>
<div class="col-md-6 mb-3">
<label for="duplicate-image" class="form-label">Image <span class="text-danger">*</span></label>
<input type="text" id="duplicate-image" class="form-control bg-dark text-white" placeholder="nginx:latest" required>
<small class="text-muted">Docker image name and tag</small>
</div>
</div>
<div class="row">
<div class="col-12 mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-always-pull" checked>
<label class="form-check-label" for="duplicate-always-pull">Always pull the image</label>
</div>
<small class="text-muted">When on, pull from the registry before create. When off, use a local image if present.</small>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="duplicate-command" class="form-label">Command Override</label>
<input type="text" id="duplicate-command" class="form-control bg-dark text-white" placeholder="Override default command">
<small class="text-muted">Override container CMD</small>
</div>
<div class="col-md-4 mb-3">
<label for="duplicate-entrypoint" class="form-label">Entrypoint Override</label>
<input type="text" id="duplicate-entrypoint" class="form-control bg-dark text-white" placeholder="Override default entrypoint">
<small class="text-muted">Override container ENTRYPOINT</small>
</div>
<div class="col-md-4 mb-3">
<label for="duplicate-workdir" class="form-label">Working Directory</label>
<input type="text" id="duplicate-workdir" class="form-control bg-dark text-white" placeholder="/app">
<small class="text-muted">Container working directory</small>
</div>
</div>
</div>
<!-- Accordion for Advanced Settings -->
<div class="accordion accordion-flush" id="duplicateAccordion">
<!-- Networking -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateNetworkingCollapse">
<i class="fas fa-network-wired me-2"></i> Networking
</button>
</h2>
<div id="duplicateNetworkingCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-network-mode" class="form-label">Network Mode</label>
<select id="duplicate-network-mode" class="form-select bg-dark text-white">
<option value="bridge">Bridge</option>
<option value="host">Host</option>
<option value="none">None</option>
<option value="container">Container</option>
</select>
</div>
<div class="col-md-6" id="duplicate-custom-network-container" style="display: none;">
<label for="duplicate-custom-network" class="form-label">Custom Network</label>
<input type="text" id="duplicate-custom-network" class="form-control bg-dark text-white" placeholder="network-name">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-hostname" class="form-label">Hostname</label>
<input type="text" id="duplicate-hostname" class="form-control bg-dark text-white" placeholder="container-hostname">
</div>
<div class="col-md-6">
<label for="duplicate-domainname" class="form-label">Domain Name</label>
<input type="text" id="duplicate-domainname" class="form-control bg-dark text-white" placeholder="example.com">
</div>
</div>
<div class="mb-3">
<label class="form-label">Port Mappings</label>
<div id="duplicate-ports-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicatePortMapping()">
<i class="fas fa-plus"></i> Add Port
</button>
</div>
<div class="mb-3">
<label class="form-label">DNS Servers</label>
<div id="duplicate-dns-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateDnsServer()">
<i class="fas fa-plus"></i> Add DNS Server
</button>
</div>
<div class="mb-3">
<label class="form-label">Extra Hosts</label>
<div id="duplicate-extra-hosts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateExtraHost()">
<i class="fas fa-plus"></i> Add Host
</button>
<small class="text-muted d-block mt-1">Format: hostname:ip (e.g., example.com:127.0.0.1)</small>
</div>
</div>
</div>
</div>
<!-- Volumes & Storage -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateVolumesCollapse">
<i class="fas fa-hdd me-2"></i> Volumes & Storage
</button>
</h2>
<div id="duplicateVolumesCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Volume Mounts</label>
<div id="duplicate-volumes-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateVolumeMount()">
<i class="fas fa-plus"></i> Add Volume
</button>
</div>
<div class="mb-3">
<label class="form-label">Tmpfs Mounts</label>
<div id="duplicate-tmpfs-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateTmpfsMount()">
<i class="fas fa-plus"></i> Add Tmpfs
</button>
<small class="text-muted d-block mt-1">Format: /path:size (e.g., /tmp:100m)</small>
</div>
</div>
</div>
</div>
<!-- Resources -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateResourcesCollapse">
<i class="fas fa-server me-2"></i> Resources
</button>
</h2>
<div id="duplicateResourcesCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-cpu-limit" class="form-label">CPU Limit: <strong id="duplicate-cpu-limit-val">Unlimited</strong></label>
<input type="range" class="form-range" id="duplicate-cpu-limit" min="0" max="16" step="0.1" value="0">
<small class="text-muted">0 = unlimited · cores</small>
</div>
<div class="col-md-6">
<label for="duplicate-memory-limit" class="form-label">Memory Limit: <strong id="duplicate-memory-limit-val">Unlimited</strong></label>
<input type="range" class="form-range" id="duplicate-memory-limit" min="0" max="16384" step="64" value="0">
<small class="text-muted">0 = unlimited · MB</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="duplicate-cpu-reservation" class="form-label">CPU Reservation</label>
<input type="number" id="duplicate-cpu-reservation" class="form-control bg-dark text-white" placeholder="1" step="0.1" min="0">
</div>
<div class="col-md-4">
<label for="duplicate-cpu-shares" class="form-label">CPU Shares</label>
<input type="number" id="duplicate-cpu-shares" class="form-control bg-dark text-white" placeholder="1024" min="0">
</div>
<div class="col-md-4">
<label for="duplicate-memory-reservation" class="form-label">Memory Reservation (MB)</label>
<input type="number" id="duplicate-memory-reservation" class="form-control bg-dark text-white" placeholder="256" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="duplicate-memory-swap" class="form-label">Memory Swap (MB)</label>
<input type="number" id="duplicate-memory-swap" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label class="form-label">Device Mappings</label>
<div id="duplicate-devices-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateDeviceMapping()">
<i class="fas fa-plus"></i> Add Device
</button>
<small class="text-muted d-block mt-1">Format: /host/device:/container/device:permissions</small>
</div>
</div>
</div>
</div>
<!-- Environment & Labels -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateEnvLabelsCollapse">
<i class="fas fa-tags me-2"></i> Environment & Labels
</button>
</h2>
<div id="duplicateEnvLabelsCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Environment Variables</label>
<div id="duplicate-env"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateEnvVar()">
<i class="fas fa-plus"></i> Add Variable
</button>
</div>
<div class="mb-3">
<label class="form-label">Labels</label>
<div id="duplicate-labels-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateLabel()">
<i class="fas fa-plus"></i> Add Label
</button>
<small class="text-muted d-block mt-1">Format: key=value</small>
</div>
</div>
</div>
</div>
<!-- Security -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateSecurityCollapse">
<i class="fas fa-shield-alt me-2"></i> Security
</button>
</h2>
<div id="duplicateSecurityCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-user" class="form-label">User/UID</label>
<input type="text" id="duplicate-user" class="form-control bg-dark text-white" placeholder="1000 or username">
</div>
<div class="col-md-6">
<label for="duplicate-group" class="form-label">Group/GID</label>
<input type="text" id="duplicate-group" class="form-control bg-dark text-white" placeholder="1000 or groupname">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-privileged">
<label class="form-check-label" for="duplicate-privileged">Privileged Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-readonly-rootfs">
<label class="form-check-label" for="duplicate-readonly-rootfs">Read-only Root Filesystem</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Capabilities</label>
<div id="duplicate-capabilities-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateCapability()">
<i class="fas fa-plus"></i> Add Capability
</button>
<small class="text-muted d-block mt-1">e.g., NET_ADMIN, SYS_ADMIN</small>
</div>
<div class="mb-3">
<label class="form-label">Security Options</label>
<div id="duplicate-security-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateSecurityOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
<small class="text-muted d-block mt-1">e.g., apparmor=profile, seccomp=unconfined</small>
</div>
</div>
</div>
</div>
<!-- Runtime & Behavior -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateRuntimeCollapse">
<i class="fas fa-play-circle me-2"></i> Runtime & Behavior
</button>
</h2>
<div id="duplicateRuntimeCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-restart-policy" class="form-label">Restart Policy</label>
<select id="duplicate-restart-policy" class="form-select bg-dark text-white">
<option value="no">No</option>
<option value="always">Always</option>
<option value="on-failure">On Failure</option>
<option value="unless-stopped">Unless Stopped</option>
</select>
</div>
<div class="col-md-6">
<label for="duplicate-restart-max-retries" class="form-label">Max Retries (on-failure)</label>
<input type="number" id="duplicate-restart-max-retries" class="form-control bg-dark text-white" placeholder="10" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-auto-remove">
<label class="form-check-label" for="duplicate-auto-remove">Auto Remove</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-tty">
<label class="form-check-label" for="duplicate-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-stdin-open">
<label class="form-check-label" for="duplicate-stdin-open">Interactive (Stdin Open)</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-detach" checked>
<label class="form-check-label" for="duplicate-detach">Detach Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-init">
<label class="form-check-label" for="duplicate-init">Init Process</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Health & Logging -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateHealthCollapse">
<i class="fas fa-heartbeat me-2"></i> Health & Logging
</button>
</h2>
<div id="duplicateHealthCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="duplicate-health-cmd" class="form-label">Health Check Command</label>
<input type="text" id="duplicate-health-cmd" class="form-control bg-dark text-white" placeholder="CMD-SHELL curl -f http://localhost/ || exit 1">
</div>
<div class="row mb-3">
<div class="col-md-3">
<label for="duplicate-health-interval" class="form-label">Interval (s)</label>
<input type="number" id="duplicate-health-interval" class="form-control bg-dark text-white" placeholder="30" min="1">
</div>
<div class="col-md-3">
<label for="duplicate-health-timeout" class="form-label">Timeout (s)</label>
<input type="number" id="duplicate-health-timeout" class="form-control bg-dark text-white" placeholder="10" min="1">
</div>
<div class="col-md-3">
<label for="duplicate-health-retries" class="form-label">Retries</label>
<input type="number" id="duplicate-health-retries" class="form-control bg-dark text-white" placeholder="3" min="0">
</div>
<div class="col-md-3">
<label for="duplicate-health-start-period" class="form-label">Start Period (s)</label>
<input type="number" id="duplicate-health-start-period" class="form-control bg-dark text-white" placeholder="0" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="duplicate-log-driver" class="form-label">Logging Driver</label>
<select id="duplicate-log-driver" class="form-select bg-dark text-white">
<option value="">Default</option>
<option value="json-file">JSON File</option>
<option value="syslog">Syslog</option>
<option value="journald">Journald</option>
<option value="gelf">GELF</option>
<option value="fluentd">Fluentd</option>
<option value="awslogs">AWS Logs</option>
<option value="none">None</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Logging Options</label>
<div id="duplicate-log-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateLogOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Advanced -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#duplicateAdvancedCollapse">
<i class="fas fa-cogs me-2"></i> Advanced
</button>
</h2>
<div id="duplicateAdvancedCollapse" class="accordion-collapse collapse" data-bs-parent="#duplicateAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Sysctls</label>
<div id="duplicate-sysctls-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateSysctl()">
<i class="fas fa-plus"></i> Add Sysctl
</button>
<small class="text-muted d-block mt-1">Format: key=value (e.g., net.ipv4.ip_forward=1)</small>
</div>
<div class="mb-3">
<label class="form-label">Ulimits</label>
<div id="duplicate-ulimits-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDuplicateUlimit()">
<i class="fas fa-plus"></i> Add Ulimit
</button>
<small class="text-muted d-block mt-1">Format: name=soft:hard (e.g., nofile=1024:2048)</small>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="duplicate-oom-kill-disable">
<label class="form-check-label" for="duplicate-oom-kill-disable">Disable OOM Killer</label>
</div>
</div>
<div class="col-md-6">
<label for="duplicate-pids-limit" class="form-label">PIDs Limit</label>
<input type="number" id="duplicate-pids-limit" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label for="duplicate-shm-size" class="form-label">Shared Memory Size (MB)</label>
<input type="number" id="duplicate-shm-size" class="form-control bg-dark text-white" placeholder="64" min="0">
</div>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-clone"></i> Duplicate Container
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- Terminal Modal -->
<div id="terminal-modal">
<div class="header">
<span id="terminal-title"></span>
<div>
<button id="kill-terminal-btn" class="btn btn-sm btn-danger">
<i class="fas fa-times-circle"></i>
</button>
</div>
</div>
<!-- Terminal Toolbar -->
<div class="terminal-toolbar" id="modal-terminal-toolbar">
<div class="terminal-toolbar-left">
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="modal-terminal-font-decrease" title="Decrease font size">
<i class="fas fa-minus"></i>
</button>
<span class="terminal-font-size" id="modal-terminal-font-size-display">14</span>
<button class="terminal-control-btn" id="modal-terminal-font-increase" title="Increase font size">
<i class="fas fa-plus"></i>
</button>
<button class="terminal-control-btn" id="modal-terminal-font-reset" title="Reset font size">
<i class="fas fa-undo"></i>
</button>
</div>
<div class="terminal-controls-divider"></div>
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="modal-terminal-copy-btn" title="Copy selection">
<i class="fas fa-copy"></i> Copy
</button>
<button class="terminal-control-btn" id="modal-terminal-clear-btn" title="Clear terminal">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</div>
<div class="terminal-toolbar-right">
<div class="terminal-controls-group">
<select class="terminal-theme-select" id="modal-terminal-theme-select" title="Select theme">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="solarized-dark">Solarized Dark</option>
<option value="solarized-light">Solarized Light</option>
<option value="monokai">Monokai</option>
</select>
</div>
</div>
</div>
<div id="terminal-container"></div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="deleteModalLabel">Confirm Deletion</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to delete this container?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" id="confirm-delete-btn" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
<!-- Add Peer / Connection Modal -->
<div class="modal fade" id="addConnectionModal" tabindex="-1" aria-labelledby="add-connection-modal-title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="add-connection-modal-title">
<i class="fas fa-plug me-2"></i>Add peer
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="add-connection-form" autocomplete="off">
<div class="modal-body">
<p class="text-muted small mb-3">
<strong>Public key only</strong> → read-only (viewer).<br>
<strong>Public key + SERVER_SEED</strong> → admin.<br>
<strong>pd1. invite</strong> → automatic connect with granted role (never needs the private seed).
</p>
<div class="mb-3">
<label for="new-connection-topic" class="form-label">Server public key or peardock invite</label>
<textarea
id="new-connection-topic"
class="form-control font-monospace invite-secret-full"
rows="3"
placeholder="64 hex public key, or pd1.… operator invite (paste entire string)…"
spellcheck="false"
required
autocomplete="off"
wrap="soft"
></textarea>
<div class="form-text text-muted">For operator access paste a full <code>pd1.</code> invite from an admin — not an old saved peer alone.</div>
</div>
<div class="mb-3">
<label for="new-connection-admin-seed" class="form-label">Admin private key <span class="text-muted">(SERVER_SEED — optional)</span></label>
<input type="password" id="new-connection-admin-seed" class="form-control font-monospace" placeholder="64 hex seed for full admin" spellcheck="false" autocomplete="off">
<div class="form-text text-muted">Saved in the local peer config (<code>~/.config/peardock/cache/peers.json</code>, mode 600) so reconnect stays admin. Never share this seed.</div>
</div>
<div class="mb-0">
<label for="new-connection-alias" class="form-label">Alias <span class="text-muted">(optional)</span></label>
<input type="text" id="new-connection-alias" class="form-control" placeholder="e.g. prod-host" spellcheck="false" maxlength="64" autocomplete="off">
</div>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plug me-1"></i> Connect
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Docker Terminal Modal -->
<div class="modal fade" id="dockerTerminalModal" tabindex="-1" aria-labelledby="docker-terminal-title" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 id="docker-terminal-title" class="modal-title">Docker CLI Terminal</h5>
<button id="docker-kill-terminal-btn" type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<!-- Docker Terminal Toolbar -->
<div class="terminal-toolbar" id="docker-terminal-toolbar">
<div class="terminal-toolbar-left">
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="docker-terminal-font-decrease" title="Decrease font size">
<i class="fas fa-minus"></i>
</button>
<span class="terminal-font-size" id="docker-terminal-font-size-display">14</span>
<button class="terminal-control-btn" id="docker-terminal-font-increase" title="Increase font size">
<i class="fas fa-plus"></i>
</button>
<button class="terminal-control-btn" id="docker-terminal-font-reset" title="Reset font size">
<i class="fas fa-undo"></i>
</button>
</div>
<div class="terminal-controls-divider"></div>
<div class="terminal-controls-group">
<button class="terminal-control-btn" id="docker-terminal-copy-btn" title="Copy selection">
<i class="fas fa-copy"></i> Copy
</button>
<button class="terminal-control-btn" id="docker-terminal-clear-btn" title="Clear terminal">
<i class="fas fa-trash"></i> Clear
</button>
</div>
</div>
<div class="terminal-toolbar-right">
<div class="terminal-controls-group">
<select class="terminal-theme-select" id="docker-terminal-theme-select" title="Select theme">
<option value="dark">Dark</option>
<option value="light">Light</option>
<option value="solarized-dark">Solarized Dark</option>
<option value="solarized-light">Solarized Light</option>
<option value="monokai">Monokai</option>
</select>
</div>
</div>
</div>
<div class="modal-body" style="padding: 0;">
<div id="docker-terminal-container" style="height: 500px;"></div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="logsModal" tabindex="-1" aria-labelledby="logsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="logsModalLabel">Container Logs</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="logs-container" style="max-height: 70vh; overflow-y: auto; font-family: monospace; background: black; padding: 10px;"></div>
</div>
</div>
</div>
</div>
<!-- Container Inspect Modal -->
<div class="modal fade" id="containerInspectModal" tabindex="-1" aria-labelledby="containerInspectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="containerInspectModalLabel">
<i class="fas fa-info-circle me-2"></i>Container Information
</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="toggle-json-view">
<i class="fas fa-code"></i> Raw JSON
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="copy-json-btn">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<!-- Formatted View -->
<div id="inspect-formatted-view">
<div class="accordion accordion-flush" id="inspectAccordion">
<!-- Overview Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectOverviewCollapse">
<i class="fas fa-info-circle me-2"></i> Overview
</button>
</h2>
<div id="inspectOverviewCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-overview-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Configuration Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectConfigCollapse">
<i class="fas fa-cog me-2"></i> Configuration
</button>
</h2>
<div id="inspectConfigCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-config-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Networking Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectNetworkingCollapse">
<i class="fas fa-network-wired me-2"></i> Networking
</button>
</h2>
<div id="inspectNetworkingCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-networking-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Storage Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectStorageCollapse">
<i class="fas fa-hdd me-2"></i> Storage
</button>
</h2>
<div id="inspectStorageCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-storage-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Resources Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectResourcesCollapse">
<i class="fas fa-server me-2"></i> Resources
</button>
</h2>
<div id="inspectResourcesCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-resources-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Security Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectSecurityCollapse">
<i class="fas fa-shield-alt me-2"></i> Security
</button>
</h2>
<div id="inspectSecurityCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-security-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Runtime Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectRuntimeCollapse">
<i class="fas fa-play-circle me-2"></i> Runtime & Behavior
</button>
</h2>
<div id="inspectRuntimeCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-runtime-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Health & Logging Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectHealthCollapse">
<i class="fas fa-heartbeat me-2"></i> Health & Logging
</button>
</h2>
<div id="inspectHealthCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-health-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
<!-- Labels & Metadata Section -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#inspectLabelsCollapse">
<i class="fas fa-tags me-2"></i> Labels & Metadata
</button>
</h2>
<div id="inspectLabelsCollapse" class="accordion-collapse collapse" data-bs-parent="#inspectAccordion">
<div class="accordion-body" id="inspect-labels-content">
<!-- Content will be populated by JavaScript -->
</div>
</div>
</div>
</div>
</div>
<!-- Raw JSON View -->
<div id="inspect-json-view" style="display: none;">
<pre id="inspect-json-content" class="bg-dark text-white p-3 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word;"></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Image Inspect Modal -->
<div class="modal fade" id="imageInspectModal" tabindex="-1" aria-labelledby="imageInspectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="imageInspectModalLabel">
<i class="fas fa-info-circle me-2"></i>Image Information
</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="toggle-image-json-view">
<i class="fas fa-code"></i> Raw JSON
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="copy-image-json-btn">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<div id="image-inspect-formatted-view">
<div class="accordion accordion-flush" id="imageInspectAccordion">
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#imageOverviewCollapse">
<i class="fas fa-info-circle me-2"></i> Overview
</button>
</h2>
<div id="imageOverviewCollapse" class="accordion-collapse collapse" data-bs-parent="#imageInspectAccordion">
<div class="accordion-body" id="image-overview-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#imageConfigCollapse">
<i class="fas fa-cog me-2"></i> Configuration
</button>
</h2>
<div id="imageConfigCollapse" class="accordion-collapse collapse" data-bs-parent="#imageInspectAccordion">
<div class="accordion-body" id="image-config-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#imageHistoryCollapse">
<i class="fas fa-history me-2"></i> History & Layers
</button>
</h2>
<div id="imageHistoryCollapse" class="accordion-collapse collapse" data-bs-parent="#imageInspectAccordion">
<div class="accordion-body" id="image-history-content"></div>
</div>
</div>
</div>
</div>
<div id="image-inspect-json-view" style="display: none;">
<pre id="image-inspect-json-content" class="bg-dark text-white p-3 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word;"></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Network Inspect Modal -->
<div class="modal fade" id="networkInspectModal" tabindex="-1" aria-labelledby="networkInspectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="networkInspectModalLabel">
<i class="fas fa-info-circle me-2"></i>Network Information
</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="toggle-network-json-view">
<i class="fas fa-code"></i> Raw JSON
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="copy-network-json-btn">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<div id="network-inspect-formatted-view">
<div class="accordion accordion-flush" id="networkInspectAccordion">
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#networkOverviewCollapse">
<i class="fas fa-info-circle me-2"></i> Overview
</button>
</h2>
<div id="networkOverviewCollapse" class="accordion-collapse collapse" data-bs-parent="#networkInspectAccordion">
<div class="accordion-body" id="network-overview-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#networkConfigCollapse">
<i class="fas fa-cog me-2"></i> Configuration
</button>
</h2>
<div id="networkConfigCollapse" class="accordion-collapse collapse" data-bs-parent="#networkInspectAccordion">
<div class="accordion-body" id="network-config-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#networkContainersCollapse">
<i class="fas fa-box me-2"></i> Connected Containers
</button>
</h2>
<div id="networkContainersCollapse" class="accordion-collapse collapse" data-bs-parent="#networkInspectAccordion">
<div class="accordion-body" id="network-containers-content"></div>
</div>
</div>
</div>
</div>
<div id="network-inspect-json-view" style="display: none;">
<pre id="network-inspect-json-content" class="bg-dark text-white p-3 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word;"></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Volume Inspect Modal -->
<div class="modal fade" id="volumeInspectModal" tabindex="-1" aria-labelledby="volumeInspectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="volumeInspectModalLabel">
<i class="fas fa-info-circle me-2"></i>Volume Information
</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="toggle-volume-json-view">
<i class="fas fa-code"></i> Raw JSON
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="copy-volume-json-btn">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<div id="volume-inspect-formatted-view">
<div class="accordion accordion-flush" id="volumeInspectAccordion">
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#volumeOverviewCollapse">
<i class="fas fa-info-circle me-2"></i> Overview
</button>
</h2>
<div id="volumeOverviewCollapse" class="accordion-collapse collapse" data-bs-parent="#volumeInspectAccordion">
<div class="accordion-body" id="volume-overview-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#volumeConfigCollapse">
<i class="fas fa-cog me-2"></i> Configuration
</button>
</h2>
<div id="volumeConfigCollapse" class="accordion-collapse collapse" data-bs-parent="#volumeInspectAccordion">
<div class="accordion-body" id="volume-config-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#volumeUsageCollapse">
<i class="fas fa-box me-2"></i> Usage
</button>
</h2>
<div id="volumeUsageCollapse" class="accordion-collapse collapse" data-bs-parent="#volumeInspectAccordion">
<div class="accordion-body" id="volume-usage-content"></div>
</div>
</div>
</div>
</div>
<div id="volume-inspect-json-view" style="display: none;">
<pre id="volume-inspect-json-content" class="bg-dark text-white p-3 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word;"></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Stack Inspect Modal -->
<div class="modal fade" id="stackInspectModal" tabindex="-1" aria-labelledby="stackInspectModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="stackInspectModalLabel">
<i class="fas fa-info-circle me-2"></i>Stack Information
</h5>
<div class="d-flex gap-2">
<button type="button" class="btn btn-sm btn-outline-secondary" id="toggle-stack-json-view">
<i class="fas fa-code"></i> Raw JSON
</button>
<button type="button" class="btn btn-sm btn-outline-primary" id="copy-stack-json-btn">
<i class="fas fa-copy"></i> Copy JSON
</button>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<div id="stack-inspect-formatted-view">
<div class="accordion accordion-flush" id="stackInspectAccordion">
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#stackOverviewCollapse">
<i class="fas fa-info-circle me-2"></i> Overview
</button>
</h2>
<div id="stackOverviewCollapse" class="accordion-collapse collapse" data-bs-parent="#stackInspectAccordion">
<div class="accordion-body" id="stack-overview-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#stackServicesCollapse">
<i class="fas fa-cogs me-2"></i> Services
</button>
</h2>
<div id="stackServicesCollapse" class="accordion-collapse collapse" data-bs-parent="#stackInspectAccordion">
<div class="accordion-body" id="stack-services-content"></div>
</div>
</div>
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#stackContainersCollapse">
<i class="fas fa-box me-2"></i> Containers
</button>
</h2>
<div id="stackContainersCollapse" class="accordion-collapse collapse" data-bs-parent="#stackInspectAccordion">
<div class="accordion-body" id="stack-containers-content"></div>
</div>
</div>
</div>
</div>
<div id="stack-inspect-json-view" style="display: none;">
<pre id="stack-inspect-json-content" class="bg-dark text-white p-3 rounded" style="max-height: 70vh; overflow-y: auto; font-size: 12px; font-family: 'Courier New', monospace; white-space: pre-wrap; word-wrap: break-word;"></pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Search Input -->
<!-- Template Deploy Modal -->
<div class="modal fade" id="templateDeployModal" tabindex="-1" aria-labelledby="templateDeployModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="templateDeployModalLabel">Deploy Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="text" id="template-search-input" class="form-control mb-3" placeholder="Search templates...">
<ul id="template-list" class="list-group"></ul>
</div>
</div>
</div>
</div>
<!-- Deploy Template Modal -->
<div class="modal fade" id="templateDeployModalUnique" tabindex="-1" aria-labelledby="templateDeployModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="deploy-title">Deploy Template</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="max-height: 80vh; overflow-y: auto;">
<form id="deploy-form">
<!-- Basic Settings (Always Visible) -->
<div class="mb-4">
<h6 class="text-primary mb-3"><i class="fas fa-cog"></i> Basic Settings</h6>
<div class="row">
<div class="col-md-6 mb-3">
<label for="deploy-container-name" class="form-label">Container Name <span class="text-danger">*</span></label>
<input type="text" id="deploy-container-name" class="form-control bg-dark text-white" placeholder="my-container" required>
<small class="text-muted">Alphanumeric, dashes, and underscores only</small>
</div>
<div class="col-md-6 mb-3">
<label for="deploy-image" class="form-label">Image <span class="text-danger">*</span></label>
<input type="text" id="deploy-image" class="form-control bg-dark text-white" placeholder="nginx:latest" required>
<small class="text-muted">Docker image name and tag</small>
</div>
</div>
<div class="row">
<div class="col-md-4 mb-3">
<label for="deploy-command" class="form-label">Command Override</label>
<input type="text" id="deploy-command" class="form-control bg-dark text-white" placeholder="Override default command">
<small class="text-muted">Override container CMD</small>
</div>
<div class="col-md-4 mb-3">
<label for="deploy-entrypoint" class="form-label">Entrypoint Override</label>
<input type="text" id="deploy-entrypoint" class="form-control bg-dark text-white" placeholder="Override default entrypoint">
<small class="text-muted">Override container ENTRYPOINT</small>
</div>
<div class="col-md-4 mb-3">
<label for="deploy-workdir" class="form-label">Working Directory</label>
<input type="text" id="deploy-workdir" class="form-control bg-dark text-white" placeholder="/app">
<small class="text-muted">Container working directory</small>
</div>
</div>
</div>
<!-- Accordion for Advanced Settings -->
<div class="accordion accordion-flush" id="tplDeployAccordion">
<!-- Networking — unique collapse IDs so they don't fight deploy-view accordion -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplNetworkingCollapse">
<i class="fas fa-network-wired me-2"></i> Networking
</button>
</h2>
<div id="tplNetworkingCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-network-mode" class="form-label">Network Mode</label>
<!-- Keep same field id pattern via name; id unique to avoid dual accordion targets -->
<select id="tpl-deploy-network-mode" name="deploy-network-mode" class="form-select bg-dark text-white">
<option value="bridge">Bridge</option>
<option value="host">Host</option>
<option value="none">None</option>
<option value="container">Container</option>
</select>
</div>
<div class="col-md-6" id="tpl-deploy-custom-network-container" style="display: none;">
<label for="tpl-deploy-custom-network" class="form-label">Custom Network</label>
<input type="text" id="tpl-deploy-custom-network" class="form-control bg-dark text-white" placeholder="network-name">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-hostname" class="form-label">Hostname</label>
<input type="text" id="deploy-hostname" class="form-control bg-dark text-white" placeholder="container-hostname">
</div>
<div class="col-md-6">
<label for="deploy-domainname" class="form-label">Domain Name</label>
<input type="text" id="deploy-domainname" class="form-control bg-dark text-white" placeholder="example.com">
</div>
</div>
<div class="mb-3">
<label class="form-label">Port Mappings</label>
<div id="deploy-ports-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addPortMapping()">
<i class="fas fa-plus"></i> Add Port
</button>
</div>
<div class="mb-3">
<label class="form-label">DNS Servers</label>
<div id="deploy-dns-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDnsServer()">
<i class="fas fa-plus"></i> Add DNS Server
</button>
</div>
<div class="mb-3">
<label class="form-label">Extra Hosts</label>
<div id="deploy-extra-hosts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addExtraHost()">
<i class="fas fa-plus"></i> Add Host
</button>
<small class="text-muted d-block mt-1">Format: hostname:ip (e.g., example.com:127.0.0.1)</small>
</div>
</div>
</div>
</div>
<!-- Volumes & Storage -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplVolumesCollapse">
<i class="fas fa-hdd me-2"></i> Volumes & Storage
</button>
</h2>
<div id="tplVolumesCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Volume Mounts</label>
<div id="deploy-volumes-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addVolumeMount()">
<i class="fas fa-plus"></i> Add Volume
</button>
</div>
<div class="mb-3">
<label class="form-label">Tmpfs Mounts</label>
<div id="deploy-tmpfs-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addTmpfsMount()">
<i class="fas fa-plus"></i> Add Tmpfs
</button>
<small class="text-muted d-block mt-1">Format: /path:size (e.g., /tmp:100m)</small>
</div>
</div>
</div>
</div>
<!-- Resources -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplResourcesCollapse">
<i class="fas fa-server me-2"></i> Resources
</button>
</h2>
<div id="tplResourcesCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-4">
<label for="deploy-cpu-limit" class="form-label">CPU Limit (cores)</label>
<input type="number" id="deploy-cpu-limit" class="form-control bg-dark text-white" placeholder="2" step="0.1" min="0">
<small class="text-muted">Number of CPU cores</small>
</div>
<div class="col-md-4">
<label for="deploy-cpu-reservation" class="form-label">CPU Reservation</label>
<input type="number" id="deploy-cpu-reservation" class="form-control bg-dark text-white" placeholder="1" step="0.1" min="0">
</div>
<div class="col-md-4">
<label for="deploy-cpu-shares" class="form-label">CPU Shares</label>
<input type="number" id="deploy-cpu-shares" class="form-control bg-dark text-white" placeholder="1024" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label for="deploy-memory-limit" class="form-label">Memory Limit (MB)</label>
<input type="number" id="deploy-memory-limit" class="form-control bg-dark text-white" placeholder="512" min="0">
</div>
<div class="col-md-4">
<label for="deploy-memory-reservation" class="form-label">Memory Reservation (MB)</label>
<input type="number" id="deploy-memory-reservation" class="form-control bg-dark text-white" placeholder="256" min="0">
</div>
<div class="col-md-4">
<label for="deploy-memory-swap" class="form-label">Memory Swap (MB)</label>
<input type="number" id="deploy-memory-swap" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label class="form-label">Device Mappings</label>
<div id="deploy-devices-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addDeviceMapping()">
<i class="fas fa-plus"></i> Add Device
</button>
<small class="text-muted d-block mt-1">Format: /host/device:/container/device:permissions</small>
</div>
</div>
</div>
</div>
<!-- Environment & Labels -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplEnvLabelsCollapse">
<i class="fas fa-tags me-2"></i> Environment & Labels
</button>
</h2>
<div id="tplEnvLabelsCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Environment Variables</label>
<div id="deploy-env"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addEnvVar()">
<i class="fas fa-plus"></i> Add Variable
</button>
</div>
<div class="mb-3">
<label class="form-label">Labels</label>
<div id="deploy-labels-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addLabel()">
<i class="fas fa-plus"></i> Add Label
</button>
<small class="text-muted d-block mt-1">Format: key=value</small>
</div>
</div>
</div>
</div>
<!-- Security -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplSecurityCollapse">
<i class="fas fa-shield-alt me-2"></i> Security
</button>
</h2>
<div id="tplSecurityCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-user" class="form-label">User/UID</label>
<input type="text" id="deploy-user" class="form-control bg-dark text-white" placeholder="1000 or username">
</div>
<div class="col-md-6">
<label for="deploy-group" class="form-label">Group/GID</label>
<input type="text" id="deploy-group" class="form-control bg-dark text-white" placeholder="1000 or groupname">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-privileged">
<label class="form-check-label" for="deploy-privileged">Privileged Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-readonly-rootfs">
<label class="form-check-label" for="deploy-readonly-rootfs">Read-only Root Filesystem</label>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label">Capabilities</label>
<div id="deploy-capabilities-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addCapability()">
<i class="fas fa-plus"></i> Add Capability
</button>
<small class="text-muted d-block mt-1">e.g., NET_ADMIN, SYS_ADMIN</small>
</div>
<div class="mb-3">
<label class="form-label">Security Options</label>
<div id="deploy-security-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addSecurityOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
<small class="text-muted d-block mt-1">e.g., apparmor=profile, seccomp=unconfined</small>
</div>
</div>
</div>
</div>
<!-- Runtime & Behavior -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplRuntimeCollapse">
<i class="fas fa-play-circle me-2"></i> Runtime & Behavior
</button>
</h2>
<div id="tplRuntimeCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-restart-policy" class="form-label">Restart Policy</label>
<select id="deploy-restart-policy" class="form-select bg-dark text-white">
<option value="no">No</option>
<option value="always">Always</option>
<option value="on-failure">On Failure</option>
<option value="unless-stopped">Unless Stopped</option>
</select>
</div>
<div class="col-md-6">
<label for="deploy-restart-max-retries" class="form-label">Max Retries (on-failure)</label>
<input type="number" id="deploy-restart-max-retries" class="form-control bg-dark text-white" placeholder="10" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-auto-remove">
<label class="form-check-label" for="deploy-auto-remove">Auto Remove</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-tty">
<label class="form-check-label" for="deploy-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-stdin-open">
<label class="form-check-label" for="deploy-stdin-open">Interactive (Stdin Open)</label>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-detach">
<label class="form-check-label" for="deploy-detach">Detach Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-init">
<label class="form-check-label" for="deploy-init">Init Process</label>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Health & Logging -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplHealthCollapse">
<i class="fas fa-heartbeat me-2"></i> Health & Logging
</button>
</h2>
<div id="tplHealthCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label for="deploy-health-cmd" class="form-label">Health Check Command</label>
<input type="text" id="deploy-health-cmd" class="form-control bg-dark text-white" placeholder="CMD-SHELL curl -f http://localhost/ || exit 1">
</div>
<div class="row mb-3">
<div class="col-md-3">
<label for="deploy-health-interval" class="form-label">Interval (s)</label>
<input type="number" id="deploy-health-interval" class="form-control bg-dark text-white" placeholder="30" min="1">
</div>
<div class="col-md-3">
<label for="deploy-health-timeout" class="form-label">Timeout (s)</label>
<input type="number" id="deploy-health-timeout" class="form-control bg-dark text-white" placeholder="10" min="1">
</div>
<div class="col-md-3">
<label for="deploy-health-retries" class="form-label">Retries</label>
<input type="number" id="deploy-health-retries" class="form-control bg-dark text-white" placeholder="3" min="0">
</div>
<div class="col-md-3">
<label for="deploy-health-start-period" class="form-label">Start Period (s)</label>
<input type="number" id="deploy-health-start-period" class="form-control bg-dark text-white" placeholder="0" min="0">
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label for="deploy-log-driver" class="form-label">Logging Driver</label>
<select id="deploy-log-driver" class="form-select bg-dark text-white">
<option value="">Default</option>
<option value="json-file">JSON File</option>
<option value="syslog">Syslog</option>
<option value="journald">Journald</option>
<option value="gelf">GELF</option>
<option value="fluentd">Fluentd</option>
<option value="awslogs">AWS Logs</option>
<option value="none">None</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Logging Options</label>
<div id="deploy-log-opts-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addLogOpt()">
<i class="fas fa-plus"></i> Add Option
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Advanced -->
<div class="accordion-item bg-dark border-secondary">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-dark text-white" type="button" data-bs-toggle="collapse" data-bs-target="#tplAdvancedCollapse">
<i class="fas fa-cogs me-2"></i> Advanced
</button>
</h2>
<div id="tplAdvancedCollapse" class="accordion-collapse collapse" data-bs-parent="#tplDeployAccordion">
<div class="accordion-body">
<div class="mb-3">
<label class="form-label">Sysctls</label>
<div id="deploy-sysctls-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addSysctl()">
<i class="fas fa-plus"></i> Add Sysctl
</button>
<small class="text-muted d-block mt-1">Format: key=value (e.g., net.ipv4.ip_forward=1)</small>
</div>
<div class="mb-3">
<label class="form-label">Ulimits</label>
<div id="deploy-ulimits-container"></div>
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addUlimit()">
<i class="fas fa-plus"></i> Add Ulimit
</button>
<small class="text-muted d-block mt-1">Format: name=soft:hard (e.g., nofile=1024:2048)</small>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-oom-kill-disable">
<label class="form-check-label" for="deploy-oom-kill-disable">Disable OOM Killer</label>
</div>
</div>
<div class="col-md-6">
<label for="deploy-pids-limit" class="form-label">PIDs Limit</label>
<input type="number" id="deploy-pids-limit" class="form-control bg-dark text-white" placeholder="-1" min="-1">
<small class="text-muted">-1 for unlimited</small>
</div>
</div>
<div class="mb-3">
<label for="deploy-shm-size" class="form-label">Shared Memory Size (MB)</label>
<input type="number" id="deploy-shm-size" class="form-control bg-dark text-white" placeholder="64" min="0">
</div>
</div>
</div>
</div>
</div>
<!-- Preview Section -->
<div class="mt-4 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="deploy-show-preview" onchange="togglePreview()">
<label class="form-check-label" for="deploy-show-preview">Show JSON Preview</label>
</div>
<div id="deploy-preview-container" style="display: none;" class="mt-2">
<pre id="deploy-preview" class="bg-dark text-white p-3 rounded" style="max-height: 300px; overflow-y: auto; font-size: 12px;"></pre>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<div>
<button type="button" class="btn btn-outline-info me-2" onclick="saveCurrentTemplate()">
<i class="fas fa-save"></i> Save as Template
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-rocket"></i> Deploy Container
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- File Browser Modal -->
<div class="modal fade" id="fileBrowserModal" tabindex="-1" aria-labelledby="fileBrowserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="fileBrowserModalLabel"><i class="fas fa-folder-open me-2"></i>Browse Directory</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="fileBrowserBreadcrumb" class="mb-3"></div>
<div id="fileBrowserContent" style="min-height: 300px; max-height: 400px; overflow-y: auto; border: 1px solid var(--border-color); border-radius: 4px; padding: 1rem;">
<div class="text-center p-4 text-muted">
<i class="fas fa-spinner fa-spin"></i> Loading...
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="selectDirectoryForVolume()">
<i class="fas fa-check"></i> Select Directory
</button>
</div>
</div>
</div>
</div>
<!-- Pull Image Modal -->
<div class="modal fade" id="pullImageModal" tabindex="-1" aria-labelledby="pullImageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="pullImageModalLabel"><i class="fas fa-download me-2"></i>Pull Image</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="pull-image-form">
<div class="mb-3">
<label for="pull-image-name" class="form-label">Image name</label>
<input type="text" id="pull-image-name" class="form-control bg-dark text-white font-monospace" placeholder="nginx:latest" required autocomplete="off" spellcheck="false">
<small class="text-muted">Full image reference (e.g. nginx:latest, ghcr.io/org/app:1.0)</small>
</div>
<div class="mb-3">
<label for="pull-image-credential" class="form-label">Registry credential</label>
<select id="pull-image-credential" class="form-select bg-dark text-white registry-cred-select">
<option value="">No credential (public / session auto)</option>
</select>
<small class="text-muted">Pick a vault credential for private registries, or leave empty to use session auth / auto-match.</small>
</div>
<div class="mb-2">
<label class="form-label" for="hub-search-term">Search Docker Hub</label>
<div class="input-group input-group-sm">
<input type="search" id="hub-search-term" class="form-control bg-dark text-white" placeholder="Search Hub (e.g. redis, postgres)…" spellcheck="false" autocomplete="off">
<button type="button" class="btn btn-outline-info" id="hub-search-btn" onclick="window.searchDockerHub && window.searchDockerHub()">
<i class="fas fa-magnifying-glass me-1"></i>Search
</button>
</div>
</div>
<div id="hub-search-results" class="list-group list-group-flush small mb-0" style="max-height: 220px; overflow: auto;"></div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-pull-image-btn">
<i class="fas fa-download me-2"></i>Pull Image
</button>
</div>
</div>
</div>
</div>
<!-- Push Image Modal -->
<div class="modal fade" id="pushImageModal" tabindex="-1" aria-labelledby="pushImageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="pushImageModalLabel"><i class="fas fa-upload me-2"></i>Push Image</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" id="push-image-id">
<div class="mb-3">
<label for="push-image-ref" class="form-label">Local reference to push</label>
<select id="push-image-ref" class="form-select bg-dark text-white font-monospace">
<option value="">Select an image tag…</option>
</select>
<small class="text-muted">Choose which local tag to push to the registry.</small>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="push-image-retag">
<label class="form-check-label" for="push-image-retag">Re-tag before push (e.g. to your registry namespace)</label>
</div>
<div id="push-retag-fields" class="row g-2 mb-3" style="display: none;">
<div class="col-md-8">
<label for="push-image-repo" class="form-label">Destination repository</label>
<input type="text" id="push-image-repo" class="form-control bg-dark text-white font-monospace" placeholder="ghcr.io/org/app" autocomplete="off" spellcheck="false">
</div>
<div class="col-md-4">
<label for="push-image-tag" class="form-label">Tag</label>
<input type="text" id="push-image-tag" class="form-control bg-dark text-white font-monospace" value="latest" autocomplete="off" spellcheck="false">
</div>
</div>
<div class="mb-3">
<label for="push-image-credential" class="form-label">Registry credential / target</label>
<select id="push-image-credential" class="form-select bg-dark text-white registry-cred-select">
<option value="">No credential (public / session auto)</option>
</select>
<small class="text-muted">Required for most private registries. Manage credentials under Registry. Or pick <strong>ttl.sh</strong> for a temporary public push.</small>
</div>
<div id="push-ttl-sh-fields" class="push-ttl-sh-panel mb-0" style="display: none;">
<label for="push-ttl-sh-duration" class="form-label">ttl.sh lifetime</label>
<select id="push-ttl-sh-duration" class="form-select bg-dark text-white">
<option value="5m">5 minutes</option>
<option value="30m">30 minutes</option>
<option value="1h" selected>1 hour (default)</option>
<option value="6h">6 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours (max)</option>
</select>
<small class="text-muted d-block mt-1">
Anonymous ephemeral registry at <a href="https://ttl.sh/" target="_blank" rel="noopener noreferrer">ttl.sh</a>.
Images are <strong>public</strong> and auto-delete after the TTL. Tag becomes the duration (e.g. <code>1h</code>).
</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-push-image-btn" data-min-role="operator">
<i class="fas fa-upload me-2"></i>Push
</button>
</div>
</div>
</div>
</div>
<!-- Generic Confirmation Modal -->
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="confirmModalLabel">Confirm Action</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p id="confirmModalMessage">Are you sure?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirmModalBtn">Confirm</button>
</div>
</div>
</div>
</div>
<!-- Container more-actions modal (replaces ⋮ context menu) -->
<div class="modal fade" id="containerActionsModal" tabindex="-1" aria-labelledby="containerActionsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content cam-modal bg-dark text-white">
<div class="modal-header cam-header border-0 pb-0">
<div class="cam-header-text">
<h5 class="modal-title mb-1" id="containerActionsModalLabel">Container</h5>
<div id="cam-subtitle" class="cam-subtitle small"></div>
</div>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body cam-body pt-3">
<div class="cam-section-label">Lifecycle</div>
<div class="cam-grid">
<button type="button" class="cam-action" data-cam-action="restart" data-min-role="operator" data-cam-need-running>
<span class="cam-action-icon"><i class="fas fa-redo"></i></span>
<span class="cam-action-text">
<strong>Restart</strong>
<small>Stop then start the container</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="pause" data-min-role="operator" data-cam-need-running>
<span class="cam-action-icon"><i class="fas fa-pause"></i></span>
<span class="cam-action-text">
<strong>Pause</strong>
<small>Freeze all processes</small>
</span>
</button>
<button type="button" class="cam-action cam-action-warn" data-cam-action="kill" data-min-role="operator" data-cam-need-running>
<span class="cam-action-icon"><i class="fas fa-skull"></i></span>
<span class="cam-action-text">
<strong>Kill</strong>
<small>Force stop (SIGKILL)</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="processes" data-cam-need-running>
<span class="cam-action-icon"><i class="fas fa-microchip"></i></span>
<span class="cam-action-text">
<strong>Processes</strong>
<small>View running process list</small>
</span>
</button>
</div>
<div class="cam-section-label mt-3">Manage</div>
<div class="cam-grid">
<button type="button" class="cam-action" data-cam-action="inspect">
<span class="cam-action-icon"><i class="fas fa-info-circle"></i></span>
<span class="cam-action-text">
<strong>Inspect</strong>
<small>Details, logs &amp; config</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="duplicate" data-min-role="admin">
<span class="cam-action-icon"><i class="fas fa-clone"></i></span>
<span class="cam-action-text">
<strong>Duplicate</strong>
<small>Create from this config</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="rename" data-min-role="operator">
<span class="cam-action-icon"><i class="fas fa-edit"></i></span>
<span class="cam-action-text">
<strong>Rename</strong>
<small>Change container name</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="recreate" data-min-role="admin">
<span class="cam-action-icon"><i class="fas fa-rotate"></i></span>
<span class="cam-action-text">
<strong>Recreate</strong>
<small>Replace with same settings</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="resources" data-min-role="operator">
<span class="cam-action-icon"><i class="fas fa-sliders"></i></span>
<span class="cam-action-text">
<strong>Resources</strong>
<small>CPU &amp; memory limits</small>
</span>
</button>
<button type="button" class="cam-action" data-cam-action="flatten" data-min-role="admin">
<span class="cam-action-icon cam-action-icon-flatten"><i class="fas fa-compress"></i></span>
<span class="cam-action-text">
<strong>Flatten</strong>
<small>Squash into single-layer image</small>
</span>
</button>
</div>
<div class="cam-section-label mt-3">Danger zone</div>
<div class="cam-grid">
<button type="button" class="cam-action cam-action-danger" data-cam-action="remove" data-min-role="admin">
<span class="cam-action-icon"><i class="fas fa-trash"></i></span>
<span class="cam-action-text">
<strong>Remove</strong>
<small>Delete this container</small>
</span>
</button>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Rename Container Modal -->
<div class="modal fade" id="renameContainerModal" tabindex="-1" aria-labelledby="renameContainerModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="renameContainerModalLabel">Rename Container</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="renameContainerForm">
<div class="mb-3">
<label for="new-container-name" class="form-label">New Container Name</label>
<input type="text" class="form-control bg-dark text-white" id="new-container-name" required>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-rename-btn">Rename</button>
</div>
</div>
</div>
</div>
<!-- Commit Container Modal -->
<div class="modal fade" id="commitContainerModal" tabindex="-1" aria-labelledby="commitContainerModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="commitContainerModalLabel">Commit Container</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="commitContainerForm">
<div class="mb-3">
<label for="commit-repo" class="form-label">Repository Name</label>
<input type="text" class="form-control bg-dark text-white" id="commit-repo" required>
</div>
<div class="mb-3">
<label for="commit-tag" class="form-label">Tag</label>
<input type="text" class="form-control bg-dark text-white" id="commit-tag" value="latest">
</div>
<div class="mb-3">
<label for="commit-message" class="form-label">Commit Message (Optional)</label>
<textarea class="form-control bg-dark text-white" id="commit-message" rows="3"></textarea>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-commit-btn">Commit</button>
</div>
</div>
</div>
</div>
<!-- Container Flattener Modal (export | import → single-layer image) -->
<div class="modal fade" id="containerFlattenerModal" tabindex="-1" aria-labelledby="containerFlattenerModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content flatten-modal bg-dark text-white">
<div class="modal-header border-0 pb-0">
<div>
<h5 class="modal-title mb-1" id="containerFlattenerModalLabel">
<i class="fas fa-compress me-2"></i>Container Flattener
</h5>
<div id="flatten-modal-subtitle" class="small text-muted">Squash a container into a single-layer image</div>
</div>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-3">
<div class="flatten-callout mb-3">
<div class="flatten-callout-icon"><i class="fas fa-layer-group"></i></div>
<div>
<strong>What this does</strong>
<p class="mb-0 small text-muted">
Runs <code class="flatten-code">docker export | docker import</code> on the host —
collapsing every image layer and container write into <em>one</em> new layer.
History and parent layers are discarded (unlike Commit). Ideal for baking runtime
state into a slim, pushable artifact.
</p>
</div>
</div>
<div id="flatten-source-card" class="flatten-source-card mb-3"></div>
<div class="flatten-section-label">Destination image</div>
<div class="row g-2 mb-2">
<div class="col-md-8">
<label for="flatten-repo" class="form-label">Repository</label>
<input type="text" class="form-control bg-dark text-white font-monospace" id="flatten-repo"
placeholder="192.168.0.12:5555/myapp" autocomplete="off" spellcheck="false" required>
<small class="text-muted">Include registry host/port when you plan to push (e.g. <code class="flatten-code">host:5555/name</code>).</small>
</div>
<div class="col-md-4">
<label for="flatten-tag" class="form-label">Tag</label>
<input type="text" class="form-control bg-dark text-white font-monospace" id="flatten-tag"
value="flat" autocomplete="off" spellcheck="false">
</div>
</div>
<div class="flatten-ref-bar mb-3">
<span class="text-muted small me-2">Full reference</span>
<code id="flatten-ref-preview" class="flatten-ref-preview text-muted">repository:tag</code>
</div>
<div class="mb-3">
<label for="flatten-message" class="form-label">Message <span class="text-muted fw-normal">(optional)</span></label>
<input type="text" class="form-control bg-dark text-white" id="flatten-message"
placeholder="Why this flat image was created" maxlength="500" autocomplete="off">
</div>
<div class="flatten-options mb-3">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="flatten-pause" checked>
<label class="form-check-label" for="flatten-pause">
Pause container during export
<small class="d-block text-muted">Best-effort FS consistency; auto-resumes when done</small>
</label>
</div>
<div class="form-check form-switch mt-2">
<input class="form-check-input" type="checkbox" id="flatten-preserve-config" checked>
<label class="form-check-label" for="flatten-preserve-config">
Re-apply runtime config (CMD, ENTRYPOINT, …)
<small class="d-block text-muted">Import drops image config — we restore it from the container</small>
</label>
</div>
</div>
<div class="flatten-section-label d-flex align-items-center justify-content-between">
<span>Push to registry</span>
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="flatten-push" role="switch">
<label class="form-check-label small" for="flatten-push">Enable</label>
</div>
</div>
<div id="flatten-push-fields" class="flatten-push-panel mb-3" style="display: none;">
<label for="flatten-credential" class="form-label">Registry credential / target</label>
<select id="flatten-credential" class="form-select bg-dark text-white registry-cred-select">
<option value="">No credential (public / session auto)</option>
</select>
<small class="text-muted d-block">Vault credentials from <strong>Registry</strong>, or <strong>ttl.sh</strong> for a temporary public push (no auth, auto-expire).</small>
<div id="flatten-ttl-sh-fields" class="mt-2" style="display: none;">
<label for="flatten-ttl-sh-duration" class="form-label">ttl.sh lifetime</label>
<select id="flatten-ttl-sh-duration" class="form-select bg-dark text-white form-select-sm">
<option value="5m">5 minutes</option>
<option value="30m">30 minutes</option>
<option value="1h" selected>1 hour (default)</option>
<option value="6h">6 hours</option>
<option value="12h">12 hours</option>
<option value="24h">24 hours (max)</option>
</select>
<small class="text-muted d-block mt-1">
Pushes to <a href="https://ttl.sh/" target="_blank" rel="noopener noreferrer">ttl.sh</a> anonymously.
Public image; tag is the TTL (e.g. <code class="flatten-code">ttl.sh/myapp:1h</code>).
</small>
</div>
<div id="flatten-push-hint" class="flatten-push-hint small mt-2" hidden></div>
</div>
<div class="flatten-section-label d-flex align-items-center justify-content-between mt-1">
<span>Advanced changes</span>
<div class="form-check form-switch mb-0">
<input class="form-check-input" type="checkbox" id="flatten-advanced-toggle" role="switch">
<label class="form-check-label small" for="flatten-advanced-toggle">Edit</label>
</div>
</div>
<div id="flatten-advanced-fields" class="flatten-advanced-panel" style="display: none;">
<div class="row g-2 mb-2">
<div class="col-md-6">
<label for="flatten-cmd" class="form-label">CMD</label>
<input type="text" class="form-control bg-dark text-white font-monospace form-control-sm" id="flatten-cmd"
placeholder='["/bin/sh"] or nginx -g "daemon off;"' autocomplete="off" spellcheck="false">
</div>
<div class="col-md-6">
<label for="flatten-entrypoint" class="form-label">ENTRYPOINT</label>
<input type="text" class="form-control bg-dark text-white font-monospace form-control-sm" id="flatten-entrypoint"
placeholder='["/docker-entrypoint.sh"]' autocomplete="off" spellcheck="false">
</div>
<div class="col-md-6">
<label for="flatten-workdir" class="form-label">WORKDIR</label>
<input type="text" class="form-control bg-dark text-white font-monospace form-control-sm" id="flatten-workdir"
placeholder="/app" autocomplete="off" spellcheck="false">
</div>
<div class="col-md-6">
<label for="flatten-user" class="form-label">USER</label>
<input type="text" class="form-control bg-dark text-white font-monospace form-control-sm" id="flatten-user"
placeholder="1000:1000" autocomplete="off" spellcheck="false">
</div>
</div>
<div class="mb-0">
<label for="flatten-env" class="form-label">ENV <span class="text-muted fw-normal">(one KEY=value per line)</span></label>
<textarea class="form-control bg-dark text-white font-monospace form-control-sm" id="flatten-env" rows="3"
placeholder="PATH=/usr/local/bin&#10;NODE_ENV=production" spellcheck="false"></textarea>
</div>
</div>
</div>
<div class="modal-footer border-0 pt-0">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-flatten-btn" data-min-role="admin">
<i class="fas fa-compress me-2"></i>Flatten image
</button>
</div>
</div>
</div>
</div>
<!-- Exec Container Modal -->
<div class="modal fade" id="execContainerModal" tabindex="-1" aria-labelledby="execContainerModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="execContainerModalLabel">Execute Command in Container</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="execContainerForm">
<div class="mb-3">
<label for="exec-command" class="form-label">Command</label>
<input type="text" class="form-control bg-dark text-white font-monospace" id="exec-command" value="/bin/sh" required>
<small class="text-muted">Enter command to execute (e.g., /bin/sh, /bin/bash, ls -la)</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-exec-btn">Execute</button>
</div>
</div>
</div>
</div>
<!-- Tag Image Modal -->
<div class="modal fade" id="tagImageModal" tabindex="-1" aria-labelledby="tagImageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="tagImageModalLabel">Tag Image</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="tagImageForm">
<div class="mb-3">
<label for="tag-repo" class="form-label">Repository Name</label>
<input type="text" class="form-control bg-dark text-white" id="tag-repo" required>
</div>
<div class="mb-3">
<label for="tag-tag" class="form-label">Tag</label>
<input type="text" class="form-control bg-dark text-white" id="tag-tag" value="latest">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-tag-btn">Tag</button>
</div>
</div>
</div>
</div>
<!-- Connect Container to Network Modal -->
<div class="modal fade" id="connectNetworkModal" tabindex="-1" aria-labelledby="connectNetworkModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="connectNetworkModalLabel">Connect Container to Network</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="connectNetworkForm">
<div class="mb-3">
<label for="connect-container-select" class="form-label">Select Container</label>
<select class="form-select bg-dark text-white" id="connect-container-select" required>
<option value="">Loading containers...</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="confirm-connect-btn">Connect</button>
</div>
</div>
</div>
</div>
<!-- Invite peer modal (admin only — also gated in UI + MethodRoles.invitePeer) -->
<div class="modal fade" id="invitePeerModal" tabindex="-1" aria-labelledby="invitePeerModalLabel" aria-hidden="true" data-min-role="admin" data-admin-invites>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content bg-dark text-white">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="invitePeerModalLabel"><i class="fas fa-ticket me-2"></i>Create invite</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form id="invite-peer-form">
<div class="modal-body">
<p class="small text-muted">Admin only. Creates a share string starting with <code>pd1.</code> that includes the server public key and role grant (capability). Recipients paste it in Add peer — they never need <code>SERVER_SEED</code>.</p>
<div class="mb-3">
<label class="form-label" for="invite-role">Role</label>
<select id="invite-role" class="form-select bg-dark text-white">
<option value="viewer">viewer (read-only)</option>
<option value="operator" selected>operator</option>
<option value="admin">admin</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label class="form-label" for="invite-ttl">TTL (hours)</label>
<input type="number" id="invite-ttl" class="form-control bg-dark text-white" min="0" max="876000" value="0" title="0 = never expires">
<div class="form-text text-muted">0 = forever (default)</div>
</div>
<div class="col-6">
<label class="form-label" for="invite-max-uses">Max uses</label>
<input type="number" id="invite-max-uses" class="form-control bg-dark text-white" min="0" max="1000000" value="0" title="0 = unlimited reconnects">
<div class="form-text text-muted">0 = unlimited (default)</div>
</div>
</div>
<p class="small text-muted mb-0">
Grants persist forever by default. After create, the full <code>pd1.</code> invite appears under
<strong>Active invites</strong> (with Copy). Invited clients reconnect with their saved capability + stable identity.
</p>
</div>
<div class="modal-footer border-secondary">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary" id="invite-peer-submit">
<i class="fas fa-ticket me-1"></i>Create invite
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Legacy create-network modal removed — use smart create (#createNetworkSmartModal) -->
<div class="modal fade d-none" id="createNetworkModal" tabindex="-1" aria-hidden="true" data-legacy="removed">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title">Create Network</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="text-muted small mb-0">This form was replaced by the smart Create network dialog.</p>
<form id="create-network-form" class="d-none">
<input type="text" id="network-name">
<select id="network-driver"><option value="bridge">bridge</option></select>
<input type="text" id="network-subnet">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createNetwork()">
<i class="fas fa-plus me-2"></i>Create Network
</button>
</div>
</div>
</div>
</div>
<!-- Create Volume Modal -->
<div class="modal fade" id="createVolumeModal" tabindex="-1" aria-labelledby="createVolumeModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content bg-dark text-white">
<div class="modal-header">
<h5 class="modal-title" id="createVolumeModalLabel"><i class="fas fa-plus me-2"></i>Create Volume</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="create-volume-form">
<div class="mb-3">
<label for="volume-name" class="form-label">Volume Name</label>
<input type="text" id="volume-name" class="form-control bg-dark text-white" placeholder="my-volume" required>
</div>
<div class="mb-3">
<label for="volume-driver" class="form-label">Driver (Optional)</label>
<input type="text" id="volume-driver" class="form-control bg-dark text-white" placeholder="local">
<small class="text-muted">Leave empty for default local driver</small>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" onclick="createVolume()">
<i class="fas fa-plus me-2"></i>Create Volume
</button>
</div>
</div>
</div>
</div>
<!-- Floating toast stack (visible feedback; tray keeps history) -->
<div id="toast-stack" class="toast-stack" aria-live="polite" aria-atomic="false"></div>
<!-- xterm.js and addons loaded via script tags for UMD compatibility -->
<!-- Scripts load synchronously before modules -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm-addon-fit.js"></script>
<script>
// Verify xterm and FitAddon are loaded immediately after scripts
if (typeof window.Terminal === 'undefined') {
console.error('xterm.js failed to load');
} else {
console.log('xterm.js loaded successfully');
}
if (typeof window.FitAddon === 'undefined') {
console.error('xterm-addon-fit failed to load');
// Check for alternative namespaces
console.log('Checking alternative namespaces...');
console.log('window.xterm:', window.xterm);
console.log('window.xtermAddonFit:', window.xtermAddonFit);
console.log('Available window properties:', Object.keys(window).filter(k => k.toLowerCase().includes('fit') || k.toLowerCase().includes('xterm')));
} else {
console.log('xterm-addon-fit loaded successfully');
console.log('FitAddon type:', typeof window.FitAddon);
console.log('FitAddon is function:', typeof window.FitAddon === 'function');
console.log('FitAddon value:', window.FitAddon);
// Test if it's a constructor (check for nested FitAddon property)
try {
const FitAddonConstructor = window.FitAddon.FitAddon || window.FitAddon;
if (typeof FitAddonConstructor === 'function') {
const test = new FitAddonConstructor();
console.log('FitAddon constructor test: SUCCESS (using', window.FitAddon.FitAddon ? 'nested' : 'direct', 'property)');
} else {
console.error('FitAddon constructor test: FAILED - not a function');
}
} catch (e) {
console.error('FitAddon constructor test: FAILED', e);
}
}
</script>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<!-- Your App JS -->
<script type="module" src="app.js"></script>
</body>
</html>