Files
peardock/index.html
T
2025-11-24 15:27:10 -05:00

1759 lines
76 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Docker P2P Manager</title>
<!-- Bootstrap CSS -->
<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.0.0-beta3/css/all.min.css" rel="stylesheet">
<!-- xterm.css for Terminal -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm/css/xterm.css">
<style>
body {
margin: 0;
display: flex;
height: 100vh;
background-color: #1a1a1a;
color: white;
overflow: hidden;
}
.hidden {
display: none !important;
}
#titlebar {
-webkit-app-region: drag;
height: 30px;
width: 100%;
position: fixed;
top: 0;
background-color: #2c2c2c;
z-index: 1000;
}
pear-ctrl[data-platform="darwin"] {
float: left;
margin-top: 5px;
margin-left: 10px;
}
#sidebar {
position: fixed;
top: 30px;
left: 0;
background-color: #2c2c2c;
height: calc(100vh - 30px);
width: 250px;
overflow-y: auto;
transition: width 0.3s ease-in-out;
}
#sidebar.collapsed {
width: 50px;
}
#sidebar.collapsed .content {
display: none;
}
#collapse-sidebar-btn {
position: absolute;
top: 10px;
right: 10px;
background-color: #444;
border: none;
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
font-size: 16px;
line-height: 30px;
text-align: center;
cursor: pointer;
}
#content {
display: flex;
flex-direction: column;
/* Keep vertical stacking for child elements */
margin-left: 250px;
/* Leave space for the sidebar */
flex: 1;
/* Allow the content to grow */
overflow-y: auto;
/* Allow scrolling if content overflows */
position: relative;
}
#sidebar.collapsed~#content {
margin-left: 50px;
}
.connection-status {
border-radius: 50%;
width: 10px;
height: 10px;
display: inline-block;
margin-right: 8px;
}
.status-connected {
background-color: green;
}
.status-disconnected {
background-color: red;
}
#terminal-modal {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
max-height: 90vh;
height: 300px;
background-color: #1a1a1a;
border-top: 2px solid #444;
display: none;
flex-direction: column;
z-index: 1000;
overflow: hidden;
}
#terminal-modal .header {
background-color: #444;
cursor: move;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
#terminal-resize-handle {
width: 100%;
height: 10px;
cursor: ns-resize;
background-color: #444;
position: absolute;
bottom: 0;
left: 0;
}
#terminal-container {
flex: 1;
overflow: hidden;
background-color: black;
color: white;
}
#tray {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background-color: #444;
padding: 5px 10px;
display: flex;
gap: 10px;
overflow-x: auto;
white-space: nowrap;
z-index: 999;
}
#tray .tray-item {
background-color: #555;
color: white;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
}
#status-indicator {
display: none;
/* Ensure it's hidden by default */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.75);
z-index: 1050;
}
#status-indicator .spinner-border {
width: 3rem;
height: 3rem;
}
#status-indicator p {
margin-top: 1rem;
color: #fff;
font-size: 1.25rem;
}
#welcome-page {
display: flex;
flex-direction: column;
/* Stack child elements vertically */
justify-content: center;
/* Center content vertically */
align-items: center;
/* Center content horizontally */
text-align: center;
/* Center-align text */
position: absolute;
/* Overlay it over the content area */
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
/* Center it perfectly in the content */
max-width: 800px;
/* Restrict the width of the welcome page */
width: 100%;
/* Allow it to scale */
padding: 20px;
background-color: transparent;
/* Match the theme */
}
#welcome-page.hidden {
display: none !important;
/* Completely hide when not needed */
}
#dashboard {
display: flex;
/* Use flex layout for content within the dashboard */
flex-direction: column;
/* Stack elements vertically */
flex: 1;
/* Ensure it uses all available space */
width: 100%;
/* Take up the full width of the content area */
margin-top: 30px;
/* Remove extra padding */
overflow-y: auto;
/* Allow vertical scrolling if needed */
position: relative;
/* Prevent overlap with other elements */
}
#dashboard.hidden {
display: none !important;
/* Hide the dashboard completely when not needed */
}
#alert-container {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1055;
display: flex;
flex-direction: column-reverse;
gap: 10px;
pointer-events: none;
}
.alert {
display: inline-flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px;
background-color: #2b2b2b;
color: #e0e0e0;
border-radius: 6px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.5);
font-family: Arial, sans-serif;
font-size: 14px;
animation: fadeIn 0.3s ease-out, fadeOut 4.5s ease-in forwards;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: auto;
/* Allow alerts to be interactive */
}
.alert.success {
border-left: 6px solid #28a745;
/* Green border for success */
}
.alert.danger {
border-left: 6px solid #dc3545;
/* Red border for danger */
}
.alert .close-btn {
background: none;
border: none;
color: #e0e0e0;
font-size: 16px;
cursor: pointer;
margin-left: auto;
/* Align to the far right */
}
@media (max-width: 768px) {
#alert-container {
bottom: 10px;
right: 10px;
}
.alert {
max-width: 90%;
font-size: 12px;
padding: 10px 15px;
white-space: normal;
/* Allow wrapping on small screens */
text-overflow: clip;
/* Disable ellipsis when wrapping */
}
}
@media (min-width: 768px) {
.alert {
white-space: nowrap;
/* Re-enable nowrap for larger screens */
text-overflow: ellipsis;
/* Add ellipsis for overflowed text */
}
}
#sidebar.collapsed .btn-danger {
display: none;
}
/* Skeleton loader styles */
.skeleton {
background: linear-gradient(90deg, #2c2c2c 25%, #3a3a3a 50%, #2c2c2c 75%);
background-size: 200% 100%;
animation: loading 1.5s ease-in-out infinite;
border-radius: 4px;
height: 20px;
}
.skeleton-text {
width: 60%;
}
.skeleton-number {
width: 40px;
}
.skeleton-badge {
width: 80px;
height: 24px;
}
.skeleton-buttons {
width: 200px;
height: 32px;
}
@keyframes loading {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
/* Progress bar container */
.progress-container {
background-color: #2c2c2c;
padding: 15px;
border-radius: 5px;
margin-bottom: 10px;
}
/* Operation status container */
.operation-status-container {
max-width: 300px;
}
.operation-status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin-bottom: 5px;
background-color: #2c2c2c;
border-radius: 5px;
border-left: 3px solid #555;
}
.operation-status-item.status-processing {
border-left-color: #0d6efd;
}
.operation-status-item.status-completed {
border-left-color: #28a745;
}
.operation-status-item.status-failed {
border-left-color: #dc3545;
}
/* Stats visualization */
.stats-container {
display: flex;
align-items: center;
gap: 8px;
min-width: 120px;
}
.stats-value {
min-width: 45px;
font-weight: 500;
}
.stats-bar-container {
flex: 1;
height: 8px;
background-color: #2c2c2c;
border-radius: 4px;
overflow: hidden;
min-width: 60px;
}
.stats-bar {
height: 100%;
transition: width 0.3s ease, background-color 0.3s ease;
border-radius: 4px;
}
.cpu-bar {
background: linear-gradient(90deg, #28a745, #ffc107, #dc3545);
}
.memory-bar {
background: linear-gradient(90deg, #17a2b8, #ffc107, #dc3545);
}
/* General scrollbar styles for dark and skinny scrollbars */
* {
scrollbar-width: thin;
/* Firefox */
scrollbar-color: #555 #1a1a1a;
/* Firefox: thumb and track color */
}
/* WebKit-based browsers (Chrome, Edge, Safari) */
*::-webkit-scrollbar {
width: 8px;
/* Width of vertical scrollbar */
height: 8px;
/* Height of horizontal scrollbar */
}
*::-webkit-scrollbar-track {
background: #1a1a1a;
/* Track color */
}
*::-webkit-scrollbar-thumb {
background-color: #555;
/* Thumb color */
border-radius: 10px;
/* Round the thumb */
border: 2px solid #1a1a1a;
/* Space between thumb and track */
}
*::-webkit-scrollbar-thumb:hover {
background-color: #777;
/* Lighter color on hover */
}
*::-webkit-scrollbar-thumb:active {
background-color: #999;
/* Even lighter color when active */
}
/* Accordion styling for deploy modal */
.accordion-item {
background-color: #2c2c2c;
border-color: #555;
}
.accordion-button {
background-color: #2c2c2c;
color: #e0e0e0;
border-color: #555;
}
.accordion-button:not(.collapsed) {
background-color: #3a3a3a;
color: #ffffff;
box-shadow: inset 0 -1px 0 rgba(255, 255, 255, 0.1);
}
.accordion-button:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.5);
border-color: #0d6efd;
}
.accordion-body {
background-color: #2c2c2c;
color: #e0e0e0;
}
/* Improve form label visibility in deploy modal */
#templateDeployModalUnique .form-label {
color: #e0e0e0;
font-weight: 500;
margin-bottom: 0.5rem;
}
/* Improve form control visibility in deploy modal */
#templateDeployModalUnique .form-control,
#templateDeployModalUnique .form-select {
background-color: #1a1a1a;
border-color: #555;
color: #ffffff;
}
#templateDeployModalUnique .form-control:focus,
#templateDeployModalUnique .form-select:focus {
background-color: #1a1a1a;
border-color: #0d6efd;
color: #ffffff;
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
/* Improve help text visibility */
#templateDeployModalUnique .text-muted {
color: #a0a0a0 !important;
font-size: 0.875rem;
}
/* Improve small text visibility */
#templateDeployModalUnique small {
color: #a0a0a0;
}
/* Improve button visibility */
#templateDeployModalUnique .btn-outline-primary {
border-color: #0d6efd;
color: #0d6efd;
}
#templateDeployModalUnique .btn-outline-primary:hover {
background-color: #0d6efd;
border-color: #0d6efd;
color: #ffffff;
}
/* Improve input placeholder visibility */
#templateDeployModalUnique .form-control::placeholder,
#templateDeployModalUnique .form-select::placeholder {
color: #666;
opacity: 1;
}
/* Form control styling in dark mode */
.form-control:focus,
.form-select:focus {
background-color: #2c2c2c;
border-color: #0d6efd;
color: #fff;
}
/* Array item styling */
.array-item {
display: flex;
gap: 8px;
margin-bottom: 8px;
align-items: center;
}
.array-item input {
flex: 1;
}
.array-item .btn-sm {
flex-shrink: 0;
}
.list-group-item {
position: relative;
display: block;
padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);
color: #ffffff;
text-decoration: none;
background-color: #2c2c2c;
}
.text-primary {
--bs-text-opacity: 1;
color: rgb(254 254 254) !important;
}
.list-group {
--bs-list-group-color: var(--bs-body-color);
--bs-list-group-bg: var(--bs-body-bg);
--bs-list-group-border-color: transparent;
--bs-list-group-border-width: var(--bs-border-width);
--bs-list-group-border-radius: var(--bs-border-radius);
--bs-list-group-item-padding-x: 1rem;
--bs-list-group-item-padding-y: 0.5rem;
--bs-list-group-action-color: var(--bs-secondary-color);
--bs-list-group-action-hover-color: var(--bs-emphasis-color);
--bs-list-group-action-hover-bg: var(--bs-tertiary-bg);
--bs-list-group-action-active-color: var(--bs-body-color);
--bs-list-group-action-active-bg: var(--bs-secondary-bg);
--bs-list-group-disabled-color: var(--bs-secondary-color);
--bs-list-group-disabled-bg: var(--bs-body-bg);
--bs-list-group-active-color: #fff;
--bs-list-group-active-bg: #0d6efd;
--bs-list-group-active-border-color: #0d6efd;
display: flex;
flex-direction: column;
padding-left: 0;
margin-bottom: 0;
border-radius: var(--bs-list-group-border-radius);
}
</style>
</head>
<body>
<div id="titlebar">
<pear-ctrl></pear-ctrl>
</div>
<div id="sidebar">
<button id="collapse-sidebar-btn">&lt;</button>
<div class="content">
<h4 class="text-center mt-3">Connections</h4>
<ul id="connection-list" class="list-group mb-3"></ul>
<form id="add-connection-form" class="px-3 d-flex align-items-center">
<input type="text" id="new-connection-topic" class="form-control me-2" placeholder="Enter server topic" required>
<button type="submit" class="btn btn-primary">
<i class="fas fa-plug"></i> Add
</button>
</form>
</div>
</div>
<div id="content">
<div id="welcome-page">
<h1>Welcome to peardock</h1>
<p class="mt-3">Easily manage your Docker containers across peer-to-peer connections.</p>
<p>To get started, add a connection using the form in the sidebar.</p>
<!-- <img src="https://via.placeholder.com/500x300" alt="Welcome Graphic" class="img-fluid mt-4"> -->
</div>
<div id="dashboard" class="hidden">
<!-- Container Filter and Search -->
<div class="container-fluid mb-3 mt-3">
<div class="row g-2">
<div class="col-md-4">
<input type="text" id="container-search" class="form-control bg-dark text-white" placeholder="Search containers by name or image...">
</div>
<div class="col-md-3">
<select id="container-status-filter" class="form-select bg-dark text-white">
<option value="all">All Status</option>
<option value="running">Running</option>
<option value="exited">Stopped</option>
<option value="created">Created</option>
<option value="restarting">Restarting</option>
</select>
</div>
<div class="col-md-3">
<select id="container-sort" class="form-select bg-dark text-white">
<option value="name-asc">Sort by Name (A-Z)</option>
<option value="name-desc">Sort by Name (Z-A)</option>
<option value="cpu-desc">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-2">
<button id="clear-filters" class="btn btn-outline-secondary w-100">Clear</button>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-striped">
<thead>
<tr>
<th>Name</th>
<th>Image</th>
<th>Status</th>
<th>CPU (%)</th>
<th>Memory (MB)</th>
<th>IP Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="container-list"></tbody>
</table>
</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">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" required>
<small class="text-muted">Alphanumeric, dashes, and underscores only</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-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>
<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="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>
<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="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-4">
<label for="duplicate-cpu-limit" class="form-label">CPU Limit (cores)</label>
<input type="number" id="duplicate-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="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>
<div class="row mb-3">
<div class="col-md-4">
<label for="duplicate-memory-limit" class="form-label">Memory Limit (MB)</label>
<input type="number" id="duplicate-memory-limit" class="form-control bg-dark text-white" placeholder="512" 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 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">
<input class="form-check-input" type="checkbox" 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">
<input class="form-check-input" type="checkbox" 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">
<input class="form-check-input" type="checkbox" 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">
<input class="form-check-input" type="checkbox" id="duplicate-tty">
<label class="form-check-label" for="duplicate-tty">TTY</label>
</div>
</div>
<div class="col-md-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" 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">
<input class="form-check-input" type="checkbox" id="duplicate-detach">
<label class="form-check-label" for="duplicate-detach">Detach Mode</label>
</div>
</div>
<div class="col-md-6">
<div class="form-check">
<input class="form-check-input" type="checkbox" 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">
<input class="form-check-input" type="checkbox" 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>
<div id="terminal-container"></div>
<div id="terminal-resize-handle"></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>
<!-- Status Indicator Overlay -->
<div id="status-indicator"
class="position-fixed top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-dark bg-opacity-75"
style="display: none; z-index: 1050;">
<div class="text-center">
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-3 text-light">Processing...</p>
</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>
<div class="modal-body">
<div id="docker-terminal-container"></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>
<!-- 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="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-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="#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">
<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="#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">
<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="#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">
<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>
<!-- Alert Container -->
<div id="alert-container" class="position-fixed top-0 start-50 translate-middle-x mt-3"
style="z-index: 1051; max-width: 90%;"></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>