700 lines
20 KiB
HTML
700 lines
20 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/bootstrap@5.3.2/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;
|
|
/* Ensure it overlays important elements only */
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
/* Stack alerts upwards */
|
|
gap: 10px;
|
|
/* Add space between alerts */
|
|
pointer-events: none;
|
|
/* Prevent container from blocking clicks */
|
|
}
|
|
|
|
#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;
|
|
/* Prevent container from blocking clicks */
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* 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 */
|
|
}
|
|
|
|
|
|
.list-group-item {
|
|
position: relative;
|
|
display: block;
|
|
padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x);
|
|
color: var(--bs-list-group-color);
|
|
text-decoration: none;
|
|
background-color: #2c2c2c
|
|
|
|
;
|
|
}
|
|
|
|
.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"><</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">
|
|
<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-lg">
|
|
<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">
|
|
<form id="duplicate-container-form">
|
|
<div class="mb-3">
|
|
<label for="container-name" class="form-label">Container Name</label>
|
|
<input type="text" class="form-control" id="container-name" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-hostname" class="form-label">Hostname</label>
|
|
<input type="text" class="form-control" id="container-hostname" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-image" class="form-label">Image</label>
|
|
<input type="text" class="form-control" id="container-image" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-netmode" class="form-label">Net Mode</label>
|
|
<input type="text" class="form-control" id="container-netmode" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-cpu" class="form-label">CPU Count</label>
|
|
<input type="number" class="form-control" id="container-cpu" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-memory" class="form-label">Memory (MB)</label>
|
|
<input type="number" class="form-control" id="container-memory" required>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="container-config" class="form-label">Container Configuration (JSON)</label>
|
|
<textarea class="form-control" id="container-config" rows="10" required></textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Deploy Duplicate</button>
|
|
</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 -->
|
|
|
|
<!-- Deploy Modal -->
|
|
<div class="modal fade" id="templateDeployModal" tabindex="-1" aria-labelledby="deployModalLabel" 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="deploy-title"></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 my-3" placeholder="Search templates...">
|
|
<ul id="template-list" class="list-group"></ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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-lg">
|
|
<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">
|
|
<form id="deploy-form">
|
|
<div class="form-group mb-3">
|
|
<label for="deploy-container-name">Container Name</label>
|
|
<input type="text" id="deploy-container-name" class="form-control" placeholder="Enter container name">
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="deploy-image" class="form-label">Image</label>
|
|
<input type="text" id="deploy-image" class="form-control" required />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="deploy-ports" class="form-label">Ports</label>
|
|
<input type="text" id="deploy-ports" class="form-control" placeholder="e.g., 80/tcp, 443/tcp" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="deploy-volumes" class="form-label">Volumes</label>
|
|
<input type="text" id="deploy-volumes" class="form-control" placeholder="e.g., /host/path:/container/path" />
|
|
</div>
|
|
<div class="mb-3">
|
|
<label for="deploy-env" class="form-label">Environment Variables</label>
|
|
<div id="deploy-env"></div>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">Deploy</button>
|
|
</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 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/xterm/lib/xterm.js"></script>
|
|
|
|
<!-- Bootstrap JS -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<!-- Your App JS -->
|
|
<script type="module" src="app.js"></script>
|
|
</body>
|
|
|
|
</html> |