+5
-1
@@ -269,7 +269,11 @@
|
||||
<span id="status-protocol"></span>
|
||||
<span id="status-session-count">0 sessions</span>
|
||||
<span class="status-spacer"></span>
|
||||
<button class="status-update is-hidden" id="status-update" type="button">
|
||||
<button
|
||||
class="status-update is-hidden"
|
||||
id="status-update"
|
||||
type="button"
|
||||
>
|
||||
Update available
|
||||
</button>
|
||||
<span id="status-vault">🔒 Locked</span>
|
||||
|
||||
+104
-93
@@ -7,170 +7,181 @@
|
||||
Main application grid
|
||||
----------------------------------------------------------------------- */
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-rows: var(--tab-bar-height) 1fr var(--status-bar-height);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-rows: var(--tab-bar-height) 1fr var(--status-bar-height);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
}
|
||||
|
||||
/* Collapsed sidebar variant */
|
||||
.app.sidebar-collapsed {
|
||||
grid-template-columns: var(--sidebar-width-collapsed) 1fr;
|
||||
grid-template-columns: var(--sidebar-width-collapsed) 1fr;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Sidebar
|
||||
----------------------------------------------------------------------- */
|
||||
.sidebar {
|
||||
grid-row: 1 / -1;
|
||||
grid-column: 1;
|
||||
background: var(--bg-surface);
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: var(--z-sticky);
|
||||
grid-row: 1 / -1;
|
||||
grid-column: 1;
|
||||
background: var(--bg-surface);
|
||||
border-right: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Tab bar
|
||||
----------------------------------------------------------------------- */
|
||||
.tab-bar {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--tab-bar-height);
|
||||
overflow: hidden;
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
background: var(--bg-surface);
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: var(--tab-bar-height);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tab-list {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
.tab-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Workspace
|
||||
----------------------------------------------------------------------- */
|
||||
.workspace {
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
grid-column: 2;
|
||||
grid-row: 2;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--bg-base);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Panel container — fills workspace, holds session panels */
|
||||
.panel-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Individual session panel — one per tab, fills the panel container */
|
||||
.session-panel {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Terminal container — fills workspace */
|
||||
.terminal-container {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Status bar
|
||||
----------------------------------------------------------------------- */
|
||||
.status-bar {
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
gap: var(--space-4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
background: var(--bg-surface);
|
||||
border-top: 1px solid var(--border-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--space-3);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-tertiary);
|
||||
gap: var(--space-4);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.status-bar span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.status-spacer {
|
||||
flex: 1;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-update {
|
||||
border: 1px solid var(--accent);
|
||||
background: rgba(78, 205, 196, 0.12);
|
||||
color: var(--accent);
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease, color 150ms ease, border-color 150ms ease;
|
||||
border: 1px solid var(--accent);
|
||||
background: rgba(78, 205, 196, 0.12);
|
||||
color: var(--accent);
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 150ms ease,
|
||||
color 150ms ease,
|
||||
border-color 150ms ease;
|
||||
}
|
||||
|
||||
.status-update:hover {
|
||||
background: rgba(78, 205, 196, 0.2);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-strong, var(--accent));
|
||||
background: rgba(78, 205, 196, 0.2);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--accent-strong, var(--accent));
|
||||
}
|
||||
|
||||
.status-update.is-hidden {
|
||||
display: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Status bar indicator dots */
|
||||
.status-bar .status-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
.status-dot.is-connected {
|
||||
background: var(--success);
|
||||
}
|
||||
.status-dot.is-connecting {
|
||||
background: var(--warning);
|
||||
}
|
||||
.status-dot.is-error {
|
||||
background: var(--error);
|
||||
}
|
||||
.status-dot.is-disconnected {
|
||||
background: var(--text-tertiary);
|
||||
}
|
||||
.status-dot.is-connected { background: var(--success); }
|
||||
.status-dot.is-connecting { background: var(--warning); }
|
||||
.status-dot.is-error { background: var(--error); }
|
||||
.status-dot.is-disconnected { background: var(--text-tertiary); }
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
Resize handle (between sidebar and workspace)
|
||||
----------------------------------------------------------------------- */
|
||||
.resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: var(--z-sticky);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -3px;
|
||||
width: 6px;
|
||||
height: 100%;
|
||||
cursor: col-resize;
|
||||
z-index: var(--z-sticky);
|
||||
}
|
||||
.resize-handle:hover,
|
||||
.resize-handle.is-dragging {
|
||||
background: var(--accent-muted);
|
||||
background: var(--accent-muted);
|
||||
}
|
||||
|
||||
@@ -513,7 +513,9 @@
|
||||
const updateBtn = document.getElementById("status-update");
|
||||
if (updateBtn) {
|
||||
updateBtn.addEventListener("mousedown", (e) => e.preventDefault());
|
||||
updateBtn.addEventListener("click", () => this.settings.openTab("about"));
|
||||
updateBtn.addEventListener("click", () =>
|
||||
this.settings.openTab("about"),
|
||||
);
|
||||
}
|
||||
|
||||
const searchInput = document.getElementById("profile-search");
|
||||
|
||||
@@ -36,8 +36,7 @@ export namespace models {
|
||||
}
|
||||
export class AuditEntry {
|
||||
id: string;
|
||||
// Go type: time
|
||||
timestamp: any;
|
||||
timestamp: string;
|
||||
action: string;
|
||||
profileId?: string;
|
||||
sessionId?: string;
|
||||
@@ -50,30 +49,12 @@ export namespace models {
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.timestamp = this.convertValues(source["timestamp"], null);
|
||||
this.timestamp = source["timestamp"];
|
||||
this.action = source["action"];
|
||||
this.profileId = source["profileId"];
|
||||
this.sessionId = source["sessionId"];
|
||||
this.details = source["details"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class GitHubConfig {
|
||||
baseURL: string;
|
||||
@@ -160,10 +141,8 @@ export namespace models {
|
||||
keyType: string;
|
||||
fingerprint: string;
|
||||
publicKey: string;
|
||||
// Go type: time
|
||||
firstSeen: any;
|
||||
// Go type: time
|
||||
lastSeen: any;
|
||||
firstSeen: string;
|
||||
lastSeen: string;
|
||||
trusted: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@@ -178,28 +157,10 @@ export namespace models {
|
||||
this.keyType = source["keyType"];
|
||||
this.fingerprint = source["fingerprint"];
|
||||
this.publicKey = source["publicKey"];
|
||||
this.firstSeen = this.convertValues(source["firstSeen"], null);
|
||||
this.lastSeen = this.convertValues(source["lastSeen"], null);
|
||||
this.firstSeen = source["firstSeen"];
|
||||
this.lastSeen = source["lastSeen"];
|
||||
this.trusted = source["trusted"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class LoginScriptStep {
|
||||
type: string;
|
||||
@@ -436,14 +397,11 @@ export namespace models {
|
||||
environment: string;
|
||||
notes: string;
|
||||
favourite: boolean;
|
||||
// Go type: time
|
||||
lastConnected?: any;
|
||||
lastConnected?: string;
|
||||
connectTimeout: number;
|
||||
readTimeout: number;
|
||||
// Go type: time
|
||||
createdAt: any;
|
||||
// Go type: time
|
||||
updatedAt: any;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
terminalFontFamily: string;
|
||||
terminalFontSize: number;
|
||||
terminalScrollback: number;
|
||||
@@ -471,11 +429,11 @@ export namespace models {
|
||||
this.environment = source["environment"];
|
||||
this.notes = source["notes"];
|
||||
this.favourite = source["favourite"];
|
||||
this.lastConnected = this.convertValues(source["lastConnected"], null);
|
||||
this.lastConnected = source["lastConnected"];
|
||||
this.connectTimeout = source["connectTimeout"];
|
||||
this.readTimeout = source["readTimeout"];
|
||||
this.createdAt = this.convertValues(source["createdAt"], null);
|
||||
this.updatedAt = this.convertValues(source["updatedAt"], null);
|
||||
this.createdAt = source["createdAt"];
|
||||
this.updatedAt = source["updatedAt"];
|
||||
this.terminalFontFamily = source["terminalFontFamily"];
|
||||
this.terminalFontSize = source["terminalFontSize"];
|
||||
this.terminalScrollback = source["terminalScrollback"];
|
||||
@@ -516,8 +474,7 @@ export namespace models {
|
||||
state: string;
|
||||
tabName: string;
|
||||
pinned: boolean;
|
||||
// Go type: time
|
||||
connectedAt: any;
|
||||
connectedAt: string;
|
||||
error?: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
@@ -535,27 +492,9 @@ export namespace models {
|
||||
this.state = source["state"];
|
||||
this.tabName = source["tabName"];
|
||||
this.pinned = source["pinned"];
|
||||
this.connectedAt = this.convertValues(source["connectedAt"], null);
|
||||
this.connectedAt = source["connectedAt"];
|
||||
this.error = source["error"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
export class Snippet {
|
||||
id: string;
|
||||
@@ -564,10 +503,8 @@ export namespace models {
|
||||
description: string;
|
||||
folder: string;
|
||||
tags: string[];
|
||||
// Go type: time
|
||||
createdAt: any;
|
||||
// Go type: time
|
||||
updatedAt: any;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new Snippet(source);
|
||||
@@ -581,27 +518,9 @@ export namespace models {
|
||||
this.description = source["description"];
|
||||
this.folder = source["folder"];
|
||||
this.tags = source["tags"];
|
||||
this.createdAt = this.convertValues(source["createdAt"], null);
|
||||
this.updatedAt = this.convertValues(source["updatedAt"], null);
|
||||
this.createdAt = source["createdAt"];
|
||||
this.updatedAt = source["updatedAt"];
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user