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:
Andy Dixon
2026-05-21 12:53:08 +01:00
parent 96fedb9348
commit 3ff04feb92
12 changed files with 891 additions and 712 deletions
+11 -2
View File
@@ -11,7 +11,14 @@ const esbuild = require("esbuild");
const path = require("path");
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
.build({
@@ -26,7 +33,9 @@ esbuild
footer: { js: "})();" },
})
.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) => {
console.error("[bundle-novnc] Failed:", err.message);
+13 -4
View File
@@ -191,10 +191,19 @@
}
@keyframes trigger-flash {
0%, 100% { background: var(--bg-hover); }
15% { background: rgba(239, 83, 80, 0.25); }
30% { background: var(--bg-hover); }
45% { background: rgba(239, 83, 80, 0.15); }
0%,
100% {
background: var(--bg-hover);
}
15% {
background: rgba(239, 83, 80, 0.25);
}
30% {
background: var(--bg-hover);
}
45% {
background: rgba(239, 83, 80, 0.15);
}
}
/* -----------------------------------------------------------------------
+12 -9
View File
@@ -76,10 +76,11 @@
/* -----------------------------------------------------------------------
Typography
----------------------------------------------------------------------- */
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI',
system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'SF Mono', Monaco,
'Cascadia Code', monospace;
--font-sans:
"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui,
sans-serif;
--font-mono:
"JetBrains Mono", "Fira Code", "SF Mono", Monaco, "Cascadia Code", monospace;
--text-xs: 11px;
--text-sm: 12px;
@@ -109,10 +110,10 @@
/* -----------------------------------------------------------------------
Shadows
----------------------------------------------------------------------- */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.30);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.40);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.50);
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.60);
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5);
--shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.6);
/* -----------------------------------------------------------------------
Transitions
@@ -146,7 +147,9 @@
Global input overrides — disable macOS/WebKit auto-capitalisation,
autocorrect, and spellcheck on all text fields.
----------------------------------------------------------------------- */
input, textarea, select {
input,
textarea,
select {
autocapitalize: none;
autocorrect: off;
spellcheck: false;
+3 -1
View File
@@ -40,7 +40,9 @@
border: 1px solid var(--border-subtle);
color: var(--text-secondary);
cursor: pointer;
transition: background 0.15s, color 0.15s;
transition:
background 0.15s,
color 0.15s;
}
.btn-vnc-action:hover {
+6 -2
View File
@@ -168,7 +168,9 @@
try {
const raw = atob(payload.data);
this.triggers.check(payload.sessionID, raw);
} catch (_) { /* noop */ }
} catch (_) {
/* noop */
}
});
// Session state transitions (connecting → connected → reconnecting → disconnected)
@@ -529,7 +531,9 @@
setupUI() {
var btnEl = document.getElementById("btn-new-connection");
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-import", () => this.importProfiles());
this._bindClick("btn-export", () => this.exportProfiles());
+9 -5
View File
@@ -206,14 +206,16 @@
);
break;
case "vnc":
this._renderVNCSections(profile).forEach((s) =>
body.appendChild(s),
);
this._renderVNCSections(profile).forEach((s) => body.appendChild(s));
break;
}
// 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));
}
@@ -2288,7 +2290,9 @@
if (profile.protocol === "vnc") {
const vncHost = (profile.vnc && profile.vnc.vncHost) || profile.host;
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()) {
errors.push("Host is required.");
+42 -13
View File
@@ -63,11 +63,15 @@
});
// Events
modal.querySelector(".modal-close").addEventListener("click", () => this.close());
modal
.querySelector(".modal-close")
.addEventListener("click", () => this.close());
backdrop.addEventListener("click", (e) => {
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() {
@@ -79,7 +83,8 @@
try {
const keys = await window.go.api.Service.ListSSHKeyPairs();
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;
}
list.innerHTML = "";
@@ -95,7 +100,9 @@
const row = createElement("div", "sshkey-row");
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", {
textContent: key.type.toUpperCase() + (key.bits ? ` ${key.bits}` : ""),
});
@@ -104,11 +111,15 @@
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("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("click", () => this._confirmDelete(key));
@@ -208,11 +219,16 @@
list.style.display = "";
});
form.querySelector(".sshkey-create-btn").addEventListener("click", async () => {
form
.querySelector(".sshkey-create-btn")
.addEventListener("click", async () => {
const nameInput = form.querySelector(".sshkey-name-input");
const name = nameInput.value.trim();
const keyType = typeSelect.value;
const bits = parseInt(form.querySelector(".sshkey-bits-select").value, 10);
const bits = parseInt(
form.querySelector(".sshkey-bits-select").value,
10,
);
const passphrase = form.querySelector(".sshkey-pass-input").value;
if (!name) {
@@ -221,8 +237,15 @@
}
try {
await window.go.api.Service.GenerateSSHKey(name, keyType, bits, passphrase);
this.app.notifications.success(`Key "${name}" generated successfully`);
await window.go.api.Service.GenerateSSHKey(
name,
keyType,
bits,
passphrase,
);
this.app.notifications.success(
`Key "${name}" generated successfully`,
);
form.remove();
list.style.display = "";
this._loadKeys();
@@ -266,9 +289,15 @@
setTimeout(() => backdrop.remove(), 200);
resolve(val);
};
modal.querySelector(".modal-close").addEventListener("click", () => close(false));
modal.querySelector(".confirm-cancel").addEventListener("click", () => close(false));
modal.querySelector(".confirm-ok").addEventListener("click", () => close(true));
modal
.querySelector(".modal-close")
.addEventListener("click", () => close(false));
modal
.querySelector(".confirm-cancel")
.addEventListener("click", () => close(false));
modal
.querySelector(".confirm-ok")
.addEventListener("click", () => close(true));
backdrop.addEventListener("click", (e) => {
if (e.target === backdrop) close(false);
});
+261 -262
View File
@@ -11,310 +11,310 @@
*/
const THEMES = {
charcoal: {
label: 'Charcoal (Default)',
label: "Charcoal (Default)",
theme: {
background: '#1c1c1c',
foreground: '#e4e4e4',
cursor: '#4ecdc4',
cursorAccent: '#1c1c1c',
selectionBackground: 'rgba(78, 205, 196, 0.3)',
selectionForeground: '#ffffff',
black: '#1c1c1c',
red: '#ef5350',
green: '#66bb6a',
yellow: '#ffa726',
blue: '#42a5f5',
magenta: '#ab47bc',
cyan: '#4ecdc4',
white: '#e4e4e4',
brightBlack: '#555555',
brightRed: '#ff7043',
brightGreen: '#81c784',
brightYellow: '#ffcc02',
brightBlue: '#64b5f6',
brightMagenta: '#ce93d8',
brightCyan: '#80deea',
brightWhite: '#ffffff',
background: "#1c1c1c",
foreground: "#e4e4e4",
cursor: "#4ecdc4",
cursorAccent: "#1c1c1c",
selectionBackground: "rgba(78, 205, 196, 0.3)",
selectionForeground: "#ffffff",
black: "#1c1c1c",
red: "#ef5350",
green: "#66bb6a",
yellow: "#ffa726",
blue: "#42a5f5",
magenta: "#ab47bc",
cyan: "#4ecdc4",
white: "#e4e4e4",
brightBlack: "#555555",
brightRed: "#ff7043",
brightGreen: "#81c784",
brightYellow: "#ffcc02",
brightBlue: "#64b5f6",
brightMagenta: "#ce93d8",
brightCyan: "#80deea",
brightWhite: "#ffffff",
},
},
'solarized-dark': {
label: 'Solarized Dark',
"solarized-dark": {
label: "Solarized Dark",
theme: {
background: '#002b36',
foreground: '#839496',
cursor: '#93a1a1',
cursorAccent: '#002b36',
selectionBackground: 'rgba(147, 161, 161, 0.3)',
selectionForeground: '#fdf6e3',
black: '#073642',
red: '#dc322f',
green: '#859900',
yellow: '#b58900',
blue: '#268bd2',
magenta: '#d33682',
cyan: '#2aa198',
white: '#eee8d5',
brightBlack: '#586e75',
brightRed: '#cb4b16',
brightGreen: '#586e75',
brightYellow: '#657b83',
brightBlue: '#839496',
brightMagenta: '#6c71c4',
brightCyan: '#93a1a1',
brightWhite: '#fdf6e3',
background: "#002b36",
foreground: "#839496",
cursor: "#93a1a1",
cursorAccent: "#002b36",
selectionBackground: "rgba(147, 161, 161, 0.3)",
selectionForeground: "#fdf6e3",
black: "#073642",
red: "#dc322f",
green: "#859900",
yellow: "#b58900",
blue: "#268bd2",
magenta: "#d33682",
cyan: "#2aa198",
white: "#eee8d5",
brightBlack: "#586e75",
brightRed: "#cb4b16",
brightGreen: "#586e75",
brightYellow: "#657b83",
brightBlue: "#839496",
brightMagenta: "#6c71c4",
brightCyan: "#93a1a1",
brightWhite: "#fdf6e3",
},
},
monokai: {
label: 'Monokai',
label: "Monokai",
theme: {
background: '#272822',
foreground: '#f8f8f2',
cursor: '#f8f8f0',
cursorAccent: '#272822',
selectionBackground: 'rgba(73, 72, 62, 0.6)',
selectionForeground: '#f8f8f2',
black: '#272822',
red: '#f92672',
green: '#a6e22e',
yellow: '#f4bf75',
blue: '#66d9ef',
magenta: '#ae81ff',
cyan: '#a1efe4',
white: '#f8f8f2',
brightBlack: '#75715e',
brightRed: '#f92672',
brightGreen: '#a6e22e',
brightYellow: '#f4bf75',
brightBlue: '#66d9ef',
brightMagenta: '#ae81ff',
brightCyan: '#a1efe4',
brightWhite: '#f9f8f5',
background: "#272822",
foreground: "#f8f8f2",
cursor: "#f8f8f0",
cursorAccent: "#272822",
selectionBackground: "rgba(73, 72, 62, 0.6)",
selectionForeground: "#f8f8f2",
black: "#272822",
red: "#f92672",
green: "#a6e22e",
yellow: "#f4bf75",
blue: "#66d9ef",
magenta: "#ae81ff",
cyan: "#a1efe4",
white: "#f8f8f2",
brightBlack: "#75715e",
brightRed: "#f92672",
brightGreen: "#a6e22e",
brightYellow: "#f4bf75",
brightBlue: "#66d9ef",
brightMagenta: "#ae81ff",
brightCyan: "#a1efe4",
brightWhite: "#f9f8f5",
},
},
nord: {
label: 'Nord',
label: "Nord",
theme: {
background: '#2e3440',
foreground: '#d8dee9',
cursor: '#d8dee9',
cursorAccent: '#2e3440',
selectionBackground: 'rgba(136, 192, 208, 0.3)',
selectionForeground: '#eceff4',
black: '#3b4252',
red: '#bf616a',
green: '#a3be8c',
yellow: '#ebcb8b',
blue: '#81a1c1',
magenta: '#b48ead',
cyan: '#88c0d0',
white: '#e5e9f0',
brightBlack: '#4c566a',
brightRed: '#bf616a',
brightGreen: '#a3be8c',
brightYellow: '#ebcb8b',
brightBlue: '#81a1c1',
brightMagenta: '#b48ead',
brightCyan: '#8fbcbb',
brightWhite: '#eceff4',
background: "#2e3440",
foreground: "#d8dee9",
cursor: "#d8dee9",
cursorAccent: "#2e3440",
selectionBackground: "rgba(136, 192, 208, 0.3)",
selectionForeground: "#eceff4",
black: "#3b4252",
red: "#bf616a",
green: "#a3be8c",
yellow: "#ebcb8b",
blue: "#81a1c1",
magenta: "#b48ead",
cyan: "#88c0d0",
white: "#e5e9f0",
brightBlack: "#4c566a",
brightRed: "#bf616a",
brightGreen: "#a3be8c",
brightYellow: "#ebcb8b",
brightBlue: "#81a1c1",
brightMagenta: "#b48ead",
brightCyan: "#8fbcbb",
brightWhite: "#eceff4",
},
},
dracula: {
label: 'Dracula',
label: "Dracula",
theme: {
background: '#282a36',
foreground: '#f8f8f2',
cursor: '#f8f8f2',
cursorAccent: '#282a36',
selectionBackground: 'rgba(68, 71, 90, 0.6)',
selectionForeground: '#f8f8f2',
black: '#21222c',
red: '#ff5555',
green: '#50fa7b',
yellow: '#f1fa8c',
blue: '#bd93f9',
magenta: '#ff79c6',
cyan: '#8be9fd',
white: '#f8f8f2',
brightBlack: '#6272a4',
brightRed: '#ff6e6e',
brightGreen: '#69ff94',
brightYellow: '#ffffa5',
brightBlue: '#d6acff',
brightMagenta: '#ff92df',
brightCyan: '#a4ffff',
brightWhite: '#ffffff',
background: "#282a36",
foreground: "#f8f8f2",
cursor: "#f8f8f2",
cursorAccent: "#282a36",
selectionBackground: "rgba(68, 71, 90, 0.6)",
selectionForeground: "#f8f8f2",
black: "#21222c",
red: "#ff5555",
green: "#50fa7b",
yellow: "#f1fa8c",
blue: "#bd93f9",
magenta: "#ff79c6",
cyan: "#8be9fd",
white: "#f8f8f2",
brightBlack: "#6272a4",
brightRed: "#ff6e6e",
brightGreen: "#69ff94",
brightYellow: "#ffffa5",
brightBlue: "#d6acff",
brightMagenta: "#ff92df",
brightCyan: "#a4ffff",
brightWhite: "#ffffff",
},
},
'one-dark': {
label: 'One Dark',
"one-dark": {
label: "One Dark",
theme: {
background: '#282c34',
foreground: '#abb2bf',
cursor: '#528bff',
cursorAccent: '#282c34',
selectionBackground: 'rgba(82, 139, 255, 0.3)',
selectionForeground: '#abb2bf',
black: '#282c34',
red: '#e06c75',
green: '#98c379',
yellow: '#e5c07b',
blue: '#61afef',
magenta: '#c678dd',
cyan: '#56b6c2',
white: '#abb2bf',
brightBlack: '#5c6370',
brightRed: '#e06c75',
brightGreen: '#98c379',
brightYellow: '#e5c07b',
brightBlue: '#61afef',
brightMagenta: '#c678dd',
brightCyan: '#56b6c2',
brightWhite: '#ffffff',
background: "#282c34",
foreground: "#abb2bf",
cursor: "#528bff",
cursorAccent: "#282c34",
selectionBackground: "rgba(82, 139, 255, 0.3)",
selectionForeground: "#abb2bf",
black: "#282c34",
red: "#e06c75",
green: "#98c379",
yellow: "#e5c07b",
blue: "#61afef",
magenta: "#c678dd",
cyan: "#56b6c2",
white: "#abb2bf",
brightBlack: "#5c6370",
brightRed: "#e06c75",
brightGreen: "#98c379",
brightYellow: "#e5c07b",
brightBlue: "#61afef",
brightMagenta: "#c678dd",
brightCyan: "#56b6c2",
brightWhite: "#ffffff",
},
},
'gruvbox-dark': {
label: 'Gruvbox Dark',
"gruvbox-dark": {
label: "Gruvbox Dark",
theme: {
background: '#282828',
foreground: '#ebdbb2',
cursor: '#ebdbb2',
cursorAccent: '#282828',
selectionBackground: 'rgba(214, 93, 14, 0.3)',
selectionForeground: '#ebdbb2',
black: '#282828',
red: '#cc241d',
green: '#98971a',
yellow: '#d79921',
blue: '#458588',
magenta: '#b16286',
cyan: '#689d6a',
white: '#a89984',
brightBlack: '#928374',
brightRed: '#fb4934',
brightGreen: '#b8bb26',
brightYellow: '#fabd2f',
brightBlue: '#83a598',
brightMagenta: '#d3869b',
brightCyan: '#8ec07c',
brightWhite: '#ebdbb2',
background: "#282828",
foreground: "#ebdbb2",
cursor: "#ebdbb2",
cursorAccent: "#282828",
selectionBackground: "rgba(214, 93, 14, 0.3)",
selectionForeground: "#ebdbb2",
black: "#282828",
red: "#cc241d",
green: "#98971a",
yellow: "#d79921",
blue: "#458588",
magenta: "#b16286",
cyan: "#689d6a",
white: "#a89984",
brightBlack: "#928374",
brightRed: "#fb4934",
brightGreen: "#b8bb26",
brightYellow: "#fabd2f",
brightBlue: "#83a598",
brightMagenta: "#d3869b",
brightCyan: "#8ec07c",
brightWhite: "#ebdbb2",
},
},
'tokyo-night': {
label: 'Tokyo Night',
"tokyo-night": {
label: "Tokyo Night",
theme: {
background: '#1a1b26',
foreground: '#a9b1d6',
cursor: '#c0caf5',
cursorAccent: '#1a1b26',
selectionBackground: 'rgba(41, 46, 66, 0.9)',
selectionForeground: '#c0caf5',
black: '#15161e',
red: '#f7768e',
green: '#9ece6a',
yellow: '#e0af68',
blue: '#7aa2f7',
magenta: '#bb9af7',
cyan: '#7dcfff',
white: '#a9b1d6',
brightBlack: '#414868',
brightRed: '#f7768e',
brightGreen: '#9ece6a',
brightYellow: '#e0af68',
brightBlue: '#7aa2f7',
brightMagenta: '#bb9af7',
brightCyan: '#7dcfff',
brightWhite: '#c0caf5',
background: "#1a1b26",
foreground: "#a9b1d6",
cursor: "#c0caf5",
cursorAccent: "#1a1b26",
selectionBackground: "rgba(41, 46, 66, 0.9)",
selectionForeground: "#c0caf5",
black: "#15161e",
red: "#f7768e",
green: "#9ece6a",
yellow: "#e0af68",
blue: "#7aa2f7",
magenta: "#bb9af7",
cyan: "#7dcfff",
white: "#a9b1d6",
brightBlack: "#414868",
brightRed: "#f7768e",
brightGreen: "#9ece6a",
brightYellow: "#e0af68",
brightBlue: "#7aa2f7",
brightMagenta: "#bb9af7",
brightCyan: "#7dcfff",
brightWhite: "#c0caf5",
},
},
'catppuccin-mocha': {
label: 'Catppuccin Mocha',
"catppuccin-mocha": {
label: "Catppuccin Mocha",
theme: {
background: '#1e1e2e',
foreground: '#cdd6f4',
cursor: '#f5e0dc',
cursorAccent: '#1e1e2e',
selectionBackground: 'rgba(88, 91, 112, 0.5)',
selectionForeground: '#cdd6f4',
black: '#45475a',
red: '#f38ba8',
green: '#a6e3a1',
yellow: '#f9e2af',
blue: '#89b4fa',
magenta: '#f5c2e7',
cyan: '#94e2d5',
white: '#bac2de',
brightBlack: '#585b70',
brightRed: '#f38ba8',
brightGreen: '#a6e3a1',
brightYellow: '#f9e2af',
brightBlue: '#89b4fa',
brightMagenta: '#f5c2e7',
brightCyan: '#94e2d5',
brightWhite: '#a6adc8',
background: "#1e1e2e",
foreground: "#cdd6f4",
cursor: "#f5e0dc",
cursorAccent: "#1e1e2e",
selectionBackground: "rgba(88, 91, 112, 0.5)",
selectionForeground: "#cdd6f4",
black: "#45475a",
red: "#f38ba8",
green: "#a6e3a1",
yellow: "#f9e2af",
blue: "#89b4fa",
magenta: "#f5c2e7",
cyan: "#94e2d5",
white: "#bac2de",
brightBlack: "#585b70",
brightRed: "#f38ba8",
brightGreen: "#a6e3a1",
brightYellow: "#f9e2af",
brightBlue: "#89b4fa",
brightMagenta: "#f5c2e7",
brightCyan: "#94e2d5",
brightWhite: "#a6adc8",
},
},
'rose-pine': {
label: 'Rosé Pine',
"rose-pine": {
label: "Rosé Pine",
theme: {
background: '#191724',
foreground: '#e0def4',
cursor: '#524f67',
cursorAccent: '#e0def4',
selectionBackground: 'rgba(110, 106, 134, 0.4)',
selectionForeground: '#e0def4',
black: '#26233a',
red: '#eb6f92',
green: '#31748f',
yellow: '#f6c177',
blue: '#9ccfd8',
magenta: '#c4a7e7',
cyan: '#ebbcba',
white: '#e0def4',
brightBlack: '#6e6a86',
brightRed: '#eb6f92',
brightGreen: '#31748f',
brightYellow: '#f6c177',
brightBlue: '#9ccfd8',
brightMagenta: '#c4a7e7',
brightCyan: '#ebbcba',
brightWhite: '#e0def4',
background: "#191724",
foreground: "#e0def4",
cursor: "#524f67",
cursorAccent: "#e0def4",
selectionBackground: "rgba(110, 106, 134, 0.4)",
selectionForeground: "#e0def4",
black: "#26233a",
red: "#eb6f92",
green: "#31748f",
yellow: "#f6c177",
blue: "#9ccfd8",
magenta: "#c4a7e7",
cyan: "#ebbcba",
white: "#e0def4",
brightBlack: "#6e6a86",
brightRed: "#eb6f92",
brightGreen: "#31748f",
brightYellow: "#f6c177",
brightBlue: "#9ccfd8",
brightMagenta: "#c4a7e7",
brightCyan: "#ebbcba",
brightWhite: "#e0def4",
},
},
'solarized-light': {
label: 'Solarized Light',
"solarized-light": {
label: "Solarized Light",
theme: {
background: '#fdf6e3',
foreground: '#657b83',
cursor: '#586e75',
cursorAccent: '#fdf6e3',
selectionBackground: 'rgba(7, 54, 66, 0.2)',
selectionForeground: '#073642',
black: '#073642',
red: '#dc322f',
green: '#859900',
yellow: '#b58900',
blue: '#268bd2',
magenta: '#d33682',
cyan: '#2aa198',
white: '#eee8d5',
brightBlack: '#002b36',
brightRed: '#cb4b16',
brightGreen: '#586e75',
brightYellow: '#657b83',
brightBlue: '#839496',
brightMagenta: '#6c71c4',
brightCyan: '#93a1a1',
brightWhite: '#fdf6e3',
background: "#fdf6e3",
foreground: "#657b83",
cursor: "#586e75",
cursorAccent: "#fdf6e3",
selectionBackground: "rgba(7, 54, 66, 0.2)",
selectionForeground: "#073642",
black: "#073642",
red: "#dc322f",
green: "#859900",
yellow: "#b58900",
blue: "#268bd2",
magenta: "#d33682",
cyan: "#2aa198",
white: "#eee8d5",
brightBlack: "#002b36",
brightRed: "#cb4b16",
brightGreen: "#586e75",
brightYellow: "#657b83",
brightBlue: "#839496",
brightMagenta: "#6c71c4",
brightCyan: "#93a1a1",
brightWhite: "#fdf6e3",
},
},
};
@@ -326,7 +326,7 @@ const THEMES = {
* @returns {Object} xterm.js theme object
*/
function getTerminalTheme(name) {
const entry = THEMES[name || 'charcoal'];
const entry = THEMES[name || "charcoal"];
return entry ? entry.theme : THEMES.charcoal.theme;
}
@@ -344,5 +344,4 @@ function listTerminalThemes() {
window.NexusTerminal = window.NexusTerminal || {};
window.NexusTerminal.getTerminalTheme = getTerminalTheme;
window.NexusTerminal.listTerminalThemes = listTerminalThemes;
})();
+33 -12
View File
@@ -92,7 +92,9 @@
}
} else {
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);
}
@@ -101,13 +103,19 @@
// Fire actions
if (rule.action === "notify" || rule.action === "both") {
const session = this.app.state.getSession(sessionId);
const label = session ? (session.tabName || session.profileName) : sessionId;
this.app.notifications.info(`[${label}] Trigger: ${rule.name || rule.pattern}`);
const label = session
? session.tabName || session.profileName
: sessionId;
this.app.notifications.info(
`[${label}] Trigger: ${rule.name || rule.pattern}`,
);
}
if (rule.action === "highlight" || rule.action === "both") {
// Flash the tab briefly
const tabEl = document.querySelector(`.tab[data-session-id="${sessionId}"]`);
const tabEl = document.querySelector(
`.tab[data-session-id="${sessionId}"]`,
);
if (tabEl) {
tabEl.classList.add("tab-trigger-flash");
setTimeout(() => tabEl.classList.remove("tab-trigger-flash"), 2000);
@@ -186,11 +194,15 @@
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) => {
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();
}
@@ -201,7 +213,8 @@
list.innerHTML = "";
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;
}
@@ -236,11 +249,15 @@
});
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("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("click", () => {
this._rules.splice(index, 1);
@@ -287,7 +304,7 @@
form.innerHTML = `
<div class="form-group">
<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 class="form-group">
<label class="form-label">Pattern</label>
@@ -325,14 +342,18 @@
list.parentNode.insertBefore(form, list);
form.querySelector(".trigger-cancel-btn").addEventListener("click", () => {
form
.querySelector(".trigger-cancel-btn")
.addEventListener("click", () => {
form.remove();
list.style.display = "";
if (toolbar) toolbar.style.display = "";
});
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) {
this.app.notifications.error("Pattern is required");
return;
+93 -50
View File
@@ -35,15 +35,15 @@ function createElement(tag, className, attrs = {}) {
el.className = className;
}
for (const [key, value] of Object.entries(attrs)) {
if (key === 'textContent') {
if (key === "textContent") {
el.textContent = value;
} else if (key === 'innerHTML') {
} else if (key === "innerHTML") {
el.innerHTML = value;
} else if (key === 'dataset') {
} else if (key === "dataset") {
for (const [dKey, dVal] of Object.entries(value)) {
el.dataset[dKey] = dVal;
}
} else if (key.startsWith('on') && typeof value === 'function') {
} else if (key.startsWith("on") && typeof value === "function") {
el.addEventListener(key.slice(2).toLowerCase(), value);
} else {
el.setAttribute(key, value);
@@ -58,13 +58,13 @@ function createElement(tag, className, attrs = {}) {
* @returns {string} Escaped string safe for innerHTML.
*/
function escapeHtml(str) {
if (!str) return '';
if (!str) return "";
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;',
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
return str.replace(/[&<>"']/g, (ch) => map[ch]);
}
@@ -75,21 +75,33 @@ function escapeHtml(str) {
* @returns {string} e.g. "14:32:07" or "2 Jan 14:32".
*/
function formatTimestamp(date) {
if (!date) return '';
if (!date) return "";
const d = date instanceof Date ? date : new Date(date);
if (isNaN(d.getTime())) return '';
if (isNaN(d.getTime())) return "";
const now = new Date();
const isToday = d.toDateString() === now.toDateString();
const hours = String(d.getHours()).padStart(2, '0');
const mins = String(d.getMinutes()).padStart(2, '0');
const secs = String(d.getSeconds()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, "0");
const mins = String(d.getMinutes()).padStart(2, "0");
const secs = String(d.getSeconds()).padStart(2, "0");
if (isToday) {
return `${hours}:${mins}:${secs}`;
}
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const months = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
return `${d.getDate()} ${months[d.getMonth()]} ${hours}:${mins}`;
}
@@ -174,8 +186,8 @@ function generateId() {
* @returns {string} e.g. "1.23 KB"
*/
function formatBytes(bytes) {
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
if (bytes === 0) return "0 B";
const units = ["B", "KB", "MB", "GB", "TB"];
const k = 1024;
const i = Math.floor(Math.log(bytes) / Math.log(k));
const value = bytes / Math.pow(k, i);
@@ -189,15 +201,15 @@ function formatBytes(bytes) {
* @returns {string}
*/
function truncate(str, len) {
if (!str || str.length <= len) return str || '';
return str.slice(0, len) + '…';
if (!str || str.length <= len) return str || "";
return str.slice(0, len) + "…";
}
/**
* Removes any open context menus from the DOM.
*/
function dismissContextMenus() {
document.querySelectorAll('.context-menu').forEach((m) => m.remove());
document.querySelectorAll(".context-menu").forEach((m) => m.remove());
}
/**
@@ -207,12 +219,18 @@ function dismissContextMenus() {
*/
function protocolColour(protocol) {
switch (protocol) {
case 'ssh': return 'proto-ssh';
case 'telnet': return 'proto-telnet';
case 'raw': return 'proto-raw';
case 'github': return 'proto-github';
case 'vnc': return 'proto-vnc';
default: return '';
case "ssh":
return "proto-ssh";
case "telnet":
return "proto-telnet";
case "raw":
return "proto-raw";
case "github":
return "proto-github";
case "vnc":
return "proto-vnc";
default:
return "";
}
}
@@ -222,24 +240,25 @@ function protocolColour(protocol) {
* @returns {string} e.g. "3 hours ago", "2 days ago".
*/
function relativeTime(isoDate) {
if (!isoDate) return '';
if (!isoDate) return "";
const d = new Date(isoDate);
if (isNaN(d.getTime())) return '';
if (isNaN(d.getTime())) return "";
const now = Date.now();
const diff = now - d.getTime();
const secs = Math.floor(diff / 1000);
if (secs < 60) return 'just now';
if (secs < 60) return "just now";
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);
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);
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);
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);
return years === 1 ? '1 year ago' : `${years} years ago`;
return years === 1 ? "1 year ago" : `${years} years ago`;
}
/**
@@ -249,27 +268,50 @@ function relativeTime(isoDate) {
*/
function stateLabel(state) {
switch (state) {
case 'connecting': return 'Connecting';
case 'connected': return 'Connected';
case 'disconnected': return 'Disconnected';
case 'reconnecting': return 'Reconnecting';
case 'error': return 'Error';
default: return state || 'Unknown';
case "connecting":
return "Connecting";
case "connected":
return "Connected";
case "disconnected":
return "Disconnected";
case "reconnecting":
return "Reconnecting";
case "error":
return "Error";
default:
return state || "Unknown";
}
}
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 ───────── */
function disableAutoCapitalise(el) {
el.setAttribute('autocapitalize', 'none');
el.setAttribute('autocorrect', 'off');
el.setAttribute('spellcheck', 'false');
el.setAttribute("autocapitalize", "none");
el.setAttribute("autocorrect", "off");
el.setAttribute("spellcheck", "false");
}
// Apply to existing inputs
document.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
document.querySelectorAll("input, textarea").forEach(disableAutoCapitalise);
// Observe for dynamically added inputs
var inputObserver = new MutationObserver(function (mutations) {
@@ -278,15 +320,16 @@ var inputObserver = new MutationObserver(function(mutations) {
for (var j = 0; j < nodes.length; j++) {
var node = nodes[j];
if (node.nodeType !== 1) continue;
if (node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
if (node.tagName === "INPUT" || node.tagName === "TEXTAREA") {
disableAutoCapitalise(node);
}
if (node.querySelectorAll) {
node.querySelectorAll('input, textarea').forEach(disableAutoCapitalise);
node
.querySelectorAll("input, textarea")
.forEach(disableAutoCapitalise);
}
}
}
});
inputObserver.observe(document.body, { childList: true, subtree: true });
})();
+12 -4
View File
@@ -91,16 +91,24 @@
});
ctrlAltDel.addEventListener("click", () => this._sendCtrlAltDel());
const clipboardBtn = createElement("button", "btn btn-sm btn-vnc-action", {
const clipboardBtn = createElement(
"button",
"btn btn-sm btn-vnc-action",
{
textContent: "Clipboard",
title: "Sync clipboard",
});
},
);
clipboardBtn.addEventListener("click", () => this._syncClipboard());
const fullscreenBtn = createElement("button", "btn btn-sm btn-vnc-action", {
const fullscreenBtn = createElement(
"button",
"btn btn-sm btn-vnc-action",
{
textContent: "Fullscreen",
title: "Toggle fullscreen",
});
},
);
fullscreenBtn.addEventListener("click", () => this._toggleFullscreen());
this.statusEl = createElement("span", "vnc-status", {
@@ -1,12 +1,13 @@
-- Migration 004: Add VNC protocol support
-- Adds the 'vnc' value to the protocol CHECK constraint and a vnc_config column.
-- Step 1: Create the new table with the updated constraint.
CREATE TABLE IF NOT EXISTS profiles_new (
id TEXT PRIMARY KEY,
display_name TEXT NOT NULL,
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,
port INTEGER NOT NULL,
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).
INSERT OR IGNORE INTO profiles_new (
id, display_name, description, protocol, 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
INSERT
OR IGNORE INTO profiles_new (
id,
display_name,
description,
protocol,
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
id, display_name, description, protocol, 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, NULL,
created_at, updated_at
FROM profiles;
id,
display_name,
description,
protocol,
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,
NULL,
created_at,
updated_at
FROM
profiles;
-- Step 3: Drop old table and rename.
DROP TABLE profiles;
ALTER TABLE profiles_new RENAME TO profiles;
ALTER TABLE
profiles_new RENAME TO profiles;
-- 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'));