mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-30 15:15:24 +00:00
v26.10.1 (#1599)
* feat: AI translations at release, 7 new languages and regional fallback * fix: proper plural forms for count strings and full translations * fix: plural forms for selected, hidden and active counts * fix: synced plugin updates failing on desktop and server-only plugins shown as stopped * fix: restore rounded corners on app icons and favicon * feat: desktop app auto updates with automatic, ask first or never modes * feat: community and custom plugin registries * feat: plugin guide for AI tools in the SDK with a lint check * fix: keep the terminal WebSocket open past nginx's 60s default (#1588) * chore: update changelog * feat: show plugin download counts only and stop CI builds inflating them * feat: hide installed plugins in the browse tab by default * chore: pin bundled plugins to latest releases * chore: update translations * fix: focusing a saved password field and saving no longer erases it * fix: tell desktop users when their 2.8 data needs 2.9 first * fix: point windows update info at the x64 installer * fix: keep desktop client tunnel sockets alive behind proxies * chore: pin bundled plugins to latest releases * fix: cover client tunnel keepalive in the c2s runtime tests * fix: typing after opening a host no longer goes to the host search * fix: accept tab and = separators in SSH config import (#1598) * chore: update changelog * chore: drop plugin versions from core changelog * chore: update translations * chore: lint, format, and bump version to 26.10.1 --------- Co-authored-by: Darnel Hunter <68697450+innotelinc@users.noreply.github.com> Co-authored-by: Iain <Iain1970@protonmail.com>
This commit is contained in:
174 files changed
+94334
-39995
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { isElectron } from "@/lib/electron";
|
||||
|
||||
export type DesktopUpdateMode = "automatic" | "prompt" | "never";
|
||||
|
||||
export type DesktopInstallKind = "auto" | "manual" | "store" | "disabled";
|
||||
|
||||
export type DesktopUpdateStatus =
|
||||
| "idle"
|
||||
| "checking"
|
||||
| "available"
|
||||
| "downloading"
|
||||
| "downloaded"
|
||||
| "up-to-date"
|
||||
| "error";
|
||||
|
||||
export interface DesktopUpdateState {
|
||||
kind: DesktopInstallKind;
|
||||
mode: DesktopUpdateMode;
|
||||
currentVersion: string;
|
||||
status: DesktopUpdateStatus;
|
||||
version: string | null;
|
||||
releaseUrl: string | null;
|
||||
progress: number | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface DesktopUpdatesBridge {
|
||||
getState: () => Promise<DesktopUpdateState | null>;
|
||||
check: () => Promise<DesktopUpdateState | null>;
|
||||
download: () => Promise<DesktopUpdateState | null>;
|
||||
install: () => Promise<boolean>;
|
||||
setMode: (mode: DesktopUpdateMode) => Promise<DesktopUpdateState | null>;
|
||||
onState: (callback: (state: DesktopUpdateState) => void) => () => void;
|
||||
}
|
||||
|
||||
type UpdatesWindow = Window &
|
||||
typeof globalThis & {
|
||||
electronAPI?: { updates?: DesktopUpdatesBridge };
|
||||
};
|
||||
|
||||
/** The main process updater, or null in the browser and on older builds. */
|
||||
export function desktopUpdates(): DesktopUpdatesBridge | null {
|
||||
if (!isElectron()) return null;
|
||||
return (window as UpdatesWindow).electronAPI?.updates ?? null;
|
||||
}
|
||||
|
||||
/** Whether this install has an updater the user can configure. */
|
||||
export function canConfigureUpdates(
|
||||
state: DesktopUpdateState | null,
|
||||
): state is DesktopUpdateState {
|
||||
return !!state && state.kind !== "store" && state.kind !== "disabled";
|
||||
}
|
||||
|
||||
/** Which toast, if any, this state calls for. */
|
||||
export function updateToastFor(
|
||||
state: DesktopUpdateState | null,
|
||||
): "prompt" | "manual" | "ready" | null {
|
||||
if (!state || !state.version || state.mode === "never") return null;
|
||||
if (state.status === "downloaded") return "ready";
|
||||
if (state.status !== "available") return null;
|
||||
if (state.kind === "manual") return "manual";
|
||||
if (state.kind === "auto" && state.mode === "prompt") return "prompt";
|
||||
return null;
|
||||
}
|
||||
|
||||
export function useDesktopUpdate(): DesktopUpdateState | null {
|
||||
const [state, setState] = useState<DesktopUpdateState | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const bridge = desktopUpdates();
|
||||
if (!bridge) return;
|
||||
let active = true;
|
||||
bridge
|
||||
.getState()
|
||||
.then((next) => active && next && setState(next))
|
||||
.catch(() => {});
|
||||
const off = bridge.onState((next) => active && setState(next));
|
||||
return () => {
|
||||
active = false;
|
||||
off();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
}
|
||||
@@ -38,5 +38,7 @@
|
||||
"pluginCatalog": "plugins",
|
||||
"cli": "cli",
|
||||
"develop": "develop",
|
||||
"communityRegistry": "develop/community-registry"
|
||||
"communityRegistry": "develop/community-registry",
|
||||
"pluginRegistries": "guide/plugins#registries",
|
||||
"hostRegistry": "develop/registries"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { isElectron } from "@/lib/electron";
|
||||
|
||||
export type EmbeddedServerFailureReason = "port-in-use" | "crashed";
|
||||
export type EmbeddedServerFailureReason =
|
||||
"port-in-use" | "old-database" | "crashed";
|
||||
|
||||
export interface EmbeddedServerFailure {
|
||||
reason: EmbeddedServerFailureReason;
|
||||
@@ -41,9 +42,13 @@ export async function getEmbeddedServerFailure(): Promise<EmbeddedServerFailure
|
||||
// An unrecognized reason from a newer/older main process still means the
|
||||
// backend is down, which is the part the caller acts on -- so it is
|
||||
// reported as the generic crash rather than dropped.
|
||||
return failure.reason === "port-in-use"
|
||||
? { reason: "port-in-use", port: failure.port ?? null }
|
||||
: { reason: "crashed", port: null };
|
||||
if (failure.reason === "port-in-use") {
|
||||
return { reason: "port-in-use", port: failure.port ?? null };
|
||||
}
|
||||
if (failure.reason === "old-database") {
|
||||
return { reason: "old-database", port: null };
|
||||
}
|
||||
return { reason: "crashed", port: null };
|
||||
} catch {
|
||||
// No status channel, or the main process is tearing down. Neither is
|
||||
// evidence that the backend failed, so keep waiting.
|
||||
|
||||
Reference in new issue
Block a user