Refactor: Improve code readability and formatting across multiple files
- Enhanced formatting in triggers.js for better readability, including consistent line breaks and indentation. - Updated utility functions in utils.js for improved clarity and organization, ensuring consistent formatting and spacing. - Refactored VNC button creation in vnc.js to enhance readability with better structured code. - Modified SQL migration script to improve readability by aligning SQL statements and enhancing the structure of the INSERT statement.
This commit is contained in:
@@ -11,7 +11,14 @@ const esbuild = require("esbuild");
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const entryPoint = path.resolve(__dirname, "novnc-entry.js");
|
const entryPoint = path.resolve(__dirname, "novnc-entry.js");
|
||||||
const outFile = path.resolve(__dirname, "..", "src", "js", "lib", "novnc.min.js");
|
const outFile = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"src",
|
||||||
|
"js",
|
||||||
|
"lib",
|
||||||
|
"novnc.min.js",
|
||||||
|
);
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
@@ -26,7 +33,9 @@ esbuild
|
|||||||
footer: { js: "})();" },
|
footer: { js: "})();" },
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("[bundle-novnc] noVNC bundled successfully → src/js/lib/novnc.min.js");
|
console.log(
|
||||||
|
"[bundle-novnc] noVNC bundled successfully → src/js/lib/novnc.min.js",
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("[bundle-novnc] Failed:", err.message);
|
console.error("[bundle-novnc] Failed:", err.message);
|
||||||
|
|||||||
@@ -191,10 +191,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes trigger-flash {
|
@keyframes trigger-flash {
|
||||||
0%, 100% { background: var(--bg-hover); }
|
0%,
|
||||||
15% { background: rgba(239, 83, 80, 0.25); }
|
100% {
|
||||||
30% { background: var(--bg-hover); }
|
background: var(--bg-hover);
|
||||||
45% { background: rgba(239, 83, 80, 0.15); }
|
}
|
||||||
|
15% {
|
||||||
|
background: rgba(239, 83, 80, 0.25);
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
}
|
||||||
|
45% {
|
||||||
|
background: rgba(239, 83, 80, 0.15);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
|
|||||||
+102
-99
@@ -5,150 +5,153 @@
|
|||||||
========================================================================== */
|
========================================================================== */
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Colour palette — dark charcoal theme
|
Colour palette — dark charcoal theme
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--bg-base: #1c1c1c; /* deepest background */
|
--bg-base: #1c1c1c; /* deepest background */
|
||||||
--bg-surface: #232323; /* primary surface (sidebar, bars) */
|
--bg-surface: #232323; /* primary surface (sidebar, bars) */
|
||||||
--bg-elevated: #2a2a2a; /* elevated panels, cards */
|
--bg-elevated: #2a2a2a; /* elevated panels, cards */
|
||||||
--bg-overlay: #303030; /* overlays, dropdowns */
|
--bg-overlay: #303030; /* overlays, dropdowns */
|
||||||
--bg-hover: #363636; /* hover state backgrounds */
|
--bg-hover: #363636; /* hover state backgrounds */
|
||||||
--bg-active: #3c3c3c; /* active / pressed state */
|
--bg-active: #3c3c3c; /* active / pressed state */
|
||||||
--bg-input: #1e1e1e; /* input field backgrounds */
|
--bg-input: #1e1e1e; /* input field backgrounds */
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Border colours
|
Border colours
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--border-subtle: rgba(255, 255, 255, 0.07);
|
--border-subtle: rgba(255, 255, 255, 0.07);
|
||||||
--border-default: rgba(255, 255, 255, 0.12);
|
--border-default: rgba(255, 255, 255, 0.12);
|
||||||
--border-strong: rgba(255, 255, 255, 0.18);
|
--border-strong: rgba(255, 255, 255, 0.18);
|
||||||
--border-focus: var(--accent);
|
--border-focus: var(--accent);
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Text colours
|
Text colours
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--text-primary: #e4e4e4;
|
--text-primary: #e4e4e4;
|
||||||
--text-secondary: #a0a0a0;
|
--text-secondary: #a0a0a0;
|
||||||
--text-tertiary: #707070;
|
--text-tertiary: #707070;
|
||||||
--text-disabled: #4a4a4a;
|
--text-disabled: #4a4a4a;
|
||||||
--text-inverse: #1c1c1c;
|
--text-inverse: #1c1c1c;
|
||||||
--sidebar-group-title: #b89d5f;
|
--sidebar-group-title: #b89d5f;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Accent colours — teal / cyan
|
Accent colours — teal / cyan
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--accent: #4ecdc4;
|
--accent: #4ecdc4;
|
||||||
--accent-hover: #45b7af;
|
--accent-hover: #45b7af;
|
||||||
--accent-muted: rgba(78, 205, 196, 0.12);
|
--accent-muted: rgba(78, 205, 196, 0.12);
|
||||||
--accent-strong: rgba(78, 205, 196, 0.22);
|
--accent-strong: rgba(78, 205, 196, 0.22);
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Status colours
|
Status colours
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--success: #66bb6a;
|
--success: #66bb6a;
|
||||||
--warning: #ffa726;
|
--warning: #ffa726;
|
||||||
--error: #ef5350;
|
--error: #ef5350;
|
||||||
--info: #42a5f5;
|
--info: #42a5f5;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Protocol badge colours
|
Protocol badge colours
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--protocol-ssh: #4ecdc4;
|
--protocol-ssh: #4ecdc4;
|
||||||
--protocol-telnet: #ffa726;
|
--protocol-telnet: #ffa726;
|
||||||
--protocol-raw: #ab47bc;
|
--protocol-raw: #ab47bc;
|
||||||
--protocol-github: #7c8aff;
|
--protocol-github: #7c8aff;
|
||||||
--protocol-vnc: #e85d4a;
|
--protocol-vnc: #e85d4a;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Spacing scale (4 px base unit)
|
Spacing scale (4 px base unit)
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--space-1: 4px;
|
--space-1: 4px;
|
||||||
--space-2: 8px;
|
--space-2: 8px;
|
||||||
--space-3: 12px;
|
--space-3: 12px;
|
||||||
--space-4: 16px;
|
--space-4: 16px;
|
||||||
--space-5: 20px;
|
--space-5: 20px;
|
||||||
--space-6: 24px;
|
--space-6: 24px;
|
||||||
--space-8: 32px;
|
--space-8: 32px;
|
||||||
--space-10: 40px;
|
--space-10: 40px;
|
||||||
--space-12: 48px;
|
--space-12: 48px;
|
||||||
--space-16: 64px;
|
--space-16: 64px;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Typography
|
Typography
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
|
--font-sans:
|
||||||
system-ui, sans-serif;
|
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
|
||||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco,
|
sans-serif;
|
||||||
'Cascadia Code', monospace;
|
--font-mono:
|
||||||
|
"JetBrains Mono", "Fira Code", "SF Mono", Monaco, "Cascadia Code", monospace;
|
||||||
|
|
||||||
--text-xs: 11px;
|
--text-xs: 11px;
|
||||||
--text-sm: 12px;
|
--text-sm: 12px;
|
||||||
--text-base: 13px;
|
--text-base: 13px;
|
||||||
--text-md: 14px;
|
--text-md: 14px;
|
||||||
--text-lg: 16px;
|
--text-lg: 16px;
|
||||||
--text-xl: 18px;
|
--text-xl: 18px;
|
||||||
--text-2xl: 22px;
|
--text-2xl: 22px;
|
||||||
|
|
||||||
--leading-tight: 1.25;
|
--leading-tight: 1.25;
|
||||||
--leading-normal: 1.5;
|
--leading-normal: 1.5;
|
||||||
--leading-relaxed: 1.625;
|
--leading-relaxed: 1.625;
|
||||||
|
|
||||||
--weight-normal: 400;
|
--weight-normal: 400;
|
||||||
--weight-medium: 500;
|
--weight-medium: 500;
|
||||||
--weight-semibold: 600;
|
--weight-semibold: 600;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Border radius
|
Border radius
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--radius-sm: 3px;
|
--radius-sm: 3px;
|
||||||
--radius-md: 5px;
|
--radius-md: 5px;
|
||||||
--radius-lg: 8px;
|
--radius-lg: 8px;
|
||||||
--radius-xl: 12px;
|
--radius-xl: 12px;
|
||||||
--radius-full: 9999px;
|
--radius-full: 9999px;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Shadows
|
Shadows
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.40);
|
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||||
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.50);
|
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||||
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.60);
|
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Transitions
|
Transitions
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--transition-fast: 100ms ease;
|
--transition-fast: 100ms ease;
|
||||||
--transition-normal: 200ms ease;
|
--transition-normal: 200ms ease;
|
||||||
--transition-slow: 300ms ease;
|
--transition-slow: 300ms ease;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Z-index layers
|
Z-index layers
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--z-base: 0;
|
--z-base: 0;
|
||||||
--z-sticky: 100;
|
--z-sticky: 100;
|
||||||
--z-dropdown: 200;
|
--z-dropdown: 200;
|
||||||
--z-modal-backdrop: 300;
|
--z-modal-backdrop: 300;
|
||||||
--z-modal: 400;
|
--z-modal: 400;
|
||||||
--z-notification: 500;
|
--z-notification: 500;
|
||||||
--z-tooltip: 600;
|
--z-tooltip: 600;
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Layout
|
Layout
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
--sidebar-width: 260px;
|
--sidebar-width: 260px;
|
||||||
--sidebar-width-collapsed: 48px;
|
--sidebar-width-collapsed: 48px;
|
||||||
--tab-bar-height: 36px;
|
--tab-bar-height: 36px;
|
||||||
--toolbar-height: 40px;
|
--toolbar-height: 40px;
|
||||||
--status-bar-height: 24px;
|
--status-bar-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -----------------------------------------------------------------------
|
/* -----------------------------------------------------------------------
|
||||||
Global input overrides — disable macOS/WebKit auto-capitalisation,
|
Global input overrides — disable macOS/WebKit auto-capitalisation,
|
||||||
autocorrect, and spellcheck on all text fields.
|
autocorrect, and spellcheck on all text fields.
|
||||||
----------------------------------------------------------------------- */
|
----------------------------------------------------------------------- */
|
||||||
input, textarea, select {
|
input,
|
||||||
autocapitalize: none;
|
textarea,
|
||||||
autocorrect: off;
|
select {
|
||||||
spellcheck: false;
|
autocapitalize: none;
|
||||||
text-transform: none;
|
autocorrect: off;
|
||||||
|
spellcheck: false;
|
||||||
|
text-transform: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,9 @@
|
|||||||
border: 1px solid var(--border-subtle);
|
border: 1px solid var(--border-subtle);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.15s, color 0.15s;
|
transition:
|
||||||
|
background 0.15s,
|
||||||
|
color 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-vnc-action:hover {
|
.btn-vnc-action:hover {
|
||||||
|
|||||||
@@ -168,7 +168,9 @@
|
|||||||
try {
|
try {
|
||||||
const raw = atob(payload.data);
|
const raw = atob(payload.data);
|
||||||
this.triggers.check(payload.sessionID, raw);
|
this.triggers.check(payload.sessionID, raw);
|
||||||
} catch (_) { /* noop */ }
|
} catch (_) {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Session state transitions (connecting → connected → reconnecting → disconnected)
|
// Session state transitions (connecting → connected → reconnecting → disconnected)
|
||||||
@@ -529,7 +531,9 @@
|
|||||||
setupUI() {
|
setupUI() {
|
||||||
var btnEl = document.getElementById("btn-new-connection");
|
var btnEl = document.getElementById("btn-new-connection");
|
||||||
this._bindClick("btn-new-connection", () => this.profileEditor.openNew());
|
this._bindClick("btn-new-connection", () => this.profileEditor.openNew());
|
||||||
this._bindClick("btn-command-palette", () => this.commandPalette.toggle());
|
this._bindClick("btn-command-palette", () =>
|
||||||
|
this.commandPalette.toggle(),
|
||||||
|
);
|
||||||
this._bindClick("btn-settings", () => this.settings.open());
|
this._bindClick("btn-settings", () => this.settings.open());
|
||||||
this._bindClick("btn-import", () => this.importProfiles());
|
this._bindClick("btn-import", () => this.importProfiles());
|
||||||
this._bindClick("btn-export", () => this.exportProfiles());
|
this._bindClick("btn-export", () => this.exportProfiles());
|
||||||
|
|||||||
@@ -206,14 +206,16 @@
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "vnc":
|
case "vnc":
|
||||||
this._renderVNCSections(profile).forEach((s) =>
|
this._renderVNCSections(profile).forEach((s) => body.appendChild(s));
|
||||||
body.appendChild(s),
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Terminal section (SSH and Telnet only)
|
// Terminal section (SSH and Telnet only)
|
||||||
if (profile.protocol !== "raw" && profile.protocol !== "github" && profile.protocol !== "vnc") {
|
if (
|
||||||
|
profile.protocol !== "raw" &&
|
||||||
|
profile.protocol !== "github" &&
|
||||||
|
profile.protocol !== "vnc"
|
||||||
|
) {
|
||||||
body.appendChild(this._renderTerminalSection(profile));
|
body.appendChild(this._renderTerminalSection(profile));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2288,7 +2290,9 @@
|
|||||||
if (profile.protocol === "vnc") {
|
if (profile.protocol === "vnc") {
|
||||||
const vncHost = (profile.vnc && profile.vnc.vncHost) || profile.host;
|
const vncHost = (profile.vnc && profile.vnc.vncHost) || profile.host;
|
||||||
if (!vncHost || !vncHost.trim()) {
|
if (!vncHost || !vncHost.trim()) {
|
||||||
errors.push("VNC Host is required (in General or VNC Server section).");
|
errors.push(
|
||||||
|
"VNC Host is required (in General or VNC Server section).",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else if (!profile.host || !profile.host.trim()) {
|
} else if (!profile.host || !profile.host.trim()) {
|
||||||
errors.push("Host is required.");
|
errors.push("Host is required.");
|
||||||
|
|||||||
+58
-29
@@ -63,11 +63,15 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
modal.querySelector(".modal-close").addEventListener("click", () => this.close());
|
modal
|
||||||
|
.querySelector(".modal-close")
|
||||||
|
.addEventListener("click", () => this.close());
|
||||||
backdrop.addEventListener("click", (e) => {
|
backdrop.addEventListener("click", (e) => {
|
||||||
if (e.target === backdrop) this.close();
|
if (e.target === backdrop) this.close();
|
||||||
});
|
});
|
||||||
modal.querySelector(".sshkey-generate-btn").addEventListener("click", () => this._showGenerateForm());
|
modal
|
||||||
|
.querySelector(".sshkey-generate-btn")
|
||||||
|
.addEventListener("click", () => this._showGenerateForm());
|
||||||
}
|
}
|
||||||
|
|
||||||
async _loadKeys() {
|
async _loadKeys() {
|
||||||
@@ -79,7 +83,8 @@
|
|||||||
try {
|
try {
|
||||||
const keys = await window.go.api.Service.ListSSHKeyPairs();
|
const keys = await window.go.api.Service.ListSSHKeyPairs();
|
||||||
if (keys.length === 0) {
|
if (keys.length === 0) {
|
||||||
list.innerHTML = '<div class="sshkey-empty">No SSH keys found in ~/.ssh/</div>';
|
list.innerHTML =
|
||||||
|
'<div class="sshkey-empty">No SSH keys found in ~/.ssh/</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
@@ -95,7 +100,9 @@
|
|||||||
const row = createElement("div", "sshkey-row");
|
const row = createElement("div", "sshkey-row");
|
||||||
|
|
||||||
const info = createElement("div", "sshkey-info");
|
const info = createElement("div", "sshkey-info");
|
||||||
const name = createElement("div", "sshkey-name", { textContent: key.name });
|
const name = createElement("div", "sshkey-name", {
|
||||||
|
textContent: key.name,
|
||||||
|
});
|
||||||
const meta = createElement("div", "sshkey-meta", {
|
const meta = createElement("div", "sshkey-meta", {
|
||||||
textContent: key.type.toUpperCase() + (key.bits ? ` ${key.bits}` : ""),
|
textContent: key.type.toUpperCase() + (key.bits ? ` ${key.bits}` : ""),
|
||||||
});
|
});
|
||||||
@@ -104,11 +111,15 @@
|
|||||||
|
|
||||||
const actions = createElement("div", "sshkey-actions");
|
const actions = createElement("div", "sshkey-actions");
|
||||||
|
|
||||||
const copyBtn = createElement("button", "btn btn-xs", { textContent: "Copy Public Key" });
|
const copyBtn = createElement("button", "btn btn-xs", {
|
||||||
|
textContent: "Copy Public Key",
|
||||||
|
});
|
||||||
copyBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
copyBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||||
copyBtn.addEventListener("click", () => this._copyPublicKey(key));
|
copyBtn.addEventListener("click", () => this._copyPublicKey(key));
|
||||||
|
|
||||||
const deleteBtn = createElement("button", "btn btn-xs btn-danger", { textContent: "Delete" });
|
const deleteBtn = createElement("button", "btn btn-xs btn-danger", {
|
||||||
|
textContent: "Delete",
|
||||||
|
});
|
||||||
deleteBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
deleteBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||||
deleteBtn.addEventListener("click", () => this._confirmDelete(key));
|
deleteBtn.addEventListener("click", () => this._confirmDelete(key));
|
||||||
|
|
||||||
@@ -208,28 +219,40 @@
|
|||||||
list.style.display = "";
|
list.style.display = "";
|
||||||
});
|
});
|
||||||
|
|
||||||
form.querySelector(".sshkey-create-btn").addEventListener("click", async () => {
|
form
|
||||||
const nameInput = form.querySelector(".sshkey-name-input");
|
.querySelector(".sshkey-create-btn")
|
||||||
const name = nameInput.value.trim();
|
.addEventListener("click", async () => {
|
||||||
const keyType = typeSelect.value;
|
const nameInput = form.querySelector(".sshkey-name-input");
|
||||||
const bits = parseInt(form.querySelector(".sshkey-bits-select").value, 10);
|
const name = nameInput.value.trim();
|
||||||
const passphrase = form.querySelector(".sshkey-pass-input").value;
|
const keyType = typeSelect.value;
|
||||||
|
const bits = parseInt(
|
||||||
|
form.querySelector(".sshkey-bits-select").value,
|
||||||
|
10,
|
||||||
|
);
|
||||||
|
const passphrase = form.querySelector(".sshkey-pass-input").value;
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
this.app.notifications.error("Key name is required");
|
this.app.notifications.error("Key name is required");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await window.go.api.Service.GenerateSSHKey(name, keyType, bits, passphrase);
|
await window.go.api.Service.GenerateSSHKey(
|
||||||
this.app.notifications.success(`Key "${name}" generated successfully`);
|
name,
|
||||||
form.remove();
|
keyType,
|
||||||
list.style.display = "";
|
bits,
|
||||||
this._loadKeys();
|
passphrase,
|
||||||
} catch (err) {
|
);
|
||||||
this.app.notifications.error("Generation failed: " + err);
|
this.app.notifications.success(
|
||||||
}
|
`Key "${name}" generated successfully`,
|
||||||
});
|
);
|
||||||
|
form.remove();
|
||||||
|
list.style.display = "";
|
||||||
|
this._loadKeys();
|
||||||
|
} catch (err) {
|
||||||
|
this.app.notifications.error("Generation failed: " + err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
form.querySelector(".sshkey-name-input").focus();
|
form.querySelector(".sshkey-name-input").focus();
|
||||||
}
|
}
|
||||||
@@ -266,9 +289,15 @@
|
|||||||
setTimeout(() => backdrop.remove(), 200);
|
setTimeout(() => backdrop.remove(), 200);
|
||||||
resolve(val);
|
resolve(val);
|
||||||
};
|
};
|
||||||
modal.querySelector(".modal-close").addEventListener("click", () => close(false));
|
modal
|
||||||
modal.querySelector(".confirm-cancel").addEventListener("click", () => close(false));
|
.querySelector(".modal-close")
|
||||||
modal.querySelector(".confirm-ok").addEventListener("click", () => close(true));
|
.addEventListener("click", () => close(false));
|
||||||
|
modal
|
||||||
|
.querySelector(".confirm-cancel")
|
||||||
|
.addEventListener("click", () => close(false));
|
||||||
|
modal
|
||||||
|
.querySelector(".confirm-ok")
|
||||||
|
.addEventListener("click", () => close(true));
|
||||||
backdrop.addEventListener("click", (e) => {
|
backdrop.addEventListener("click", (e) => {
|
||||||
if (e.target === backdrop) close(false);
|
if (e.target === backdrop) close(false);
|
||||||
});
|
});
|
||||||
|
|||||||
+314
-315
@@ -1,348 +1,347 @@
|
|||||||
(function() {
|
(function () {
|
||||||
/**
|
/**
|
||||||
* Nexus Terminal — terminal colour themes.
|
* Nexus Terminal — terminal colour themes.
|
||||||
* Provides a collection of preset xterm.js colour schemes and a manager
|
* Provides a collection of preset xterm.js colour schemes and a manager
|
||||||
* for applying them to terminal instances.
|
* for applying them to terminal instances.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Preset terminal colour themes.
|
* Preset terminal colour themes.
|
||||||
* Each entry contains the full xterm.js theme object.
|
* Each entry contains the full xterm.js theme object.
|
||||||
*/
|
*/
|
||||||
const THEMES = {
|
const THEMES = {
|
||||||
charcoal: {
|
charcoal: {
|
||||||
label: 'Charcoal (Default)',
|
label: "Charcoal (Default)",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#1c1c1c',
|
background: "#1c1c1c",
|
||||||
foreground: '#e4e4e4',
|
foreground: "#e4e4e4",
|
||||||
cursor: '#4ecdc4',
|
cursor: "#4ecdc4",
|
||||||
cursorAccent: '#1c1c1c',
|
cursorAccent: "#1c1c1c",
|
||||||
selectionBackground: 'rgba(78, 205, 196, 0.3)',
|
selectionBackground: "rgba(78, 205, 196, 0.3)",
|
||||||
selectionForeground: '#ffffff',
|
selectionForeground: "#ffffff",
|
||||||
black: '#1c1c1c',
|
black: "#1c1c1c",
|
||||||
red: '#ef5350',
|
red: "#ef5350",
|
||||||
green: '#66bb6a',
|
green: "#66bb6a",
|
||||||
yellow: '#ffa726',
|
yellow: "#ffa726",
|
||||||
blue: '#42a5f5',
|
blue: "#42a5f5",
|
||||||
magenta: '#ab47bc',
|
magenta: "#ab47bc",
|
||||||
cyan: '#4ecdc4',
|
cyan: "#4ecdc4",
|
||||||
white: '#e4e4e4',
|
white: "#e4e4e4",
|
||||||
brightBlack: '#555555',
|
brightBlack: "#555555",
|
||||||
brightRed: '#ff7043',
|
brightRed: "#ff7043",
|
||||||
brightGreen: '#81c784',
|
brightGreen: "#81c784",
|
||||||
brightYellow: '#ffcc02',
|
brightYellow: "#ffcc02",
|
||||||
brightBlue: '#64b5f6',
|
brightBlue: "#64b5f6",
|
||||||
brightMagenta: '#ce93d8',
|
brightMagenta: "#ce93d8",
|
||||||
brightCyan: '#80deea',
|
brightCyan: "#80deea",
|
||||||
brightWhite: '#ffffff',
|
brightWhite: "#ffffff",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'solarized-dark': {
|
"solarized-dark": {
|
||||||
label: 'Solarized Dark',
|
label: "Solarized Dark",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#002b36',
|
background: "#002b36",
|
||||||
foreground: '#839496',
|
foreground: "#839496",
|
||||||
cursor: '#93a1a1',
|
cursor: "#93a1a1",
|
||||||
cursorAccent: '#002b36',
|
cursorAccent: "#002b36",
|
||||||
selectionBackground: 'rgba(147, 161, 161, 0.3)',
|
selectionBackground: "rgba(147, 161, 161, 0.3)",
|
||||||
selectionForeground: '#fdf6e3',
|
selectionForeground: "#fdf6e3",
|
||||||
black: '#073642',
|
black: "#073642",
|
||||||
red: '#dc322f',
|
red: "#dc322f",
|
||||||
green: '#859900',
|
green: "#859900",
|
||||||
yellow: '#b58900',
|
yellow: "#b58900",
|
||||||
blue: '#268bd2',
|
blue: "#268bd2",
|
||||||
magenta: '#d33682',
|
magenta: "#d33682",
|
||||||
cyan: '#2aa198',
|
cyan: "#2aa198",
|
||||||
white: '#eee8d5',
|
white: "#eee8d5",
|
||||||
brightBlack: '#586e75',
|
brightBlack: "#586e75",
|
||||||
brightRed: '#cb4b16',
|
brightRed: "#cb4b16",
|
||||||
brightGreen: '#586e75',
|
brightGreen: "#586e75",
|
||||||
brightYellow: '#657b83',
|
brightYellow: "#657b83",
|
||||||
brightBlue: '#839496',
|
brightBlue: "#839496",
|
||||||
brightMagenta: '#6c71c4',
|
brightMagenta: "#6c71c4",
|
||||||
brightCyan: '#93a1a1',
|
brightCyan: "#93a1a1",
|
||||||
brightWhite: '#fdf6e3',
|
brightWhite: "#fdf6e3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
monokai: {
|
monokai: {
|
||||||
label: 'Monokai',
|
label: "Monokai",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#272822',
|
background: "#272822",
|
||||||
foreground: '#f8f8f2',
|
foreground: "#f8f8f2",
|
||||||
cursor: '#f8f8f0',
|
cursor: "#f8f8f0",
|
||||||
cursorAccent: '#272822',
|
cursorAccent: "#272822",
|
||||||
selectionBackground: 'rgba(73, 72, 62, 0.6)',
|
selectionBackground: "rgba(73, 72, 62, 0.6)",
|
||||||
selectionForeground: '#f8f8f2',
|
selectionForeground: "#f8f8f2",
|
||||||
black: '#272822',
|
black: "#272822",
|
||||||
red: '#f92672',
|
red: "#f92672",
|
||||||
green: '#a6e22e',
|
green: "#a6e22e",
|
||||||
yellow: '#f4bf75',
|
yellow: "#f4bf75",
|
||||||
blue: '#66d9ef',
|
blue: "#66d9ef",
|
||||||
magenta: '#ae81ff',
|
magenta: "#ae81ff",
|
||||||
cyan: '#a1efe4',
|
cyan: "#a1efe4",
|
||||||
white: '#f8f8f2',
|
white: "#f8f8f2",
|
||||||
brightBlack: '#75715e',
|
brightBlack: "#75715e",
|
||||||
brightRed: '#f92672',
|
brightRed: "#f92672",
|
||||||
brightGreen: '#a6e22e',
|
brightGreen: "#a6e22e",
|
||||||
brightYellow: '#f4bf75',
|
brightYellow: "#f4bf75",
|
||||||
brightBlue: '#66d9ef',
|
brightBlue: "#66d9ef",
|
||||||
brightMagenta: '#ae81ff',
|
brightMagenta: "#ae81ff",
|
||||||
brightCyan: '#a1efe4',
|
brightCyan: "#a1efe4",
|
||||||
brightWhite: '#f9f8f5',
|
brightWhite: "#f9f8f5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
nord: {
|
nord: {
|
||||||
label: 'Nord',
|
label: "Nord",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#2e3440',
|
background: "#2e3440",
|
||||||
foreground: '#d8dee9',
|
foreground: "#d8dee9",
|
||||||
cursor: '#d8dee9',
|
cursor: "#d8dee9",
|
||||||
cursorAccent: '#2e3440',
|
cursorAccent: "#2e3440",
|
||||||
selectionBackground: 'rgba(136, 192, 208, 0.3)',
|
selectionBackground: "rgba(136, 192, 208, 0.3)",
|
||||||
selectionForeground: '#eceff4',
|
selectionForeground: "#eceff4",
|
||||||
black: '#3b4252',
|
black: "#3b4252",
|
||||||
red: '#bf616a',
|
red: "#bf616a",
|
||||||
green: '#a3be8c',
|
green: "#a3be8c",
|
||||||
yellow: '#ebcb8b',
|
yellow: "#ebcb8b",
|
||||||
blue: '#81a1c1',
|
blue: "#81a1c1",
|
||||||
magenta: '#b48ead',
|
magenta: "#b48ead",
|
||||||
cyan: '#88c0d0',
|
cyan: "#88c0d0",
|
||||||
white: '#e5e9f0',
|
white: "#e5e9f0",
|
||||||
brightBlack: '#4c566a',
|
brightBlack: "#4c566a",
|
||||||
brightRed: '#bf616a',
|
brightRed: "#bf616a",
|
||||||
brightGreen: '#a3be8c',
|
brightGreen: "#a3be8c",
|
||||||
brightYellow: '#ebcb8b',
|
brightYellow: "#ebcb8b",
|
||||||
brightBlue: '#81a1c1',
|
brightBlue: "#81a1c1",
|
||||||
brightMagenta: '#b48ead',
|
brightMagenta: "#b48ead",
|
||||||
brightCyan: '#8fbcbb',
|
brightCyan: "#8fbcbb",
|
||||||
brightWhite: '#eceff4',
|
brightWhite: "#eceff4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
dracula: {
|
dracula: {
|
||||||
label: 'Dracula',
|
label: "Dracula",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282a36',
|
background: "#282a36",
|
||||||
foreground: '#f8f8f2',
|
foreground: "#f8f8f2",
|
||||||
cursor: '#f8f8f2',
|
cursor: "#f8f8f2",
|
||||||
cursorAccent: '#282a36',
|
cursorAccent: "#282a36",
|
||||||
selectionBackground: 'rgba(68, 71, 90, 0.6)',
|
selectionBackground: "rgba(68, 71, 90, 0.6)",
|
||||||
selectionForeground: '#f8f8f2',
|
selectionForeground: "#f8f8f2",
|
||||||
black: '#21222c',
|
black: "#21222c",
|
||||||
red: '#ff5555',
|
red: "#ff5555",
|
||||||
green: '#50fa7b',
|
green: "#50fa7b",
|
||||||
yellow: '#f1fa8c',
|
yellow: "#f1fa8c",
|
||||||
blue: '#bd93f9',
|
blue: "#bd93f9",
|
||||||
magenta: '#ff79c6',
|
magenta: "#ff79c6",
|
||||||
cyan: '#8be9fd',
|
cyan: "#8be9fd",
|
||||||
white: '#f8f8f2',
|
white: "#f8f8f2",
|
||||||
brightBlack: '#6272a4',
|
brightBlack: "#6272a4",
|
||||||
brightRed: '#ff6e6e',
|
brightRed: "#ff6e6e",
|
||||||
brightGreen: '#69ff94',
|
brightGreen: "#69ff94",
|
||||||
brightYellow: '#ffffa5',
|
brightYellow: "#ffffa5",
|
||||||
brightBlue: '#d6acff',
|
brightBlue: "#d6acff",
|
||||||
brightMagenta: '#ff92df',
|
brightMagenta: "#ff92df",
|
||||||
brightCyan: '#a4ffff',
|
brightCyan: "#a4ffff",
|
||||||
brightWhite: '#ffffff',
|
brightWhite: "#ffffff",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'one-dark': {
|
"one-dark": {
|
||||||
label: 'One Dark',
|
label: "One Dark",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282c34',
|
background: "#282c34",
|
||||||
foreground: '#abb2bf',
|
foreground: "#abb2bf",
|
||||||
cursor: '#528bff',
|
cursor: "#528bff",
|
||||||
cursorAccent: '#282c34',
|
cursorAccent: "#282c34",
|
||||||
selectionBackground: 'rgba(82, 139, 255, 0.3)',
|
selectionBackground: "rgba(82, 139, 255, 0.3)",
|
||||||
selectionForeground: '#abb2bf',
|
selectionForeground: "#abb2bf",
|
||||||
black: '#282c34',
|
black: "#282c34",
|
||||||
red: '#e06c75',
|
red: "#e06c75",
|
||||||
green: '#98c379',
|
green: "#98c379",
|
||||||
yellow: '#e5c07b',
|
yellow: "#e5c07b",
|
||||||
blue: '#61afef',
|
blue: "#61afef",
|
||||||
magenta: '#c678dd',
|
magenta: "#c678dd",
|
||||||
cyan: '#56b6c2',
|
cyan: "#56b6c2",
|
||||||
white: '#abb2bf',
|
white: "#abb2bf",
|
||||||
brightBlack: '#5c6370',
|
brightBlack: "#5c6370",
|
||||||
brightRed: '#e06c75',
|
brightRed: "#e06c75",
|
||||||
brightGreen: '#98c379',
|
brightGreen: "#98c379",
|
||||||
brightYellow: '#e5c07b',
|
brightYellow: "#e5c07b",
|
||||||
brightBlue: '#61afef',
|
brightBlue: "#61afef",
|
||||||
brightMagenta: '#c678dd',
|
brightMagenta: "#c678dd",
|
||||||
brightCyan: '#56b6c2',
|
brightCyan: "#56b6c2",
|
||||||
brightWhite: '#ffffff',
|
brightWhite: "#ffffff",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'gruvbox-dark': {
|
"gruvbox-dark": {
|
||||||
label: 'Gruvbox Dark',
|
label: "Gruvbox Dark",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#282828',
|
background: "#282828",
|
||||||
foreground: '#ebdbb2',
|
foreground: "#ebdbb2",
|
||||||
cursor: '#ebdbb2',
|
cursor: "#ebdbb2",
|
||||||
cursorAccent: '#282828',
|
cursorAccent: "#282828",
|
||||||
selectionBackground: 'rgba(214, 93, 14, 0.3)',
|
selectionBackground: "rgba(214, 93, 14, 0.3)",
|
||||||
selectionForeground: '#ebdbb2',
|
selectionForeground: "#ebdbb2",
|
||||||
black: '#282828',
|
black: "#282828",
|
||||||
red: '#cc241d',
|
red: "#cc241d",
|
||||||
green: '#98971a',
|
green: "#98971a",
|
||||||
yellow: '#d79921',
|
yellow: "#d79921",
|
||||||
blue: '#458588',
|
blue: "#458588",
|
||||||
magenta: '#b16286',
|
magenta: "#b16286",
|
||||||
cyan: '#689d6a',
|
cyan: "#689d6a",
|
||||||
white: '#a89984',
|
white: "#a89984",
|
||||||
brightBlack: '#928374',
|
brightBlack: "#928374",
|
||||||
brightRed: '#fb4934',
|
brightRed: "#fb4934",
|
||||||
brightGreen: '#b8bb26',
|
brightGreen: "#b8bb26",
|
||||||
brightYellow: '#fabd2f',
|
brightYellow: "#fabd2f",
|
||||||
brightBlue: '#83a598',
|
brightBlue: "#83a598",
|
||||||
brightMagenta: '#d3869b',
|
brightMagenta: "#d3869b",
|
||||||
brightCyan: '#8ec07c',
|
brightCyan: "#8ec07c",
|
||||||
brightWhite: '#ebdbb2',
|
brightWhite: "#ebdbb2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'tokyo-night': {
|
"tokyo-night": {
|
||||||
label: 'Tokyo Night',
|
label: "Tokyo Night",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#1a1b26',
|
background: "#1a1b26",
|
||||||
foreground: '#a9b1d6',
|
foreground: "#a9b1d6",
|
||||||
cursor: '#c0caf5',
|
cursor: "#c0caf5",
|
||||||
cursorAccent: '#1a1b26',
|
cursorAccent: "#1a1b26",
|
||||||
selectionBackground: 'rgba(41, 46, 66, 0.9)',
|
selectionBackground: "rgba(41, 46, 66, 0.9)",
|
||||||
selectionForeground: '#c0caf5',
|
selectionForeground: "#c0caf5",
|
||||||
black: '#15161e',
|
black: "#15161e",
|
||||||
red: '#f7768e',
|
red: "#f7768e",
|
||||||
green: '#9ece6a',
|
green: "#9ece6a",
|
||||||
yellow: '#e0af68',
|
yellow: "#e0af68",
|
||||||
blue: '#7aa2f7',
|
blue: "#7aa2f7",
|
||||||
magenta: '#bb9af7',
|
magenta: "#bb9af7",
|
||||||
cyan: '#7dcfff',
|
cyan: "#7dcfff",
|
||||||
white: '#a9b1d6',
|
white: "#a9b1d6",
|
||||||
brightBlack: '#414868',
|
brightBlack: "#414868",
|
||||||
brightRed: '#f7768e',
|
brightRed: "#f7768e",
|
||||||
brightGreen: '#9ece6a',
|
brightGreen: "#9ece6a",
|
||||||
brightYellow: '#e0af68',
|
brightYellow: "#e0af68",
|
||||||
brightBlue: '#7aa2f7',
|
brightBlue: "#7aa2f7",
|
||||||
brightMagenta: '#bb9af7',
|
brightMagenta: "#bb9af7",
|
||||||
brightCyan: '#7dcfff',
|
brightCyan: "#7dcfff",
|
||||||
brightWhite: '#c0caf5',
|
brightWhite: "#c0caf5",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'catppuccin-mocha': {
|
"catppuccin-mocha": {
|
||||||
label: 'Catppuccin Mocha',
|
label: "Catppuccin Mocha",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#1e1e2e',
|
background: "#1e1e2e",
|
||||||
foreground: '#cdd6f4',
|
foreground: "#cdd6f4",
|
||||||
cursor: '#f5e0dc',
|
cursor: "#f5e0dc",
|
||||||
cursorAccent: '#1e1e2e',
|
cursorAccent: "#1e1e2e",
|
||||||
selectionBackground: 'rgba(88, 91, 112, 0.5)',
|
selectionBackground: "rgba(88, 91, 112, 0.5)",
|
||||||
selectionForeground: '#cdd6f4',
|
selectionForeground: "#cdd6f4",
|
||||||
black: '#45475a',
|
black: "#45475a",
|
||||||
red: '#f38ba8',
|
red: "#f38ba8",
|
||||||
green: '#a6e3a1',
|
green: "#a6e3a1",
|
||||||
yellow: '#f9e2af',
|
yellow: "#f9e2af",
|
||||||
blue: '#89b4fa',
|
blue: "#89b4fa",
|
||||||
magenta: '#f5c2e7',
|
magenta: "#f5c2e7",
|
||||||
cyan: '#94e2d5',
|
cyan: "#94e2d5",
|
||||||
white: '#bac2de',
|
white: "#bac2de",
|
||||||
brightBlack: '#585b70',
|
brightBlack: "#585b70",
|
||||||
brightRed: '#f38ba8',
|
brightRed: "#f38ba8",
|
||||||
brightGreen: '#a6e3a1',
|
brightGreen: "#a6e3a1",
|
||||||
brightYellow: '#f9e2af',
|
brightYellow: "#f9e2af",
|
||||||
brightBlue: '#89b4fa',
|
brightBlue: "#89b4fa",
|
||||||
brightMagenta: '#f5c2e7',
|
brightMagenta: "#f5c2e7",
|
||||||
brightCyan: '#94e2d5',
|
brightCyan: "#94e2d5",
|
||||||
brightWhite: '#a6adc8',
|
brightWhite: "#a6adc8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'rose-pine': {
|
"rose-pine": {
|
||||||
label: 'Rosé Pine',
|
label: "Rosé Pine",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#191724',
|
background: "#191724",
|
||||||
foreground: '#e0def4',
|
foreground: "#e0def4",
|
||||||
cursor: '#524f67',
|
cursor: "#524f67",
|
||||||
cursorAccent: '#e0def4',
|
cursorAccent: "#e0def4",
|
||||||
selectionBackground: 'rgba(110, 106, 134, 0.4)',
|
selectionBackground: "rgba(110, 106, 134, 0.4)",
|
||||||
selectionForeground: '#e0def4',
|
selectionForeground: "#e0def4",
|
||||||
black: '#26233a',
|
black: "#26233a",
|
||||||
red: '#eb6f92',
|
red: "#eb6f92",
|
||||||
green: '#31748f',
|
green: "#31748f",
|
||||||
yellow: '#f6c177',
|
yellow: "#f6c177",
|
||||||
blue: '#9ccfd8',
|
blue: "#9ccfd8",
|
||||||
magenta: '#c4a7e7',
|
magenta: "#c4a7e7",
|
||||||
cyan: '#ebbcba',
|
cyan: "#ebbcba",
|
||||||
white: '#e0def4',
|
white: "#e0def4",
|
||||||
brightBlack: '#6e6a86',
|
brightBlack: "#6e6a86",
|
||||||
brightRed: '#eb6f92',
|
brightRed: "#eb6f92",
|
||||||
brightGreen: '#31748f',
|
brightGreen: "#31748f",
|
||||||
brightYellow: '#f6c177',
|
brightYellow: "#f6c177",
|
||||||
brightBlue: '#9ccfd8',
|
brightBlue: "#9ccfd8",
|
||||||
brightMagenta: '#c4a7e7',
|
brightMagenta: "#c4a7e7",
|
||||||
brightCyan: '#ebbcba',
|
brightCyan: "#ebbcba",
|
||||||
brightWhite: '#e0def4',
|
brightWhite: "#e0def4",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
'solarized-light': {
|
"solarized-light": {
|
||||||
label: 'Solarized Light',
|
label: "Solarized Light",
|
||||||
theme: {
|
theme: {
|
||||||
background: '#fdf6e3',
|
background: "#fdf6e3",
|
||||||
foreground: '#657b83',
|
foreground: "#657b83",
|
||||||
cursor: '#586e75',
|
cursor: "#586e75",
|
||||||
cursorAccent: '#fdf6e3',
|
cursorAccent: "#fdf6e3",
|
||||||
selectionBackground: 'rgba(7, 54, 66, 0.2)',
|
selectionBackground: "rgba(7, 54, 66, 0.2)",
|
||||||
selectionForeground: '#073642',
|
selectionForeground: "#073642",
|
||||||
black: '#073642',
|
black: "#073642",
|
||||||
red: '#dc322f',
|
red: "#dc322f",
|
||||||
green: '#859900',
|
green: "#859900",
|
||||||
yellow: '#b58900',
|
yellow: "#b58900",
|
||||||
blue: '#268bd2',
|
blue: "#268bd2",
|
||||||
magenta: '#d33682',
|
magenta: "#d33682",
|
||||||
cyan: '#2aa198',
|
cyan: "#2aa198",
|
||||||
white: '#eee8d5',
|
white: "#eee8d5",
|
||||||
brightBlack: '#002b36',
|
brightBlack: "#002b36",
|
||||||
brightRed: '#cb4b16',
|
brightRed: "#cb4b16",
|
||||||
brightGreen: '#586e75',
|
brightGreen: "#586e75",
|
||||||
brightYellow: '#657b83',
|
brightYellow: "#657b83",
|
||||||
brightBlue: '#839496',
|
brightBlue: "#839496",
|
||||||
brightMagenta: '#6c71c4',
|
brightMagenta: "#6c71c4",
|
||||||
brightCyan: '#93a1a1',
|
brightCyan: "#93a1a1",
|
||||||
brightWhite: '#fdf6e3',
|
brightWhite: "#fdf6e3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the theme object for the given theme name.
|
* Returns the theme object for the given theme name.
|
||||||
* Falls back to 'charcoal' if not found.
|
* Falls back to 'charcoal' if not found.
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
* @returns {Object} xterm.js theme object
|
* @returns {Object} xterm.js theme object
|
||||||
*/
|
*/
|
||||||
function getTerminalTheme(name) {
|
function getTerminalTheme(name) {
|
||||||
const entry = THEMES[name || 'charcoal'];
|
const entry = THEMES[name || "charcoal"];
|
||||||
return entry ? entry.theme : THEMES.charcoal.theme;
|
return entry ? entry.theme : THEMES.charcoal.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all theme names and labels for building a selector.
|
* Returns all theme names and labels for building a selector.
|
||||||
* @returns {Array<{value: string, label: string}>}
|
* @returns {Array<{value: string, label: string}>}
|
||||||
*/
|
*/
|
||||||
function listTerminalThemes() {
|
function listTerminalThemes() {
|
||||||
return Object.entries(THEMES).map(([value, entry]) => ({
|
return Object.entries(THEMES).map(([value, entry]) => ({
|
||||||
value,
|
value,
|
||||||
label: entry.label,
|
label: entry.label,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
window.NexusTerminal = window.NexusTerminal || {};
|
|
||||||
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
|
|
||||||
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
|
|
||||||
|
|
||||||
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
|
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
|
||||||
|
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
|
||||||
})();
|
})();
|
||||||
|
|||||||
+37
-16
@@ -92,7 +92,9 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const haystack = rule.caseSensitive ? clean : clean.toLowerCase();
|
const haystack = rule.caseSensitive ? clean : clean.toLowerCase();
|
||||||
const needle = rule.caseSensitive ? rule.pattern : rule.pattern.toLowerCase();
|
const needle = rule.caseSensitive
|
||||||
|
? rule.pattern
|
||||||
|
: rule.pattern.toLowerCase();
|
||||||
matched = haystack.includes(needle);
|
matched = haystack.includes(needle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,13 +103,19 @@
|
|||||||
// Fire actions
|
// Fire actions
|
||||||
if (rule.action === "notify" || rule.action === "both") {
|
if (rule.action === "notify" || rule.action === "both") {
|
||||||
const session = this.app.state.getSession(sessionId);
|
const session = this.app.state.getSession(sessionId);
|
||||||
const label = session ? (session.tabName || session.profileName) : sessionId;
|
const label = session
|
||||||
this.app.notifications.info(`[${label}] Trigger: ${rule.name || rule.pattern}`);
|
? session.tabName || session.profileName
|
||||||
|
: sessionId;
|
||||||
|
this.app.notifications.info(
|
||||||
|
`[${label}] Trigger: ${rule.name || rule.pattern}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rule.action === "highlight" || rule.action === "both") {
|
if (rule.action === "highlight" || rule.action === "both") {
|
||||||
// Flash the tab briefly
|
// Flash the tab briefly
|
||||||
const tabEl = document.querySelector(`.tab[data-session-id="${sessionId}"]`);
|
const tabEl = document.querySelector(
|
||||||
|
`.tab[data-session-id="${sessionId}"]`,
|
||||||
|
);
|
||||||
if (tabEl) {
|
if (tabEl) {
|
||||||
tabEl.classList.add("tab-trigger-flash");
|
tabEl.classList.add("tab-trigger-flash");
|
||||||
setTimeout(() => tabEl.classList.remove("tab-trigger-flash"), 2000);
|
setTimeout(() => tabEl.classList.remove("tab-trigger-flash"), 2000);
|
||||||
@@ -186,11 +194,15 @@
|
|||||||
modal.classList.add("is-visible");
|
modal.classList.add("is-visible");
|
||||||
});
|
});
|
||||||
|
|
||||||
modal.querySelector(".modal-close").addEventListener("click", () => this.close());
|
modal
|
||||||
|
.querySelector(".modal-close")
|
||||||
|
.addEventListener("click", () => this.close());
|
||||||
backdrop.addEventListener("click", (e) => {
|
backdrop.addEventListener("click", (e) => {
|
||||||
if (e.target === backdrop) this.close();
|
if (e.target === backdrop) this.close();
|
||||||
});
|
});
|
||||||
modal.querySelector(".trigger-add-btn").addEventListener("click", () => this._addRule());
|
modal
|
||||||
|
.querySelector(".trigger-add-btn")
|
||||||
|
.addEventListener("click", () => this._addRule());
|
||||||
|
|
||||||
this._renderList();
|
this._renderList();
|
||||||
}
|
}
|
||||||
@@ -201,7 +213,8 @@
|
|||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
|
|
||||||
if (this._rules.length === 0) {
|
if (this._rules.length === 0) {
|
||||||
list.innerHTML = '<div class="trigger-empty">No trigger rules defined</div>';
|
list.innerHTML =
|
||||||
|
'<div class="trigger-empty">No trigger rules defined</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,11 +249,15 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const actions = createElement("div", "trigger-actions");
|
const actions = createElement("div", "trigger-actions");
|
||||||
const editBtn = createElement("button", "btn btn-xs", { textContent: "Edit" });
|
const editBtn = createElement("button", "btn btn-xs", {
|
||||||
|
textContent: "Edit",
|
||||||
|
});
|
||||||
editBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
editBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||||
editBtn.addEventListener("click", () => this._editRule(index));
|
editBtn.addEventListener("click", () => this._editRule(index));
|
||||||
|
|
||||||
const delBtn = createElement("button", "btn btn-xs btn-danger", { textContent: "✕" });
|
const delBtn = createElement("button", "btn btn-xs btn-danger", {
|
||||||
|
textContent: "✕",
|
||||||
|
});
|
||||||
delBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
delBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||||
delBtn.addEventListener("click", () => {
|
delBtn.addEventListener("click", () => {
|
||||||
this._rules.splice(index, 1);
|
this._rules.splice(index, 1);
|
||||||
@@ -287,7 +304,7 @@
|
|||||||
form.innerHTML = `
|
form.innerHTML = `
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Name (optional)</label>
|
<label class="form-label">Name (optional)</label>
|
||||||
<input type="text" class="form-input trigger-name-input" placeholder="e.g. Error alert" value="${existing ? (existing.name || "") : ""}" />
|
<input type="text" class="form-input trigger-name-input" placeholder="e.g. Error alert" value="${existing ? existing.name || "" : ""}" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="form-label">Pattern</label>
|
<label class="form-label">Pattern</label>
|
||||||
@@ -325,14 +342,18 @@
|
|||||||
|
|
||||||
list.parentNode.insertBefore(form, list);
|
list.parentNode.insertBefore(form, list);
|
||||||
|
|
||||||
form.querySelector(".trigger-cancel-btn").addEventListener("click", () => {
|
form
|
||||||
form.remove();
|
.querySelector(".trigger-cancel-btn")
|
||||||
list.style.display = "";
|
.addEventListener("click", () => {
|
||||||
if (toolbar) toolbar.style.display = "";
|
form.remove();
|
||||||
});
|
list.style.display = "";
|
||||||
|
if (toolbar) toolbar.style.display = "";
|
||||||
|
});
|
||||||
|
|
||||||
form.querySelector(".trigger-save-btn").addEventListener("click", () => {
|
form.querySelector(".trigger-save-btn").addEventListener("click", () => {
|
||||||
const pattern = form.querySelector(".trigger-pattern-input").value.trim();
|
const pattern = form
|
||||||
|
.querySelector(".trigger-pattern-input")
|
||||||
|
.value.trim();
|
||||||
if (!pattern) {
|
if (!pattern) {
|
||||||
this.app.notifications.error("Pattern is required");
|
this.app.notifications.error("Pattern is required");
|
||||||
return;
|
return;
|
||||||
|
|||||||
+255
-212
@@ -1,120 +1,132 @@
|
|||||||
(function() {
|
(function () {
|
||||||
/**
|
/**
|
||||||
* Nexus Terminal — utility functions.
|
* Nexus Terminal — utility functions.
|
||||||
* General-purpose helpers used throughout the application.
|
* General-purpose helpers used throughout the application.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand for document.querySelector.
|
* Shorthand for document.querySelector.
|
||||||
* @param {string} selector - CSS selector.
|
* @param {string} selector - CSS selector.
|
||||||
* @returns {Element|null}
|
* @returns {Element|null}
|
||||||
*/
|
*/
|
||||||
function $(selector) {
|
function $(selector) {
|
||||||
return document.querySelector(selector);
|
return document.querySelector(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand for document.querySelectorAll.
|
* Shorthand for document.querySelectorAll.
|
||||||
* @param {string} selector - CSS selector.
|
* @param {string} selector - CSS selector.
|
||||||
* @returns {NodeListOf<Element>}
|
* @returns {NodeListOf<Element>}
|
||||||
*/
|
*/
|
||||||
function $$(selector) {
|
function $$(selector) {
|
||||||
return document.querySelectorAll(selector);
|
return document.querySelectorAll(selector);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a DOM element with optional class name and attributes.
|
* Creates a DOM element with optional class name and attributes.
|
||||||
* @param {string} tag - HTML tag name.
|
* @param {string} tag - HTML tag name.
|
||||||
* @param {string} [className] - Space-separated class names.
|
* @param {string} [className] - Space-separated class names.
|
||||||
* @param {Object} [attrs] - Attribute key/value pairs.
|
* @param {Object} [attrs] - Attribute key/value pairs.
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function createElement(tag, className, attrs = {}) {
|
function createElement(tag, className, attrs = {}) {
|
||||||
const el = document.createElement(tag);
|
const el = document.createElement(tag);
|
||||||
if (className) {
|
if (className) {
|
||||||
el.className = className;
|
el.className = className;
|
||||||
}
|
}
|
||||||
for (const [key, value] of Object.entries(attrs)) {
|
for (const [key, value] of Object.entries(attrs)) {
|
||||||
if (key === 'textContent') {
|
if (key === "textContent") {
|
||||||
el.textContent = value;
|
el.textContent = value;
|
||||||
} else if (key === 'innerHTML') {
|
} else if (key === "innerHTML") {
|
||||||
el.innerHTML = value;
|
el.innerHTML = value;
|
||||||
} else if (key === 'dataset') {
|
} else if (key === "dataset") {
|
||||||
for (const [dKey, dVal] of Object.entries(value)) {
|
for (const [dKey, dVal] of Object.entries(value)) {
|
||||||
el.dataset[dKey] = dVal;
|
el.dataset[dKey] = dVal;
|
||||||
}
|
|
||||||
} else if (key.startsWith('on') && typeof value === 'function') {
|
|
||||||
el.addEventListener(key.slice(2).toLowerCase(), value);
|
|
||||||
} else {
|
|
||||||
el.setAttribute(key, value);
|
|
||||||
}
|
}
|
||||||
|
} else if (key.startsWith("on") && typeof value === "function") {
|
||||||
|
el.addEventListener(key.slice(2).toLowerCase(), value);
|
||||||
|
} else {
|
||||||
|
el.setAttribute(key, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escapes HTML special characters to prevent XSS.
|
* Escapes HTML special characters to prevent XSS.
|
||||||
* @param {string} str - Raw string.
|
* @param {string} str - Raw string.
|
||||||
* @returns {string} Escaped string safe for innerHTML.
|
* @returns {string} Escaped string safe for innerHTML.
|
||||||
*/
|
*/
|
||||||
function escapeHtml(str) {
|
function escapeHtml(str) {
|
||||||
if (!str) return '';
|
if (!str) return "";
|
||||||
const map = {
|
const map = {
|
||||||
'&': '&',
|
"&": "&",
|
||||||
'<': '<',
|
"<": "<",
|
||||||
'>': '>',
|
">": ">",
|
||||||
'"': '"',
|
'"': """,
|
||||||
"'": ''',
|
"'": "'",
|
||||||
};
|
};
|
||||||
return str.replace(/[&<>"']/g, (ch) => map[ch]);
|
return str.replace(/[&<>"']/g, (ch) => map[ch]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a Date (or ISO string) into a concise timestamp.
|
* Formats a Date (or ISO string) into a concise timestamp.
|
||||||
* @param {Date|string} date
|
* @param {Date|string} date
|
||||||
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
|
||||||
*/
|
*/
|
||||||
function formatTimestamp(date) {
|
function formatTimestamp(date) {
|
||||||
if (!date) return '';
|
if (!date) return "";
|
||||||
const d = date instanceof Date ? date : new Date(date);
|
const d = date instanceof Date ? date : new Date(date);
|
||||||
if (isNaN(d.getTime())) return '';
|
if (isNaN(d.getTime())) return "";
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const isToday = d.toDateString() === now.toDateString();
|
const isToday = d.toDateString() === now.toDateString();
|
||||||
const hours = String(d.getHours()).padStart(2, '0');
|
const hours = String(d.getHours()).padStart(2, "0");
|
||||||
const mins = String(d.getMinutes()).padStart(2, '0');
|
const mins = String(d.getMinutes()).padStart(2, "0");
|
||||||
const secs = String(d.getSeconds()).padStart(2, '0');
|
const secs = String(d.getSeconds()).padStart(2, "0");
|
||||||
|
|
||||||
if (isToday) {
|
if (isToday) {
|
||||||
return `${hours}:${mins}:${secs}`;
|
return `${hours}:${mins}:${secs}`;
|
||||||
}
|
}
|
||||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
const months = [
|
||||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
"Jan",
|
||||||
|
"Feb",
|
||||||
|
"Mar",
|
||||||
|
"Apr",
|
||||||
|
"May",
|
||||||
|
"Jun",
|
||||||
|
"Jul",
|
||||||
|
"Aug",
|
||||||
|
"Sep",
|
||||||
|
"Oct",
|
||||||
|
"Nov",
|
||||||
|
"Dec",
|
||||||
|
];
|
||||||
return `${d.getDate()} ${months[d.getMonth()]} ${hours}:${mins}`;
|
return `${d.getDate()} ${months[d.getMonth()]} ${hours}:${mins}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a debounced version of the given function.
|
* Returns a debounced version of the given function.
|
||||||
* @param {Function} fn
|
* @param {Function} fn
|
||||||
* @param {number} ms - Delay in milliseconds.
|
* @param {number} ms - Delay in milliseconds.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
function debounce(fn, ms) {
|
function debounce(fn, ms) {
|
||||||
let timer = null;
|
let timer = null;
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
timer = setTimeout(() => fn.apply(this, args), ms);
|
timer = setTimeout(() => fn.apply(this, args), ms);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a simple fuzzy match — checks whether every character in
|
* Performs a simple fuzzy match — checks whether every character in
|
||||||
* the query appears in order within the text (case-insensitive).
|
* the query appears in order within the text (case-insensitive).
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {string} query
|
* @param {string} query
|
||||||
* @returns {{ match: boolean, score: number }}
|
* @returns {{ match: boolean, score: number }}
|
||||||
*/
|
*/
|
||||||
function fuzzyMatch(text, query) {
|
function fuzzyMatch(text, query) {
|
||||||
if (!query) return { match: true, score: 1 };
|
if (!query) return { match: true, score: 1 };
|
||||||
const lowerText = text.toLowerCase();
|
const lowerText = text.toLowerCase();
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
@@ -124,169 +136,200 @@ function fuzzyMatch(text, query) {
|
|||||||
let consecutiveBonus = 0;
|
let consecutiveBonus = 0;
|
||||||
|
|
||||||
for (let qi = 0; qi < lowerQuery.length; qi++) {
|
for (let qi = 0; qi < lowerQuery.length; qi++) {
|
||||||
const ch = lowerQuery[qi];
|
const ch = lowerQuery[qi];
|
||||||
const foundAt = lowerText.indexOf(ch, textIdx);
|
const foundAt = lowerText.indexOf(ch, textIdx);
|
||||||
if (foundAt === -1) {
|
if (foundAt === -1) {
|
||||||
return { match: false, score: 0 };
|
return { match: false, score: 0 };
|
||||||
}
|
}
|
||||||
// Reward consecutive character matches and early matches
|
// Reward consecutive character matches and early matches
|
||||||
if (foundAt === textIdx) {
|
if (foundAt === textIdx) {
|
||||||
consecutiveBonus += 1;
|
consecutiveBonus += 1;
|
||||||
} else {
|
} else {
|
||||||
consecutiveBonus = 0;
|
consecutiveBonus = 0;
|
||||||
}
|
}
|
||||||
score += 1 + consecutiveBonus + (foundAt === 0 ? 2 : 0);
|
score += 1 + consecutiveBonus + (foundAt === 0 ? 2 : 0);
|
||||||
textIdx = foundAt + 1;
|
textIdx = foundAt + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { match: true, score };
|
return { match: true, score };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64-encodes a string.
|
* Base64-encodes a string.
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function base64Encode(str) {
|
function base64Encode(str) {
|
||||||
return btoa(str);
|
return btoa(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64-decodes a string.
|
* Base64-decodes a string.
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function base64Decode(str) {
|
function base64Decode(str) {
|
||||||
return atob(str);
|
return atob(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new UUID v4 using the browser crypto API.
|
* Generates a new UUID v4 using the browser crypto API.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function generateId() {
|
function generateId() {
|
||||||
return crypto.randomUUID();
|
return crypto.randomUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats a byte count into a human-friendly string.
|
* Formats a byte count into a human-friendly string.
|
||||||
* @param {number} bytes
|
* @param {number} bytes
|
||||||
* @returns {string} e.g. "1.23 KB"
|
* @returns {string} e.g. "1.23 KB"
|
||||||
*/
|
*/
|
||||||
function formatBytes(bytes) {
|
function formatBytes(bytes) {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return "0 B";
|
||||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||||
const k = 1024;
|
const k = 1024;
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
const value = bytes / Math.pow(k, i);
|
const value = bytes / Math.pow(k, i);
|
||||||
return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;
|
return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Truncates a string to the given length, appending an ellipsis if needed.
|
* Truncates a string to the given length, appending an ellipsis if needed.
|
||||||
* @param {string} str
|
* @param {string} str
|
||||||
* @param {number} len
|
* @param {number} len
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function truncate(str, len) {
|
function truncate(str, len) {
|
||||||
if (!str || str.length <= len) return str || '';
|
if (!str || str.length <= len) return str || "";
|
||||||
return str.slice(0, len) + '…';
|
return str.slice(0, len) + "…";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes any open context menus from the DOM.
|
* Removes any open context menus from the DOM.
|
||||||
*/
|
*/
|
||||||
function dismissContextMenus() {
|
function dismissContextMenus() {
|
||||||
document.querySelectorAll('.context-menu').forEach((m) => m.remove());
|
document.querySelectorAll(".context-menu").forEach((m) => m.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the protocol badge colour class.
|
* Returns the protocol badge colour class.
|
||||||
* @param {string} protocol - 'ssh', 'telnet', 'raw', or 'github'.
|
* @param {string} protocol - 'ssh', 'telnet', 'raw', or 'github'.
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function protocolColour(protocol) {
|
function protocolColour(protocol) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
case 'ssh': return 'proto-ssh';
|
case "ssh":
|
||||||
case 'telnet': return 'proto-telnet';
|
return "proto-ssh";
|
||||||
case 'raw': return 'proto-raw';
|
case "telnet":
|
||||||
case 'github': return 'proto-github';
|
return "proto-telnet";
|
||||||
case 'vnc': return 'proto-vnc';
|
case "raw":
|
||||||
default: return '';
|
return "proto-raw";
|
||||||
|
case "github":
|
||||||
|
return "proto-github";
|
||||||
|
case "vnc":
|
||||||
|
return "proto-vnc";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a human-friendly relative time string for an ISO date.
|
* Returns a human-friendly relative time string for an ISO date.
|
||||||
* @param {string} isoDate - ISO 8601 date string.
|
* @param {string} isoDate - ISO 8601 date string.
|
||||||
* @returns {string} e.g. "3 hours ago", "2 days ago".
|
* @returns {string} e.g. "3 hours ago", "2 days ago".
|
||||||
*/
|
*/
|
||||||
function relativeTime(isoDate) {
|
function relativeTime(isoDate) {
|
||||||
if (!isoDate) return '';
|
if (!isoDate) return "";
|
||||||
const d = new Date(isoDate);
|
const d = new Date(isoDate);
|
||||||
if (isNaN(d.getTime())) return '';
|
if (isNaN(d.getTime())) return "";
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const diff = now - d.getTime();
|
const diff = now - d.getTime();
|
||||||
const secs = Math.floor(diff / 1000);
|
const secs = Math.floor(diff / 1000);
|
||||||
if (secs < 60) return 'just now';
|
if (secs < 60) return "just now";
|
||||||
const mins = Math.floor(secs / 60);
|
const mins = Math.floor(secs / 60);
|
||||||
if (mins < 60) return mins === 1 ? '1 minute ago' : `${mins} minutes ago`;
|
if (mins < 60) return mins === 1 ? "1 minute ago" : `${mins} minutes ago`;
|
||||||
const hrs = Math.floor(mins / 60);
|
const hrs = Math.floor(mins / 60);
|
||||||
if (hrs < 24) return hrs === 1 ? '1 hour ago' : `${hrs} hours ago`;
|
if (hrs < 24) return hrs === 1 ? "1 hour ago" : `${hrs} hours ago`;
|
||||||
const days = Math.floor(hrs / 24);
|
const days = Math.floor(hrs / 24);
|
||||||
if (days < 30) return days === 1 ? '1 day ago' : `${days} days ago`;
|
if (days < 30) return days === 1 ? "1 day ago" : `${days} days ago`;
|
||||||
const months = Math.floor(days / 30);
|
const months = Math.floor(days / 30);
|
||||||
if (months < 12) return months === 1 ? '1 month ago' : `${months} months ago`;
|
if (months < 12)
|
||||||
|
return months === 1 ? "1 month ago" : `${months} months ago`;
|
||||||
const years = Math.floor(months / 12);
|
const years = Math.floor(months / 12);
|
||||||
return years === 1 ? '1 year ago' : `${years} years ago`;
|
return years === 1 ? "1 year ago" : `${years} years ago`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a human-friendly label for a session state.
|
* Returns a human-friendly label for a session state.
|
||||||
* @param {string} state
|
* @param {string} state
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function stateLabel(state) {
|
function stateLabel(state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case 'connecting': return 'Connecting';
|
case "connecting":
|
||||||
case 'connected': return 'Connected';
|
return "Connecting";
|
||||||
case 'disconnected': return 'Disconnected';
|
case "connected":
|
||||||
case 'reconnecting': return 'Reconnecting';
|
return "Connected";
|
||||||
case 'error': return 'Error';
|
case "disconnected":
|
||||||
default: return state || 'Unknown';
|
return "Disconnected";
|
||||||
|
case "reconnecting":
|
||||||
|
return "Reconnecting";
|
||||||
|
case "error":
|
||||||
|
return "Error";
|
||||||
|
default:
|
||||||
|
return state || "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.NexusTerminal = window.NexusTerminal || {};
|
window.NexusTerminal = window.NexusTerminal || {};
|
||||||
Object.assign(window.NexusTerminal, { $, $$, createElement, escapeHtml, formatTimestamp, debounce, fuzzyMatch, base64Encode, base64Decode, generateId, formatBytes, truncate, dismissContextMenus, protocolColour, stateLabel, relativeTime });
|
Object.assign(window.NexusTerminal, {
|
||||||
|
$,
|
||||||
|
$$,
|
||||||
|
createElement,
|
||||||
|
escapeHtml,
|
||||||
|
formatTimestamp,
|
||||||
|
debounce,
|
||||||
|
fuzzyMatch,
|
||||||
|
base64Encode,
|
||||||
|
base64Decode,
|
||||||
|
generateId,
|
||||||
|
formatBytes,
|
||||||
|
truncate,
|
||||||
|
dismissContextMenus,
|
||||||
|
protocolColour,
|
||||||
|
stateLabel,
|
||||||
|
relativeTime,
|
||||||
|
});
|
||||||
|
|
||||||
/* ── Disable macOS/WebKit auto-capitalisation on all text inputs ───────── */
|
/* ── Disable macOS/WebKit auto-capitalisation on all text inputs ───────── */
|
||||||
function disableAutoCapitalise(el) {
|
function disableAutoCapitalise(el) {
|
||||||
el.setAttribute('autocapitalize', 'none');
|
el.setAttribute("autocapitalize", "none");
|
||||||
el.setAttribute('autocorrect', 'off');
|
el.setAttribute("autocorrect", "off");
|
||||||
el.setAttribute('spellcheck', 'false');
|
el.setAttribute("spellcheck", "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply to existing inputs
|
// Apply to existing inputs
|
||||||
document.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
|
document.querySelectorAll("input, textarea").forEach(disableAutoCapitalise);
|
||||||
|
|
||||||
// Observe for dynamically added inputs
|
// Observe for dynamically added inputs
|
||||||
var inputObserver = new MutationObserver(function(mutations) {
|
var inputObserver = new MutationObserver(function (mutations) {
|
||||||
for (var i = 0; i < mutations.length; i++) {
|
for (var i = 0; i < mutations.length; i++) {
|
||||||
var nodes = mutations[i].addedNodes;
|
var nodes = mutations[i].addedNodes;
|
||||||
for (var j = 0; j < nodes.length; j++) {
|
for (var j = 0; j < nodes.length; j++) {
|
||||||
var node = nodes[j];
|
var node = nodes[j];
|
||||||
if (node.nodeType !== 1) continue;
|
if (node.nodeType !== 1) continue;
|
||||||
if (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
|
if (node.tagName === "INPUT" || node.tagName === "TEXTAREA") {
|
||||||
disableAutoCapitalise(node);
|
disableAutoCapitalise(node);
|
||||||
}
|
|
||||||
if (node.querySelectorAll) {
|
|
||||||
node.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (node.querySelectorAll) {
|
||||||
|
node
|
||||||
|
.querySelectorAll("input, textarea")
|
||||||
|
.forEach(disableAutoCapitalise);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
inputObserver.observe(document.body, { childList: true, subtree: true });
|
inputObserver.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
+16
-8
@@ -91,16 +91,24 @@
|
|||||||
});
|
});
|
||||||
ctrlAltDel.addEventListener("click", () => this._sendCtrlAltDel());
|
ctrlAltDel.addEventListener("click", () => this._sendCtrlAltDel());
|
||||||
|
|
||||||
const clipboardBtn = createElement("button", "btn btn-sm btn-vnc-action", {
|
const clipboardBtn = createElement(
|
||||||
textContent: "Clipboard",
|
"button",
|
||||||
title: "Sync clipboard",
|
"btn btn-sm btn-vnc-action",
|
||||||
});
|
{
|
||||||
|
textContent: "Clipboard",
|
||||||
|
title: "Sync clipboard",
|
||||||
|
},
|
||||||
|
);
|
||||||
clipboardBtn.addEventListener("click", () => this._syncClipboard());
|
clipboardBtn.addEventListener("click", () => this._syncClipboard());
|
||||||
|
|
||||||
const fullscreenBtn = createElement("button", "btn btn-sm btn-vnc-action", {
|
const fullscreenBtn = createElement(
|
||||||
textContent: "Fullscreen",
|
"button",
|
||||||
title: "Toggle fullscreen",
|
"btn btn-sm btn-vnc-action",
|
||||||
});
|
{
|
||||||
|
textContent: "Fullscreen",
|
||||||
|
title: "Toggle fullscreen",
|
||||||
|
},
|
||||||
|
);
|
||||||
fullscreenBtn.addEventListener("click", () => this._toggleFullscreen());
|
fullscreenBtn.addEventListener("click", () => this._toggleFullscreen());
|
||||||
|
|
||||||
this.statusEl = createElement("span", "vnc-status", {
|
this.statusEl = createElement("span", "vnc-status", {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
-- Migration 004: Add VNC protocol support
|
-- Migration 004: Add VNC protocol support
|
||||||
-- Adds the 'vnc' value to the protocol CHECK constraint and a vnc_config column.
|
-- Adds the 'vnc' value to the protocol CHECK constraint and a vnc_config column.
|
||||||
|
|
||||||
-- Step 1: Create the new table with the updated constraint.
|
-- Step 1: Create the new table with the updated constraint.
|
||||||
CREATE TABLE IF NOT EXISTS profiles_new (
|
CREATE TABLE IF NOT EXISTS profiles_new (
|
||||||
id TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
display_name TEXT NOT NULL,
|
display_name TEXT NOT NULL,
|
||||||
description TEXT DEFAULT '',
|
description TEXT DEFAULT '',
|
||||||
protocol TEXT NOT NULL CHECK(protocol IN ('ssh', 'telnet', 'raw', 'github', 'vnc')),
|
protocol TEXT NOT NULL CHECK(
|
||||||
|
protocol IN ('ssh', 'telnet', 'raw', 'github', 'vnc')
|
||||||
|
),
|
||||||
host TEXT NOT NULL,
|
host TEXT NOT NULL,
|
||||||
port INTEGER NOT NULL,
|
port INTEGER NOT NULL,
|
||||||
username TEXT DEFAULT '',
|
username TEXT DEFAULT '',
|
||||||
@@ -32,26 +33,73 @@ CREATE TABLE IF NOT EXISTS profiles_new (
|
|||||||
);
|
);
|
||||||
|
|
||||||
-- Step 2: Copy existing data (vnc_config will be NULL for legacy rows).
|
-- Step 2: Copy existing data (vnc_config will be NULL for legacy rows).
|
||||||
INSERT OR IGNORE INTO profiles_new (
|
INSERT
|
||||||
id, display_name, description, protocol, host, port, username,
|
OR IGNORE INTO profiles_new (
|
||||||
tags, folder, environment, notes, favourite, last_connected,
|
id,
|
||||||
connect_timeout, read_timeout,
|
display_name,
|
||||||
terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style,
|
description,
|
||||||
ssh_config, telnet_config, raw_config, github_config, vnc_config,
|
protocol,
|
||||||
created_at, updated_at
|
host,
|
||||||
)
|
port,
|
||||||
|
username,
|
||||||
|
tags,
|
||||||
|
folder,
|
||||||
|
environment,
|
||||||
|
notes,
|
||||||
|
favourite,
|
||||||
|
last_connected,
|
||||||
|
connect_timeout,
|
||||||
|
read_timeout,
|
||||||
|
terminal_font_family,
|
||||||
|
terminal_font_size,
|
||||||
|
terminal_scrollback,
|
||||||
|
terminal_cursor_style,
|
||||||
|
ssh_config,
|
||||||
|
telnet_config,
|
||||||
|
raw_config,
|
||||||
|
github_config,
|
||||||
|
vnc_config,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
id, display_name, description, protocol, host, port, username,
|
id,
|
||||||
tags, folder, environment, notes, favourite, last_connected,
|
display_name,
|
||||||
connect_timeout, read_timeout,
|
description,
|
||||||
terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style,
|
protocol,
|
||||||
ssh_config, telnet_config, raw_config, github_config, NULL,
|
host,
|
||||||
created_at, updated_at
|
port,
|
||||||
FROM profiles;
|
username,
|
||||||
|
tags,
|
||||||
|
folder,
|
||||||
|
environment,
|
||||||
|
notes,
|
||||||
|
favourite,
|
||||||
|
last_connected,
|
||||||
|
connect_timeout,
|
||||||
|
read_timeout,
|
||||||
|
terminal_font_family,
|
||||||
|
terminal_font_size,
|
||||||
|
terminal_scrollback,
|
||||||
|
terminal_cursor_style,
|
||||||
|
ssh_config,
|
||||||
|
telnet_config,
|
||||||
|
raw_config,
|
||||||
|
github_config,
|
||||||
|
NULL,
|
||||||
|
created_at,
|
||||||
|
updated_at
|
||||||
|
FROM
|
||||||
|
profiles;
|
||||||
|
|
||||||
-- Step 3: Drop old table and rename.
|
-- Step 3: Drop old table and rename.
|
||||||
DROP TABLE profiles;
|
DROP TABLE profiles;
|
||||||
ALTER TABLE profiles_new RENAME TO profiles;
|
|
||||||
|
ALTER TABLE
|
||||||
|
profiles_new RENAME TO profiles;
|
||||||
|
|
||||||
-- Step 4: Record the migration version.
|
-- Step 4: Record the migration version.
|
||||||
INSERT INTO schema_version (version, applied_at) VALUES (4, datetime('now'));
|
INSERT INTO
|
||||||
|
schema_version (version, applied_at)
|
||||||
|
VALUES
|
||||||
|
(4, datetime('now'));
|
||||||
Reference in New Issue
Block a user