chore: lock the plugin boundary and remove 2.9.0 leftovers

This commit is contained in:
LukeGus committed 2026-09-25 07:24:46 -05:00
1 parent 6489b62ece
commit 2961297989
399 files changed
+3964 -5627

No files matched your search

+1 -1
View File
@@ -2,7 +2,7 @@ export const AUTH_OVERRIDE_PROTOCOLS = ["ssh", "rdp", "vnc", "telnet"] as const;
export type AuthOverrideProtocol = (typeof AUTH_OVERRIDE_PROTOCOLS)[number];
export const SUPPORTED_AUTH_OVERRIDE_PROTOCOLS =
const SUPPORTED_AUTH_OVERRIDE_PROTOCOLS =
AUTH_OVERRIDE_PROTOCOLS satisfies readonly AuthOverrideProtocol[];
export const AUTH_PROTOCOL_METADATA = {
-4
View File
@@ -41,7 +41,3 @@ export type LogEntry = {
message: string;
details?: Record<string, unknown> | string;
};
export interface ConnectionLogResponse {
connectionLogs?: LogEntry[];
}
-393
View File
@@ -1,393 +0,0 @@
export const GRID_SIZE = 30;
export const MIN_ZOOM = 0.4;
export const MAX_ZOOM = 2.0;
export const ZOOM_STEP = 0.1;
export type WidgetTypeId =
| "service_link"
| "folder"
| "clock"
| "notes"
| "host_status"
| "bookmark_list"
| "weather"
| "iframe_embed"
| "rss_feed"
| "metrics_chart"
| "host_grid"
| "ping_status"
| "recent_activity"
| "termix_uptime"
| "system_overview"
| "ssh_terminal"
| "quick_connect"
| "file_manager_widget"
| "tunnel_widget"
| "calendar"
| "countdown"
| "search_bar"
| "text_banner"
| "image_widget"
| "markdown_notes"
| "custom_api"
| "service_grid"
| "dashboard_links"
| "search_links"
| "link_tree"
| "ssh_quick_connect"
| (string & {});
export interface HomepageItemRow {
id: number;
userId: string;
typeId: WidgetTypeId;
title: string | null;
config: string;
createdAt: string;
updatedAt: string;
}
export interface HomepageLayoutEntry {
itemId: number;
x: number;
y: number;
w: number;
h: number;
zOrder?: number;
}
export interface HomepageLayoutData {
entries: HomepageLayoutEntry[];
pan: { x: number; y: number };
zoom: number;
}
export interface HomepageLayoutRow {
id: number;
userId: string;
layout: HomepageLayoutData;
updatedAt: string;
}
export interface CanvasWidget {
id: number;
typeId: WidgetTypeId;
title: string | null;
config: Record<string, unknown>;
x: number;
y: number;
w: number;
h: number;
zOrder: number;
}
// ---- Per-widget config shapes ----
export interface ServiceLinkConfig {
url: string;
description?: string;
accentColor?: string;
imageUrl?: string;
showImage?: boolean;
}
export interface FolderConfig {
color?: string;
isExpanded: boolean;
}
export interface ClockConfig {
timezone?: string;
showSeconds: boolean;
format: "12h" | "24h";
}
export interface NotesConfig {
content: string;
backgroundColor?: string;
}
export type HostMetricKey =
"cpu" | "memory" | "disk" | "uptime" | "network" | "system" | "processes";
export interface HostStatusConfig {
hostId: number;
/** @deprecated use shownMetrics instead */
showMetrics?: boolean;
/** @deprecated use shownMetrics instead */
showDisk?: boolean;
shownMetrics: HostMetricKey[];
showSparkline?: boolean;
}
export interface BookmarkLink {
label: string;
url: string;
}
export interface BookmarkListConfig {
links: BookmarkLink[];
}
export interface WeatherConfig {
location: string;
unit: "C" | "F";
showForecast: boolean;
}
export interface IframeConfig {
url: string;
scrolling: boolean;
}
export interface RssFeedConfig {
feedUrl: string;
maxItems: number;
showDescription: boolean;
}
// ---- New widget configs ----
export type MetricsChartMetric =
"cpu" | "memory" | "disk" | "net_rx" | "net_tx";
export type MetricsChartRange = "15m" | "1h" | "6h" | "24h";
export interface MetricsChartConfig {
hostId: number;
metric: MetricsChartMetric;
range: MetricsChartRange;
showCurrentValue: boolean;
}
export interface HostGridConfig {
hostIds: number[];
showIp: boolean;
columns: 2 | 3 | 4;
}
export interface PingUrl {
label: string;
url: string;
}
export interface PingStatusConfig {
urls: PingUrl[];
refreshInterval: number;
showLatency: boolean;
}
/** Core activity types; plugin tabs record their own. */
export type ActivityType = "file_manager" | "tunnel" | (string & {});
export interface RecentActivityConfig {
maxItems: number;
filterTypes: ActivityType[];
showTimestamp: boolean;
}
export interface DockerActivityConfig {
maxItems: number;
showHostName: boolean;
}
export interface SshQuickConnectConfig {
hostIds: number[];
connectionType: QuickConnectType;
showStatus: boolean;
layout: "grid" | "list";
}
export interface TermixUptimeConfig {
showDetailed: boolean;
}
export interface SystemOverviewConfig {
showVersion: boolean;
showDbHealth: boolean;
showUptime: boolean;
}
export interface FileManagerWidgetConfig {
hostId: number;
}
export interface SshTerminalConfig {
hostId: number;
autoConnect: boolean;
}
/** Tab types a quick connect widget opens: core tools or plugin actions. */
export type QuickConnectType = "files" | "tunnel" | (string & {});
export interface QuickConnectConfig {
hostIds: number[];
connectionTypes: QuickConnectType[];
showStatus: boolean;
layout: "grid" | "list";
}
export interface CalendarConfig {
timezone?: string;
startOnMonday: boolean;
}
export interface CountdownConfig {
targetDate: string;
label: string;
showDays: boolean;
showHours: boolean;
}
export type SearchEngine = "google" | "duckduckgo" | "bing" | "custom";
export interface SearchBarConfig {
engine: SearchEngine;
customUrl?: string;
placeholder?: string;
openInNewTab: boolean;
}
export type TextBannerFontSize = "sm" | "md" | "lg" | "xl";
export type TextBannerAlign = "left" | "center" | "right";
export type TextBannerWeight = "normal" | "semibold" | "bold";
export interface TextBannerConfig {
text: string;
fontSize: TextBannerFontSize;
textAlign: TextBannerAlign;
fontWeight: TextBannerWeight;
backgroundColor?: string;
}
export interface ImageWidgetConfig {
imageUrl: string;
fit: "contain" | "cover" | "fill";
alt?: string;
linkUrl?: string;
}
export interface MarkdownNotesConfig {
content: string;
backgroundColor?: string;
renderMarkdown: boolean;
}
export type CustomApiDisplayMode = "value" | "json" | "table";
export interface CustomApiConfig {
url: string;
displayField?: string;
label?: string;
unit?: string;
refreshInterval: number;
displayMode: CustomApiDisplayMode;
jsonPath?: string;
}
export interface ServiceGridItem {
label: string;
url: string;
imageUrl?: string;
accentColor?: string;
}
export interface ServiceGridConfig {
services: ServiceGridItem[];
columns: 2 | 3 | 4;
showLabels: boolean;
iconSize: "sm" | "md" | "lg";
}
export interface DashboardLinksConfig {
showIcons: boolean;
columns: 1 | 2 | 3;
maxItems?: number;
}
export interface SearchLinkShortcut {
label: string;
queryTemplate: string;
icon?: string;
accentColor?: string;
}
export interface SearchLinksConfig {
shortcuts: SearchLinkShortcut[];
}
export interface LinkTreeLink {
label: string;
url: string;
description?: string;
}
export interface LinkTreeSection {
heading: string;
links: LinkTreeLink[];
}
export interface LinkTreeConfig {
sections: LinkTreeSection[];
compact: boolean;
}
// ---- Widget registry types ----
export interface WidgetComponentProps<C = Record<string, unknown>> {
widget: CanvasWidget;
config: C;
isReadOnly?: boolean;
onConfigUpdate?: (config: Record<string, unknown>) => void;
}
export interface WidgetEditFormProps<C = Record<string, unknown>> {
config: C;
onChange: (config: C) => void;
}
export interface WidgetTypeDefinition<C = Record<string, unknown>> {
id: WidgetTypeId;
name: string;
description: string;
category: "links" | "info" | "system" | "monitoring";
icon: React.ReactNode;
defaultConfig: C;
defaultSize: { w: number; h: number };
minSize: { w: number; h: number };
component: React.ComponentType<WidgetComponentProps<C>>;
editFormComponent?: React.ComponentType<WidgetEditFormProps<C>>;
}
export interface DragState {
widgetId: number;
startMouseX: number;
startMouseY: number;
startWidgetX: number;
startWidgetY: number;
}
export interface ResizeState {
widgetId: number;
startMouseX: number;
startMouseY: number;
startW: number;
startH: number;
}
export interface ContextMenuAnchor {
top: number;
bottom: number;
left: number;
right: number;
width: number;
height: number;
}
export interface ContextMenuState {
visible: boolean;
screenX: number;
screenY: number;
canvasX: number;
canvasY: number;
anchorRect?: ContextMenuAnchor;
}
+10 -363
View File
@@ -7,49 +7,12 @@ export type {
HostAuthOverrideState,
HostAuthOverrides,
} from "./auth-protocols.js";
// ============================================================================
// HOST TYPES (SSH, RDP, VNC, Telnet)
// ============================================================================
export type ConnectionType = "ssh" | "rdp" | "vnc" | "telnet";
/**
* Core's own SSH auth types, plus whatever a plugin registers through
* ctx.auth (the owning plugin decides the name).
*/
export type SSHAuthType =
| "password"
| "key"
| "credential"
| "none"
| "opkssh"
| "stepca"
| "tailscale";
export type GuacamoleAuthType = "password" | "credential";
export interface ProxmoxStatsConfig {
nodeName?: string | null;
pollInterval?: number;
enabledCards?: string[];
}
export interface ProxmoxConfig {
defaultCredentialId: number | null;
defaultAuthType?: string;
windowsPatterns: string;
dockerPatterns: string;
preferredPrefixes: string;
autoSyncEnabled?: boolean;
syncIntervalMinutes?: number;
markMissingGuests?: boolean;
lastSyncAt?: string;
lastSyncStatus?: "success" | "error";
lastSyncError?: string | null;
lastSyncResult?: {
created: number;
updated: number;
markedMissing: number;
skipped: number;
errors: string[];
};
}
"password" | "key" | "credential" | "none" | "agent" | (string & {});
export type WebEndpointAccess = "direct" | "tunnel";
export type WebEndpointRender = "external" | "embedded";
@@ -93,15 +56,6 @@ export interface WebEndpoint {
localPort?: number;
}
export interface WebUiConfig {
endpoints: WebEndpoint[];
}
/** A host may declare at most this many web endpoints. */
export const MAX_WEB_ENDPOINTS = 16;
/** Endpoint labels are truncated to this length. */
export const MAX_WEB_ENDPOINT_LABEL_LENGTH = 64;
export interface JumpHost {
hostId: number;
}
@@ -120,16 +74,7 @@ export type Host = {
folder: string;
tags: string[];
pin: boolean;
authType:
| "password"
| "key"
| "credential"
| "none"
| "opkssh"
| "stepca"
| "tailscale"
| "agent"
| "vault";
authType: SSHAuthType;
shareSshAuth?: boolean;
password?: string;
key?: string;
@@ -254,16 +199,7 @@ export interface HostData {
parentHostId?: number | string | null;
tags?: string[];
pin?: boolean;
authType:
| "password"
| "key"
| "credential"
| "none"
| "opkssh"
| "stepca"
| "tailscale"
| "agent"
| "vault";
authType: SSHAuthType;
shareSshAuth?: boolean;
password?: string;
key?: File | string | null;
@@ -382,22 +318,6 @@ export interface CredentialBackend {
updatedAt: string;
}
export interface CredentialData {
name: string;
description?: string;
folder?: string;
tags: string[];
authType: "password" | "key";
username?: string;
password?: string;
key?: string;
publicKey?: string;
/** CA-signed certificate file content (e.g. id_ed25519-cert.pub) */
certPublicKey?: string | null;
keyPassword?: string;
keyType?: string;
}
// ============================================================================
// TUNNEL TYPES
// ============================================================================
@@ -405,32 +325,6 @@ export interface CredentialData {
export type TunnelScope = "s2s" | "c2s";
export type TunnelMode = "local" | "remote" | "dynamic";
export interface TunnelConnection {
scope?: TunnelScope;
mode?: TunnelMode;
tunnelType?: "local" | "remote";
localAddress?: string;
remoteAddress?: string;
bindHost?: string;
sourceHostId?: number;
sourceHostSyncId?: string;
sourceHostName?: string;
sourcePort: number;
endpointPort: number;
endpointHost?: string;
targetHost?: string;
endpointPassword?: string;
endpointKey?: string;
endpointKeyPassword?: string;
endpointAuthType?: string;
endpointKeyType?: string;
maxRetries: number;
retryInterval: number;
autoStart: boolean;
}
// ============================================================================
// FILE MANAGER TYPES
// ============================================================================
@@ -447,61 +341,6 @@ export interface Tab {
dirty?: boolean;
}
export interface FileManagerFile {
name: string;
path: string;
type?: "file" | "directory";
isSSH?: boolean;
sshSessionId?: string;
}
export interface FileManagerShortcut {
name: string;
path: string;
}
export interface FileItem {
name: string;
path: string;
isPinned?: boolean;
type: "file" | "directory" | "link";
sshSessionId?: string;
size?: number;
modified?: string;
modifiedTimestamp?: number;
permissions?: string;
owner?: string;
group?: string;
linkTarget?: string;
executable?: boolean;
}
export interface ShortcutItem {
name: string;
path: string;
}
export interface SSHConnection {
id: number;
name: string;
ip: string;
port: number;
username: string;
isPinned?: boolean;
}
// ============================================================================
// HOST INFO TYPES
// ============================================================================
export interface HostInfo {
id: number;
name?: string;
ip: string;
port: number;
createdAt: string;
}
// ============================================================================
// ALERT TYPES
// ============================================================================
@@ -599,21 +438,8 @@ export interface TerminalConfig {
export interface TabContextTab {
id: number;
instanceId?: string;
type:
| "home"
| "terminal"
| "ssh_manager"
| "server_stats"
| "admin"
| "file_manager"
| "user_profile"
| "docker"
| "tunnel"
| "network_graph"
| "tmux_monitor" // --- tmux-monitor ---
| "rdp"
| "vnc"
| "telnet";
/** A core tab type or one a plugin registered. */
type: string;
title: string;
hostConfig?: SSHHost;
terminalRef?: RefObject<TerminalRefHandle | null>;
@@ -636,162 +462,6 @@ export interface TerminalRefHandle {
export type SplitLayout = "2h" | "2v" | "3l" | "3r" | "3t" | "4grid";
export interface SplitConfiguration {
layout: SplitLayout;
positions: Map<number, number>;
}
export interface SplitLayoutOption {
id: SplitLayout;
name: string;
description: string;
cellCount: number;
icon: string;
}
// ============================================================================
// CONNECTION STATES
// ============================================================================
export const CONNECTION_STATES = {
DISCONNECTED: "disconnected",
CONNECTING: "connecting",
CONNECTED: "connected",
VERIFYING: "verifying",
FAILED: "failed",
UNSTABLE: "unstable",
RETRYING: "retrying",
WAITING: "waiting",
DISCONNECTING: "disconnecting",
} as const;
export type ConnectionState =
(typeof CONNECTION_STATES)[keyof typeof CONNECTION_STATES];
export type ErrorType =
| "CONNECTION_FAILED"
| "AUTHENTICATION_FAILED"
| "TIMEOUT"
| "NETWORK_ERROR"
| "UNKNOWN";
// ============================================================================
// AUTHENTICATION TYPES
// ============================================================================
export type AuthType =
| "password"
| "key"
| "credential"
| "none"
| "opkssh"
| "stepca"
| "tailscale";
export type KeyType = "rsa" | "ecdsa" | "ed25519";
// ============================================================================
// API RESPONSE TYPES
// ============================================================================
export interface ApiResponse<T = unknown> {
data?: T;
error?: string;
message?: string;
status?: number;
}
// ============================================================================
// COMPONENT PROP TYPES
// ============================================================================
export interface CredentialsManagerProps {
onEditCredential?: (credential: Credential) => void;
onAddCredential?: () => void;
}
export interface CredentialEditorProps {
editingCredential?: Credential | null;
onFormSubmit?: () => void;
onBack?: () => void;
}
export interface CredentialViewerProps {
credential: Credential;
onClose: () => void;
onEdit: () => void;
}
export interface CredentialSelectorProps {
value?: number | null;
onValueChange: (value: number | null) => void;
}
export interface HostManagerProps {
onSelectView?: (view: string) => void;
isTopbarOpen?: boolean;
initialTab?: string;
hostConfig?: SSHHost;
_updateTimestamp?: number;
rightSidebarOpen?: boolean;
rightSidebarWidth?: number;
currentTabId?: number;
updateTab?: (tabId: number, updates: Partial<Omit<Tab, "id">>) => void;
}
export interface SSHManagerHostEditorProps {
editingHost?: SSHHost | null;
onFormSubmit?: () => void;
}
export interface SSHManagerHostViewerProps {
onEditHost?: (host: SSHHost) => void;
onAddHost?: () => void;
}
export interface HostProps {
host: SSHHost;
onHostConnect?: () => void;
}
export interface FileManagerProps {
onSelectView?: (view: string) => void;
embedded?: boolean;
initialHost?: SSHHost | null;
}
export interface AlertCardProps {
alert: TermixAlert;
onDismiss: (alertId: string) => void;
}
export interface AlertManagerProps {
alerts: TermixAlert[];
onDismiss: (alertId: string) => void;
loggedIn: boolean;
}
export interface FolderStats {
totalHosts: number;
hostsByType: Array<{
type: string;
count: number;
}>;
}
// Snippet, SnippetFolder types live in ui-types.ts (the shape actually used
// by SnippetsPanel.tsx); this file's older definitions were unused and removed.
// ============================================================================
// UTILITY TYPES
// ============================================================================
export type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type RequiredFields<T, K extends keyof T> = T & Required<Pick<T, K>>;
export type PartialExcept<T, K extends keyof T> = Partial<T> & Pick<T, K>;
// ============================================================================
// EXPRESS REQUEST TYPES
// ============================================================================
@@ -856,7 +526,7 @@ export interface CacheEntry<T = unknown> {
export interface ExportSummary {
sshHostsImported: number;
sshCredentialsImported: number;
fileManagerItemsImported: number;
pluginItemsImported: number;
dismissedAlertsImported: number;
credentialUsageImported: number;
settingsImported: number;
@@ -864,29 +534,6 @@ export interface ExportSummary {
errors: string[];
}
export interface ImportResult {
success: boolean;
summary: ExportSummary;
}
export interface ExportRequestBody {
password: string;
}
export interface ImportRequestBody {
password: string;
}
export interface ExportPreviewBody {
scope?: string;
includeCredentials?: boolean;
}
export interface RestoreRequestBody {
backupPath: string;
targetPath?: string;
}
// ============================================================================
// DOCKER TYPES
// ============================================================================
-124
View File
@@ -1,124 +0,0 @@
export interface ProxmoxGuest {
name: string;
vmid: number;
type: "qemu" | "lxc";
node: string;
status: string;
ip: string | null;
connectionType: "ssh" | "rdp";
enableDocker: boolean;
}
export interface ProxmoxDiscoverResult {
guests: ProxmoxGuest[];
credentialId: number | null;
defaultCredentialId: number | null;
jumpHosts?: unknown[] | null;
}
export interface ProxmoxSyncResult {
created: number;
updated: number;
markedMissing: number;
skipped: number;
errors: string[];
}
// Frontend-side mirror of the backend Proxmox Stats collectors' return shapes
// (src/backend/hosts/metrics/proxmox/*). Kept in sync by hand, same convention
// as ServerMetrics in main-axios.ts mirroring collectMetrics.
export interface ProxmoxNodeStats {
cpu: {
percent: number | null;
cores: number | null;
load: [number, number, number] | null;
};
memory: {
percent: number | null;
usedGiB: number | null;
totalGiB: number | null;
};
disk: {
percent: number | null;
usedGiB: number | null;
totalGiB: number | null;
};
uptime: {
seconds: number | null;
formatted: string | null;
};
system: {
hostname: string | null;
kernel: string | null;
pveVersion: string | null;
};
}
export interface ProxmoxNodeNetwork {
interfaces: Array<{
name: string;
ip: string | null;
state: string | null;
rxBytes: string | null;
txBytes: string | null;
}>;
}
export interface ProxmoxGuestSummary {
vmid: number;
name: string;
type: "qemu" | "lxc";
status: string;
cpuPercent: number | null;
memPercent: number | null;
memUsedGiB: number | null;
memTotalGiB: number | null;
diskPercent: number | null;
diskUsedGiB: number | null;
diskTotalGiB: number | null;
uptimeSeconds: number | null;
}
export interface ProxmoxGuestsSummary {
guests: ProxmoxGuestSummary[];
counts: { running: number; stopped: number; total: number };
}
export interface ProxmoxStoragePool {
name: string;
type: string;
active: boolean;
enabled: boolean;
usedGiB: number | null;
totalGiB: number | null;
availGiB: number | null;
percent: number | null;
}
export interface ProxmoxStorage {
pools: ProxmoxStoragePool[];
}
export type ProxmoxClusterHealth =
| { clustered: false }
| {
clustered: true;
quorate: boolean;
clusterName: string | null;
nodes: Array<{
name: string;
online: boolean;
local: boolean;
ip: string | null;
}>;
};
export interface ProxmoxStatsSnapshot {
node: ProxmoxNodeStats;
network: ProxmoxNodeNetwork;
guests: ProxmoxGuestsSummary;
storage: ProxmoxStorage;
cluster: ProxmoxClusterHealth;
lastChecked: string;
}
+20 -62
View File
@@ -32,17 +32,7 @@ export type UiAreaKey =
| "dashboard"
| "terminal"
| "fileManager"
| "hostEditor"
| "homepage";
/**
* The homepage area key, named rather than spelled inline in src/ui: it
* happens to share its spelling with the homepage plugin's id, which
* scripts/check-shell-plugin-ids.cjs treats as a plugin id wherever it sees
* that literal under src/ui. This area predates plugins and is unrelated -
* it is core's own widget-visibility override, not the plugin.
*/
export const HOMEPAGE_AREA_KEY: UiAreaKey = "homepage";
| "hostEditor";
export type UiDensity = "comfortable" | "compact";
export type UiTrayTrigger = "always" | "hover" | "click" | "actionsOnly";
@@ -74,6 +64,11 @@ export interface UiCredentialListPreferences {
export interface UiRailPreferences {
hiddenTabs: string[];
/**
* Also hide every plugin rail item that does not set simplePreset. Plugin
* items are only known at runtime, so the preset cannot list them.
*/
hidePluginItems?: boolean;
}
export interface UiDashboardPreferences {
@@ -93,11 +88,6 @@ export interface UiHostEditorPreferences {
mode: UiHostEditorMode;
}
export interface UiHomepagePreferences {
/** null means "never preset-driven" -- a preset must not touch the canvas. */
enabledWidgets: string[] | null;
}
export interface UiAreaPreferences {
chrome: UiChromePreferences;
hostList: UiHostListPreferences;
@@ -107,7 +97,6 @@ export interface UiAreaPreferences {
terminal: UiTerminalPreferences;
fileManager: UiFileManagerPreferences;
hostEditor: UiHostEditorPreferences;
homepage: UiHomepagePreferences;
}
/** A plugin's own area, keyed "plugin:<id>", holding what it declared in contributes.uiPresets. */
@@ -134,34 +123,24 @@ export interface UiPreferences {
const PRESET_VALUES: UiPreset[] = ["simple", "balanced", "advanced", "custom"];
/**
* Rail views Simple keeps. Cutting all the way down to hosts+credentials makes
* the app feel broken, so connections and snippets stay: snippets is the most
* approachable power feature and connections is where troubleshooting starts.
* Core rail views Simple keeps. Cutting all the way down to hosts+credentials
* makes the app feel broken, so connections stays: it is where
* troubleshooting starts. Plugin items opt in with simplePreset.
*/
const SIMPLE_RAIL_VISIBLE = ["hosts", "credentials", "connections", "snippets"];
const SIMPLE_RAIL_VISIBLE = ["hosts", "credentials", "connections"];
/** Every hideable rail view, mirroring HideableRailView in sidebar/AppRail.tsx. */
const ALL_HIDEABLE_RAIL_VIEWS = [
/** Core's hideable rail views, mirroring RAIL_ITEMS in sidebar/rail-items.ts. */
const CORE_HIDEABLE_RAIL_VIEWS = [
"hosts",
"credentials",
"termix-id",
"quick-connect",
"serial",
"ssh-tools",
"snippets",
"macros",
"history",
"split-screen",
"connections",
"session-logs",
"fleets",
"workspaces",
"network_graph",
"homepage",
"ai",
"quick-connect",
"ssh-tools",
"macros",
"split-screen",
];
const SIMPLE_HIDDEN_RAIL_TABS = ALL_HIDEABLE_RAIL_VIEWS.filter(
const SIMPLE_HIDDEN_RAIL_TABS = CORE_HIDEABLE_RAIL_VIEWS.filter(
(view) => !SIMPLE_RAIL_VISIBLE.includes(view),
);
@@ -196,7 +175,7 @@ export const PRESETS: Record<Exclude<UiPreset, "custom">, UiAreaPreferences> = {
rowActions: "essential",
},
credentialList: { density: "comfortable", showTags: false },
rail: { hiddenTabs: SIMPLE_HIDDEN_RAIL_TABS },
rail: { hiddenTabs: SIMPLE_HIDDEN_RAIL_TABS, hidePluginItems: true },
dashboard: {
enabledCards: [
"stats_bar",
@@ -208,7 +187,6 @@ export const PRESETS: Record<Exclude<UiPreset, "custom">, UiAreaPreferences> = {
terminal: { toolbarDensity: "icon" },
fileManager: { viewMode: "grid", showHiddenFiles: false },
hostEditor: { mode: "simple" },
homepage: { enabledWidgets: null },
},
balanced: {
chrome: {
@@ -232,7 +210,6 @@ export const PRESETS: Record<Exclude<UiPreset, "custom">, UiAreaPreferences> = {
fileManager: { viewMode: "grid", showHiddenFiles: false },
// 3 is defaultLayoutFromWidgets's own default, i.e. today's behavior.
hostEditor: { mode: "full" },
homepage: { enabledWidgets: null },
},
advanced: {
chrome: {
@@ -255,7 +232,6 @@ export const PRESETS: Record<Exclude<UiPreset, "custom">, UiAreaPreferences> = {
// List packs more files and metadata per screen than the grid.
fileManager: { viewMode: "list", showHiddenFiles: true },
hostEditor: { mode: "full" },
homepage: { enabledWidgets: null },
},
};
@@ -263,8 +239,7 @@ type FieldSpec =
| { kind: "enum"; values: readonly string[] }
| { kind: "bool" }
| { kind: "int"; min: number; max: number }
| { kind: "stringArray" }
| { kind: "nullableStringArray" };
| { kind: "stringArray" };
/**
* Field descriptors for every area knob. Unlike the flat sanitizers on the
@@ -296,6 +271,7 @@ const AREA_SPECS: {
},
rail: {
hiddenTabs: { kind: "stringArray" },
hidePluginItems: { kind: "bool" },
},
dashboard: {
enabledCards: { kind: "stringArray" },
@@ -313,9 +289,6 @@ const AREA_SPECS: {
hostEditor: {
mode: { kind: "enum", values: ["simple", "full"] },
},
homepage: {
enabledWidgets: { kind: "nullableStringArray" },
},
};
export const UI_AREA_KEYS = Object.keys(AREA_SPECS) as UiAreaKey[];
@@ -340,11 +313,6 @@ function coerce(spec: FieldSpec, value: unknown): unknown | undefined {
return Array.isArray(value)
? value.filter((v): v is string => typeof v === "string")
: undefined;
case "nullableStringArray":
if (value === null) return null;
return Array.isArray(value)
? value.filter((v): v is string => typeof v === "string")
: undefined;
}
}
@@ -532,13 +500,3 @@ export function resolvePluginArea(
export function hasUiOverrides(preferences: UiPreferences): boolean {
return Object.keys(preferences.overrides).length > 0;
}
/**
* What the settings UI should show as the active preset. "custom" is derived
* rather than stored, so clearing every override automatically restores the
* user's chosen preset instead of stranding them on a label.
*/
export function effectivePresetLabel(preferences: UiPreferences): UiPreset {
if (preferences.preset === "custom") return "custom";
return hasUiOverrides(preferences) ? "custom" : preferences.preset;
}
+9 -46
View File
@@ -1,4 +1,4 @@
import type { TerminalConfig } from "./index.js";
import type { SSHAuthType, TerminalConfig } from "./index.js";
import type { HostAuthOverrides } from "./auth-protocols.js";
export type Host = {
@@ -23,16 +23,7 @@ export type Host = {
ram: number | null;
lastAccess: string;
tags?: string[];
authType:
| "password"
| "key"
| "credential"
| "none"
| "opkssh"
| "stepca"
| "tailscale"
| "vault"
| "agent";
authType: SSHAuthType;
shareSshAuth?: boolean;
credentialId?: string;
overrideCredentialUsername?: boolean;
@@ -146,35 +137,15 @@ export type HostFolder = {
sortOrder?: number | null;
};
export type KnownTabType =
/** Core's own tab types. Plugins register theirs at runtime. */
type KnownTabType =
| "dashboard"
| "terminal"
| "local-terminal"
| "rdp"
| "vnc"
| "telnet"
| "host-metrics"
| "proxmox-stats"
| "files"
| "host-manager"
| "user-profile"
| "admin-settings"
| "docker"
| "tunnel"
| "sftp"
| "web-endpoint"
| "network_graph"
| "tmux_monitor" // --- tmux-monitor ---
| "serial"
| "homepage"
| "fleet-inventory"
// Rail panels that can also open full-width in the main area.
| "session-logs"
| "snippets"
| "macros"
| "history"
| "ssh-tools"
| "automations"
| "split-screen";
/**
@@ -273,8 +244,8 @@ export type UiFontId =
| "source-code-pro"
| "caskaydia-cove";
export type ToolsTab =
"ssh-tools" | "snippets" | "macros" | "history" | "split-screen";
/** A tools panel view: core's own, or a rail panel a plugin registered. */
export type ToolsTab = "ssh-tools" | "macros" | "split-screen" | (string & {});
export type SplitMode =
| "none"
| "2-way"
@@ -311,7 +282,7 @@ export type WorkspaceTabSnapshot = {
};
/** One dock's arrangement. `view` is a RailView, or null when the dock is closed. */
export type WorkspaceDockState = {
type WorkspaceDockState = {
view: string | null;
open: boolean;
width: number;
@@ -344,7 +315,7 @@ export type Snippet = {
isNote?: boolean;
};
export const FOLDER_ICONS = [
const FOLDER_ICONS = [
"folder",
"server",
"cloud",
@@ -356,12 +327,4 @@ export const FOLDER_ICONS = [
"cpu",
"globe",
] as const;
export type FolderIconId = (typeof FOLDER_ICONS)[number];
export type SnippetFolder = {
id: number;
name: string;
color: string;
icon: FolderIconId;
open: boolean;
};
type FolderIconId = (typeof FOLDER_ICONS)[number];