@termix-ssh/plugin-sdk 1.0.2 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +202 -0
  3. package/README.md +4 -2
  4. package/cli/commands/build.mjs +44 -19
  5. package/cli/commands/dev.mjs +1 -1
  6. package/cli/commands/migrations.mjs +2 -2
  7. package/cli/commands/openapi.mjs +39 -0
  8. package/cli/commands/validate.mjs +72 -0
  9. package/cli/index.mjs +3 -0
  10. package/cli/lib/css-classes.mjs +37 -0
  11. package/dist/changelog.d.ts +3 -1
  12. package/dist/changelog.js +12 -2
  13. package/dist/db.d.ts +1 -1
  14. package/dist/db.js +1 -1
  15. package/dist/ddl.js +4 -1
  16. package/dist/docs.d.ts +28 -0
  17. package/dist/docs.js +39 -0
  18. package/dist/frontend.d.ts +17 -0
  19. package/dist/frontend.js +8 -0
  20. package/dist/host/{chunk-XKMRKQFN.js → chunk-KOOUKYW3.js} +1 -1
  21. package/dist/host/{chunk-LKYAVM4T.js → chunk-OEOKIKXC.js} +18 -9
  22. package/dist/host/{chunk-SS43S6VI.js → chunk-VETPF5WI.js} +478 -431
  23. package/dist/host/core-classes.json +1 -0
  24. package/dist/host/{linked-server-DW5P3NLZ.js → linked-server-RWBK427T.js} +1 -1
  25. package/dist/host/{rbac-api-JKISXZEP.js → rbac-api-UJGGTQVA.js} +1 -1
  26. package/dist/host/testing-host.js +3 -3
  27. package/dist/host/types/src/types/credential-sidebar-preferences.d.ts +1 -1
  28. package/dist/host/types/src/ui/api/plugins-api.d.ts +6 -0
  29. package/dist/host/types/src/ui/components/docs-link.d.ts +25 -0
  30. package/dist/host/types/src/ui/components/panel-layout.d.ts +3 -1
  31. package/dist/host/types/src/ui/lib/docs.d.ts +10 -0
  32. package/dist/host/types/src/ui/lib/error-message.d.ts +1 -1
  33. package/dist/host/types/src/ui/main-axios.d.ts +1 -2
  34. package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +1 -0
  35. package/dist/host/ui.js +18 -8
  36. package/dist/host/{use-permissions-5CZEA3P4.js → use-permissions-P3DC7ZDR.js} +1 -1
  37. package/dist/host-commands.js +1 -1
  38. package/dist/manifest.d.ts +7 -1
  39. package/dist/manifest.js +48 -1
  40. package/dist/openapi.d.ts +43 -0
  41. package/dist/openapi.js +60 -0
  42. package/dist/testing/setup.js +1 -0
  43. package/package.json +22 -1
  44. package/schema/manifest.schema.json +21 -0
@@ -2,10 +2,11 @@ const __termixNoGlob = () => ({});
2
2
  import {
3
3
  hasPermission,
4
4
  usePermissions
5
- } from "./chunk-XKMRKQFN.js";
5
+ } from "./chunk-KOOUKYW3.js";
6
6
  import {
7
7
  PRESETS,
8
8
  SYNC_CHANGED_EVENT,
9
+ TRUST_TOKEN_KEY,
9
10
  authApi,
10
11
  buildOriginWsUrl,
11
12
  byOrderThenId,
@@ -38,7 +39,7 @@ import {
38
39
  usePluginStore,
39
40
  useTabTypes,
40
41
  websocketAuthProtocols
41
- } from "./chunk-LKYAVM4T.js";
42
+ } from "./chunk-OEOKIKXC.js";
42
43
  import {
43
44
  __export,
44
45
  define_import_meta_env_default
@@ -14572,9 +14573,6 @@ function resetShellBridge() {
14572
14573
  setShellHosts([], false);
14573
14574
  }
14574
14575
 
14575
- // src/ui/components/panel-layout.tsx
14576
- import { Children as Children5, Fragment as Fragment11, useRef as useRef28 } from "react";
14577
-
14578
14576
  // node_modules/lucide-react/dist/esm/icons/index.mjs
14579
14577
  var icons_exports = {};
14580
14578
  __export(icons_exports, {
@@ -48949,9 +48947,295 @@ var __iconData1857 = {
48949
48947
  __iconData1857.node;
48950
48948
  var ZoomOut = createLucideIcon(__iconData1857);
48951
48949
 
48952
- // src/ui/components/panel-layout.tsx
48950
+ // src/ui/components/docs-link.tsx
48951
+ import { useTranslation as useTranslation2 } from "react-i18next";
48952
+
48953
+ // packages/plugin-sdk/dist/docs.js
48954
+ function pluginDocsUrl(value) {
48955
+ if (typeof value !== "string")
48956
+ return null;
48957
+ try {
48958
+ const url = new URL(value.trim());
48959
+ if (url.protocol !== "https:")
48960
+ return null;
48961
+ return url.toString().replace(/\/$/, "");
48962
+ } catch {
48963
+ return null;
48964
+ }
48965
+ }
48966
+ function pluginDocsPage(base, page, anchor) {
48967
+ const clean = (page ?? "").replace(/^\/+|\/+$/g, "");
48968
+ const url = clean ? `${base}/${clean}` : base;
48969
+ return anchor ? `${url}#${anchor.replace(/^#/, "")}` : url;
48970
+ }
48971
+
48972
+ // src/ui/lib/docs-pages.json
48973
+ var docs_pages_default = {
48974
+ home: "",
48975
+ install: "install",
48976
+ updating: "install/updating",
48977
+ betas: "configure/betas",
48978
+ envVars: "configure/environment-variables",
48979
+ https: "configure/https",
48980
+ database: "configure/database",
48981
+ reverseProxy: "configure/reverse-proxy",
48982
+ desktopSync: "configure/desktop-sync",
48983
+ interface: "guide/interface",
48984
+ dashboard: "guide/dashboard",
48985
+ manage: "guide/manage",
48986
+ hosts: "guide/hosts",
48987
+ portKnocking: "guide/hosts#port-knocking",
48988
+ hostDefaults: "guide/host-defaults",
48989
+ credentials: "guide/credentials",
48990
+ sharing: "guide/sharing",
48991
+ account: "guide/account",
48992
+ accountSecurity: "guide/account#security",
48993
+ accountData: "guide/account#your-data",
48994
+ apiKeys: "guide/api-keys",
48995
+ commandPalette: "guide/command-palette",
48996
+ settings: "guide/settings",
48997
+ interfaceSettings: "guide/settings#interface",
48998
+ appearance: "guide/settings#appearance",
48999
+ keybindings: "guide/settings#keybindings",
49000
+ users: "guide/users",
49001
+ sessions: "guide/users#sessions",
49002
+ roles: "guide/roles",
49003
+ adminGeneral: "guide/admin-settings",
49004
+ branding: "guide/admin-settings#branding",
49005
+ adminDatabase: "guide/admin-settings#database",
49006
+ adminSsl: "configure/https",
49007
+ auditLog: "guide/audit-log",
49008
+ importExport: "guide/import-export",
49009
+ plugins: "guide/plugins",
49010
+ pluginCatalog: "plugins",
49011
+ cli: "cli",
49012
+ develop: "develop",
49013
+ communityRegistry: "develop/community-registry"
49014
+ };
49015
+
49016
+ // src/ui/lib/docs.ts
49017
+ var DOCS_URL = "https://docs.termix.site";
49018
+ function docsUrl(page = "home") {
49019
+ const path2 = docs_pages_default[page] ?? "";
49020
+ return path2 ? `${DOCS_URL}/${path2}` : `${DOCS_URL}/`;
49021
+ }
49022
+
49023
+ // src/ui/plugin-host/scope.tsx
49024
+ import {
49025
+ Component as Component2,
49026
+ createContext as createContext6,
49027
+ Suspense,
49028
+ useContext as useContext5
49029
+ } from "react";
49030
+
49031
+ // src/ui/plugin-host/host-scope.ts
49032
+ function isHostLike(value) {
49033
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
49034
+ if (!("id" in value)) return false;
49035
+ return "pluginSettings" in value || "ip" in value && "port" in value;
49036
+ }
49037
+ var cache = /* @__PURE__ */ new WeakMap();
49038
+ function scopeHost(value, pluginId) {
49039
+ if (!isHostLike(value)) return value;
49040
+ const full = shellHost(value.id)?.pluginSettings ?? value.pluginSettings;
49041
+ let perPlugin = cache.get(value);
49042
+ const hit = perPlugin?.get(pluginId);
49043
+ if (hit && hit.source === full) return hit.scoped;
49044
+ const own = full?.[pluginId];
49045
+ const scoped = { ...value, pluginSettings: own ? { [pluginId]: own } : {} };
49046
+ if (!perPlugin) {
49047
+ perPlugin = /* @__PURE__ */ new Map();
49048
+ cache.set(value, perPlugin);
49049
+ }
49050
+ perPlugin.set(pluginId, { source: full, scoped });
49051
+ return scoped;
49052
+ }
49053
+ function scopeHostArgs(args, pluginId) {
49054
+ return args.map(
49055
+ (arg) => isHostLike(arg) ? scopeHost(arg, pluginId) : scopeHostFields(arg, pluginId)
49056
+ );
49057
+ }
49058
+ var HOST_FIELDS = ["host", "sshHost", "hostConfig"];
49059
+ function scopeHostFields(value, pluginId) {
49060
+ if (!value || typeof value !== "object" || Array.isArray(value)) return value;
49061
+ const record = value;
49062
+ let next = null;
49063
+ for (const field of HOST_FIELDS) {
49064
+ if (!(field in record)) continue;
49065
+ const scoped = scopeHost(record[field], pluginId);
49066
+ if (scoped === record[field]) continue;
49067
+ next ??= { ...record };
49068
+ next[field] = scoped;
49069
+ }
49070
+ return next ?? value;
49071
+ }
49072
+ function scopeHostCallback(fn, pluginId) {
49073
+ if (!fn) return fn;
49074
+ return ((...args) => fn(...scopeHostArgs(args, pluginId)));
49075
+ }
49076
+
49077
+ // src/ui/plugin-host/scope.tsx
48953
49078
  import { useTranslation } from "react-i18next";
48954
- import { Fragment as Fragment12, jsx as jsx29, jsxs as jsxs7 } from "react/jsx-runtime";
49079
+ import { jsx as jsx29, jsxs as jsxs7 } from "react/jsx-runtime";
49080
+ var PluginScopeContext = createContext6(null);
49081
+ function usePluginScope() {
49082
+ return useContext5(PluginScopeContext);
49083
+ }
49084
+ function CrashNotice({
49085
+ pluginId,
49086
+ onRetry
49087
+ }) {
49088
+ const { t } = useTranslation();
49089
+ const name = getPluginRecord(pluginId)?.summary.name ?? pluginId;
49090
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col items-center justify-center gap-2 p-4 text-center text-sm text-muted-foreground", children: [
49091
+ /* @__PURE__ */ jsx29("span", { children: t("plugins.runtime.crashed", { name }) }),
49092
+ /* @__PURE__ */ jsx29(
49093
+ "button",
49094
+ {
49095
+ type: "button",
49096
+ className: "text-xs underline underline-offset-2 hover:text-foreground",
49097
+ onClick: onRetry,
49098
+ children: t("plugins.runtime.retry")
49099
+ }
49100
+ )
49101
+ ] });
49102
+ }
49103
+ var PluginErrorBoundary = class extends Component2 {
49104
+ state = { error: false };
49105
+ static getDerivedStateFromError() {
49106
+ return { error: true };
49107
+ }
49108
+ componentDidCatch(error, info) {
49109
+ console.error(
49110
+ `[plugins] ${this.props.pluginId} crashed while rendering`,
49111
+ error,
49112
+ info.componentStack
49113
+ );
49114
+ }
49115
+ render() {
49116
+ if (this.state.error) {
49117
+ return /* @__PURE__ */ jsx29(
49118
+ CrashNotice,
49119
+ {
49120
+ pluginId: this.props.pluginId,
49121
+ onRetry: () => this.setState({ error: false })
49122
+ }
49123
+ );
49124
+ }
49125
+ return this.props.children;
49126
+ }
49127
+ };
49128
+ function PluginScope({
49129
+ pluginId,
49130
+ children
49131
+ }) {
49132
+ return /* @__PURE__ */ jsx29(PluginScopeContext.Provider, { value: pluginId, children: /* @__PURE__ */ jsx29(PluginErrorBoundary, { pluginId, children: /* @__PURE__ */ jsx29(Suspense, { fallback: null, children }) }) });
49133
+ }
49134
+ function withPluginScope(pluginId, Inner) {
49135
+ function Scoped(props) {
49136
+ return /* @__PURE__ */ jsx29(PluginScope, { pluginId, children: /* @__PURE__ */ jsx29(Inner, { ...scopeHostFields(props, pluginId) }) });
49137
+ }
49138
+ Scoped.displayName = `Plugin(${pluginId}:${Inner.displayName ?? Inner.name ?? "Component"})`;
49139
+ return Scoped;
49140
+ }
49141
+ var IconBoundary = class extends Component2 {
49142
+ state = { error: false };
49143
+ static getDerivedStateFromError() {
49144
+ return { error: true };
49145
+ }
49146
+ componentDidCatch(error) {
49147
+ console.error(`[plugins] ${this.props.pluginId} icon failed`, error);
49148
+ }
49149
+ render() {
49150
+ return this.state.error ? null : this.props.children;
49151
+ }
49152
+ };
49153
+ function withIconBoundary(pluginId, Icon2) {
49154
+ if (!Icon2) return void 0;
49155
+ function SafeIcon(props) {
49156
+ return /* @__PURE__ */ jsx29(IconBoundary, { pluginId, children: /* @__PURE__ */ jsx29(Icon2, { ...props }) });
49157
+ }
49158
+ SafeIcon.displayName = `PluginIcon(${pluginId})`;
49159
+ return SafeIcon;
49160
+ }
49161
+ function guardCallback(pluginId, fn, fallback) {
49162
+ if (!fn) return void 0;
49163
+ return (...args) => {
49164
+ try {
49165
+ return fn(...args);
49166
+ } catch (error) {
49167
+ console.error(`[plugins] ${pluginId} callback failed`, error);
49168
+ return fallback;
49169
+ }
49170
+ };
49171
+ }
49172
+
49173
+ // src/ui/components/docs-link.tsx
49174
+ import { jsx as jsx30 } from "react/jsx-runtime";
49175
+ function usePluginDocsUrl(pluginId, page, anchor) {
49176
+ const { records } = usePluginStore();
49177
+ const base = pluginId ? records.get(pluginId)?.summary.docs : void 0;
49178
+ return base ? pluginDocsPage(base, page, anchor) : null;
49179
+ }
49180
+ function DocsLink({
49181
+ core,
49182
+ page,
49183
+ anchor,
49184
+ href,
49185
+ variant = "text",
49186
+ children,
49187
+ className
49188
+ }) {
49189
+ const { t } = useTranslation2();
49190
+ const pluginId = usePluginScope();
49191
+ const pluginUrl = usePluginDocsUrl(
49192
+ page !== void 0 ? pluginId : null,
49193
+ page,
49194
+ anchor
49195
+ );
49196
+ const url = href ?? (core ? withAnchor(docsUrl(core), anchor) : null) ?? pluginUrl;
49197
+ if (!url) return null;
49198
+ const label = t("common.docs");
49199
+ if (variant === "icon") {
49200
+ return /* @__PURE__ */ jsx30(
49201
+ "a",
49202
+ {
49203
+ href: url,
49204
+ target: "_blank",
49205
+ rel: "noopener noreferrer",
49206
+ title: label,
49207
+ "aria-label": label,
49208
+ className: cn(
49209
+ "flex size-7 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground",
49210
+ className
49211
+ ),
49212
+ children: /* @__PURE__ */ jsx30(BookOpen, { className: "size-3.5" })
49213
+ }
49214
+ );
49215
+ }
49216
+ return /* @__PURE__ */ jsx30(
49217
+ "a",
49218
+ {
49219
+ href: url,
49220
+ target: "_blank",
49221
+ rel: "noopener noreferrer",
49222
+ className: cn(
49223
+ "shrink-0 text-[10px] text-accent-brand hover:underline",
49224
+ className
49225
+ ),
49226
+ children: children ?? label
49227
+ }
49228
+ );
49229
+ }
49230
+ function withAnchor(url, anchor) {
49231
+ if (!anchor) return url;
49232
+ return `${url.split("#")[0]}#${anchor.replace(/^#/, "")}`;
49233
+ }
49234
+
49235
+ // src/ui/components/panel-layout.tsx
49236
+ import { Children as Children5, Fragment as Fragment11, useRef as useRef28 } from "react";
49237
+ import { useTranslation as useTranslation3 } from "react-i18next";
49238
+ import { Fragment as Fragment12, jsx as jsx31, jsxs as jsxs8 } from "react/jsx-runtime";
48955
49239
  var PANEL = {
48956
49240
  body: "p-2.5",
48957
49241
  gap: "gap-2",
@@ -48970,10 +49254,11 @@ function PanelShell({
48970
49254
  tabs,
48971
49255
  footer,
48972
49256
  scroll = true,
49257
+ docs,
48973
49258
  className,
48974
49259
  children
48975
49260
  }) {
48976
- const body = /* @__PURE__ */ jsx29(
49261
+ const body = /* @__PURE__ */ jsx31(
48977
49262
  "div",
48978
49263
  {
48979
49264
  className: cn(
@@ -48984,43 +49269,49 @@ function PanelShell({
48984
49269
  children
48985
49270
  }
48986
49271
  );
48987
- const bands = /* @__PURE__ */ jsxs7(Fragment12, { children: [
48988
- toolbar && /* @__PURE__ */ jsx29(
49272
+ const bands = /* @__PURE__ */ jsxs8(Fragment12, { children: [
49273
+ toolbar && /* @__PURE__ */ jsx31(
48989
49274
  "div",
48990
49275
  {
48991
49276
  className: `flex shrink-0 items-center gap-2 border-b border-border ${PANEL.band}`,
48992
49277
  children: toolbar
48993
49278
  }
48994
49279
  ),
48995
- tabs && /* @__PURE__ */ jsx29("div", { className: "shrink-0 border-b border-border px-1", children: tabs })
49280
+ tabs && /* @__PURE__ */ jsx31("div", { className: "shrink-0 border-b border-border px-1", children: tabs })
48996
49281
  ] });
48997
- const foot = footer && /* @__PURE__ */ jsx29("div", { className: "flex shrink-0 items-center gap-2 border-t border-border px-3 py-2", children: footer });
49282
+ const foot = footer && /* @__PURE__ */ jsx31("div", { className: "flex shrink-0 items-center gap-2 border-t border-border px-3 py-2", children: footer });
48998
49283
  if (!chrome) {
48999
- return /* @__PURE__ */ jsxs7("div", { className: "flex min-h-0 flex-1 flex-col", children: [
49284
+ return /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-1 flex-col", children: [
49000
49285
  bands,
49001
49286
  body,
49002
49287
  foot
49003
49288
  ] });
49004
49289
  }
49005
- return /* @__PURE__ */ jsxs7("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background", children: [
49006
- /* @__PURE__ */ jsxs7(
49290
+ return /* @__PURE__ */ jsxs8("div", { className: "flex h-full min-h-0 flex-1 flex-col overflow-hidden bg-background", children: [
49291
+ /* @__PURE__ */ jsxs8(
49007
49292
  "header",
49008
49293
  {
49009
49294
  className: `flex ${PANEL.header} shrink-0 flex-row items-center border-b border-border`,
49010
49295
  children: [
49011
- leading && /* @__PURE__ */ jsxs7(Fragment12, { children: [
49012
- leading,
49013
- /* @__PURE__ */ jsx29(Separator4, { orientation: "vertical" })
49014
- ] }),
49015
- /* @__PURE__ */ jsxs7("div", { className: "flex min-w-0 flex-1 items-center gap-2 px-3", children: [
49016
- icon && /* @__PURE__ */ jsx29("span", { className: "shrink-0 text-accent-brand", children: icon }),
49017
- /* @__PURE__ */ jsx29("span", { className: "truncate text-base font-bold tracking-tight", children: title }),
49018
- status && /* @__PURE__ */ jsxs7(Fragment12, { children: [
49019
- /* @__PURE__ */ jsx29(Separator4, { orientation: "vertical", className: "h-4 self-center" }),
49020
- /* @__PURE__ */ jsx29("span", { className: "truncate text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: status })
49296
+ leading && /* @__PURE__ */ jsx31("div", { className: "flex h-full shrink-0 border-r border-border", children: leading }),
49297
+ /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-1 items-center gap-2 px-3", children: [
49298
+ icon && /* @__PURE__ */ jsx31("span", { className: "shrink-0 text-accent-brand", children: icon }),
49299
+ /* @__PURE__ */ jsx31("span", { className: "truncate text-base font-bold tracking-tight", children: title }),
49300
+ status && /* @__PURE__ */ jsxs8(Fragment12, { children: [
49301
+ /* @__PURE__ */ jsx31(
49302
+ Separator4,
49303
+ {
49304
+ orientation: "vertical",
49305
+ className: "h-4 data-[orientation=vertical]:self-center"
49306
+ }
49307
+ ),
49308
+ /* @__PURE__ */ jsx31("span", { className: "truncate text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: status })
49021
49309
  ] })
49022
49310
  ] }),
49023
- actions4 && /* @__PURE__ */ jsx29("div", { className: "flex shrink-0 items-center gap-1 px-2", children: actions4 })
49311
+ (actions4 || docs) && /* @__PURE__ */ jsxs8("div", { className: "flex shrink-0 items-center gap-1 px-2", children: [
49312
+ actions4,
49313
+ docs && /* @__PURE__ */ jsx31(DocsLink, { href: docs, variant: "icon" })
49314
+ ] })
49024
49315
  ]
49025
49316
  }
49026
49317
  ),
@@ -49034,9 +49325,9 @@ function BackButton({
49034
49325
  label,
49035
49326
  className
49036
49327
  }) {
49037
- const { t } = useTranslation();
49328
+ const { t } = useTranslation3();
49038
49329
  const text = label ?? t("common.back");
49039
- return /* @__PURE__ */ jsx29(
49330
+ return /* @__PURE__ */ jsx31(
49040
49331
  "button",
49041
49332
  {
49042
49333
  type: "button",
@@ -49047,7 +49338,7 @@ function BackButton({
49047
49338
  "flex h-full w-12.5 shrink-0 items-center justify-center text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground focus-visible:ring-1 focus-visible:ring-ring",
49048
49339
  className
49049
49340
  ),
49050
- children: /* @__PURE__ */ jsx29(ArrowLeft, { className: "size-4" })
49341
+ children: /* @__PURE__ */ jsx31(ArrowLeft, { className: "size-4" })
49051
49342
  }
49052
49343
  );
49053
49344
  }
@@ -49063,10 +49354,10 @@ function PanelSearch({
49063
49354
  inputRef,
49064
49355
  className
49065
49356
  }) {
49066
- const { t } = useTranslation();
49357
+ const { t } = useTranslation3();
49067
49358
  const localRef = useRef28(null);
49068
49359
  const text = placeholder ?? t("common.search");
49069
- return /* @__PURE__ */ jsxs7(
49360
+ return /* @__PURE__ */ jsxs8(
49070
49361
  "div",
49071
49362
  {
49072
49363
  className: cn(
@@ -49075,8 +49366,8 @@ function PanelSearch({
49075
49366
  className
49076
49367
  ),
49077
49368
  children: [
49078
- /* @__PURE__ */ jsx29(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
49079
- /* @__PURE__ */ jsx29(
49369
+ /* @__PURE__ */ jsx31(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" }),
49370
+ /* @__PURE__ */ jsx31(
49080
49371
  "input",
49081
49372
  {
49082
49373
  ref: (el) => {
@@ -49106,7 +49397,7 @@ function PanelSearch({
49106
49397
  className: "h-8 w-full min-w-0 border border-input bg-transparent pl-8 pr-7 text-xs outline-none transition-colors placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-1 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-input/30"
49107
49398
  }
49108
49399
  ),
49109
- value && /* @__PURE__ */ jsx29(
49400
+ value && /* @__PURE__ */ jsx31(
49110
49401
  "button",
49111
49402
  {
49112
49403
  type: "button",
@@ -49117,7 +49408,7 @@ function PanelSearch({
49117
49408
  localRef.current?.focus();
49118
49409
  },
49119
49410
  className: "absolute right-1.5 top-1/2 flex size-5 -translate-y-1/2 items-center justify-center text-muted-foreground hover:text-foreground",
49120
- children: /* @__PURE__ */ jsx29(X, { className: "size-3" })
49411
+ children: /* @__PURE__ */ jsx31(X, { className: "size-3" })
49121
49412
  }
49122
49413
  )
49123
49414
  ]
@@ -49130,14 +49421,14 @@ function Segmented({
49130
49421
  options: options2,
49131
49422
  className
49132
49423
  }) {
49133
- return /* @__PURE__ */ jsx29(
49424
+ return /* @__PURE__ */ jsx31(
49134
49425
  "div",
49135
49426
  {
49136
49427
  role: "radiogroup",
49137
49428
  className: cn("flex items-center border border-border", className),
49138
49429
  children: options2.map((option, i) => {
49139
49430
  const active = option.value === value;
49140
- return /* @__PURE__ */ jsxs7(
49431
+ return /* @__PURE__ */ jsxs8(
49141
49432
  "button",
49142
49433
  {
49143
49434
  type: "button",
@@ -49153,7 +49444,7 @@ function Segmented({
49153
49444
  children: [
49154
49445
  option.icon,
49155
49446
  option.label,
49156
- option.count !== void 0 && /* @__PURE__ */ jsx29("span", { className: "text-[10px] tabular-nums opacity-60", children: option.count })
49447
+ option.count !== void 0 && /* @__PURE__ */ jsx31("span", { className: "text-[10px] tabular-nums opacity-60", children: option.count })
49157
49448
  ]
49158
49449
  },
49159
49450
  option.value
@@ -49168,35 +49459,35 @@ function ViewToggle({
49168
49459
  density,
49169
49460
  onDensity
49170
49461
  }) {
49171
- const { t } = useTranslation();
49172
- return /* @__PURE__ */ jsxs7("div", { className: "flex items-center border border-border", children: [
49173
- /* @__PURE__ */ jsx29(
49462
+ const { t } = useTranslation3();
49463
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center border border-border", children: [
49464
+ /* @__PURE__ */ jsx31(
49174
49465
  ToggleButton,
49175
49466
  {
49176
49467
  active: view === "grid",
49177
49468
  onClick: () => onView("grid"),
49178
49469
  title: t("panel.gridView"),
49179
- children: /* @__PURE__ */ jsx29(Grid3x3, { className: "size-4" })
49470
+ children: /* @__PURE__ */ jsx31(Grid3x3, { className: "size-4" })
49180
49471
  }
49181
49472
  ),
49182
- /* @__PURE__ */ jsx29(
49473
+ /* @__PURE__ */ jsx31(
49183
49474
  ToggleButton,
49184
49475
  {
49185
49476
  active: view === "list",
49186
49477
  onClick: () => onView("list"),
49187
49478
  title: t("panel.listView"),
49188
49479
  bordered: true,
49189
- children: /* @__PURE__ */ jsx29(List2, { className: "size-4" })
49480
+ children: /* @__PURE__ */ jsx31(List2, { className: "size-4" })
49190
49481
  }
49191
49482
  ),
49192
- density && onDensity && /* @__PURE__ */ jsx29(
49483
+ density && onDensity && /* @__PURE__ */ jsx31(
49193
49484
  ToggleButton,
49194
49485
  {
49195
49486
  active: density === "compact",
49196
49487
  onClick: () => onDensity(density === "compact" ? "comfortable" : "compact"),
49197
49488
  title: density === "compact" ? t("panel.comfortableRows") : t("panel.compactRows"),
49198
49489
  bordered: true,
49199
- children: /* @__PURE__ */ jsx29(Rows3, { className: "size-4" })
49490
+ children: /* @__PURE__ */ jsx31(Rows3, { className: "size-4" })
49200
49491
  }
49201
49492
  )
49202
49493
  ] });
@@ -49208,7 +49499,7 @@ function ToggleButton({
49208
49499
  bordered,
49209
49500
  children
49210
49501
  }) {
49211
- return /* @__PURE__ */ jsx29(
49502
+ return /* @__PURE__ */ jsx31(
49212
49503
  "button",
49213
49504
  {
49214
49505
  type: "button",
@@ -49231,9 +49522,9 @@ function Facts({
49231
49522
  }) {
49232
49523
  const items = Children5.toArray(children).filter(Boolean);
49233
49524
  if (items.length === 0) return null;
49234
- return /* @__PURE__ */ jsx29("span", { className: cn("flex min-w-0 items-center gap-2", className), children: items.map((child, i) => /* @__PURE__ */ jsxs7(Fragment11, { children: [
49235
- i > 0 && /* @__PURE__ */ jsx29("span", { "aria-hidden": true, className: "h-3 w-px shrink-0 bg-border" }),
49236
- /* @__PURE__ */ jsx29("span", { className: "min-w-0 truncate", children: child })
49525
+ return /* @__PURE__ */ jsx31("span", { className: cn("flex min-w-0 items-center gap-2", className), children: items.map((child, i) => /* @__PURE__ */ jsxs8(Fragment11, { children: [
49526
+ i > 0 && /* @__PURE__ */ jsx31("span", { "aria-hidden": true, className: "h-3 w-px shrink-0 bg-border" }),
49527
+ /* @__PURE__ */ jsx31("span", { className: "min-w-0 truncate", children: child })
49237
49528
  ] }, i)) });
49238
49529
  }
49239
49530
  function GroupHeading({
@@ -49242,10 +49533,10 @@ function GroupHeading({
49242
49533
  action,
49243
49534
  className
49244
49535
  }) {
49245
- return /* @__PURE__ */ jsxs7("div", { className: cn("flex items-center gap-2", className), children: [
49246
- /* @__PURE__ */ jsx29("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: title }),
49247
- count3 !== void 0 && /* @__PURE__ */ jsx29("span", { className: "text-[10px] tabular-nums text-muted-foreground/60", children: count3 }),
49248
- /* @__PURE__ */ jsx29("div", { className: "h-px flex-1 bg-border" }),
49536
+ return /* @__PURE__ */ jsxs8("div", { className: cn("flex items-center gap-2", className), children: [
49537
+ /* @__PURE__ */ jsx31("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: title }),
49538
+ count3 !== void 0 && /* @__PURE__ */ jsx31("span", { className: "text-[10px] tabular-nums text-muted-foreground/60", children: count3 }),
49539
+ /* @__PURE__ */ jsx31("div", { className: "h-px flex-1 bg-border" }),
49249
49540
  action
49250
49541
  ] });
49251
49542
  }
@@ -49257,9 +49548,9 @@ var PROMPT_DESTRUCTIVE_BUTTON = "inline-flex h-7 items-center justify-center gap
49257
49548
 
49258
49549
  // src/ui/components/surface/surface-scope.tsx
49259
49550
  import {
49260
- createContext as createContext6,
49551
+ createContext as createContext7,
49261
49552
  useCallback as useCallback18,
49262
- useContext as useContext5,
49553
+ useContext as useContext6,
49263
49554
  useEffect as useEffect27,
49264
49555
  useId as useId2,
49265
49556
  useLayoutEffect as useLayoutEffect5,
@@ -49268,9 +49559,9 @@ import {
49268
49559
  useState as useState25
49269
49560
  } from "react";
49270
49561
  import { createPortal as createPortal3 } from "react-dom";
49271
- import { useTranslation as useTranslation2 } from "react-i18next";
49272
- import { Fragment as Fragment13, jsx as jsx30, jsxs as jsxs8 } from "react/jsx-runtime";
49273
- var ScopeContext = createContext6(null);
49562
+ import { useTranslation as useTranslation4 } from "react-i18next";
49563
+ import { Fragment as Fragment13, jsx as jsx32, jsxs as jsxs9 } from "react/jsx-runtime";
49564
+ var ScopeContext = createContext7(null);
49274
49565
  function widest(widths) {
49275
49566
  if (widths.includes("wide")) return "wide";
49276
49567
  return widths.some(Boolean);
@@ -49339,7 +49630,7 @@ function SurfaceScope({
49339
49630
  [kind, overlay, openView, closeView, views, confirm, close]
49340
49631
  );
49341
49632
  const current = confirms[0];
49342
- return /* @__PURE__ */ jsx30(ScopeContext.Provider, { value, children: /* @__PURE__ */ jsxs8(
49633
+ return /* @__PURE__ */ jsx32(ScopeContext.Provider, { value, children: /* @__PURE__ */ jsxs9(
49343
49634
  "div",
49344
49635
  {
49345
49636
  className: cn(
@@ -49348,7 +49639,7 @@ function SurfaceScope({
49348
49639
  ),
49349
49640
  children: [
49350
49641
  children,
49351
- /* @__PURE__ */ jsx30(
49642
+ /* @__PURE__ */ jsx32(
49352
49643
  "div",
49353
49644
  {
49354
49645
  ref: setOverlay,
@@ -49358,7 +49649,7 @@ function SurfaceScope({
49358
49649
  )
49359
49650
  }
49360
49651
  ),
49361
- current && /* @__PURE__ */ jsx30(
49652
+ current && /* @__PURE__ */ jsx32(
49362
49653
  ConfirmCard,
49363
49654
  {
49364
49655
  options: current,
@@ -49371,13 +49662,13 @@ function SurfaceScope({
49371
49662
  ) });
49372
49663
  }
49373
49664
  function useSurfaceKind() {
49374
- return useContext5(ScopeContext)?.kind ?? null;
49665
+ return useContext6(ScopeContext)?.kind ?? null;
49375
49666
  }
49376
49667
  function useSurfaceClose() {
49377
- return useContext5(ScopeContext)?.close ?? null;
49668
+ return useContext6(ScopeContext)?.close ?? null;
49378
49669
  }
49379
49670
  function useConfirm() {
49380
- const scope = useContext5(ScopeContext);
49671
+ const scope = useContext6(ScopeContext);
49381
49672
  return useCallback18(
49382
49673
  (options2) => {
49383
49674
  if (scope) return scope.confirm(options2);
@@ -49391,7 +49682,7 @@ function ConfirmCard({
49391
49682
  options: options2,
49392
49683
  onSettle
49393
49684
  }) {
49394
- const { t } = useTranslation2();
49685
+ const { t } = useTranslation4();
49395
49686
  const confirmRef = useRef29(null);
49396
49687
  const titleId = useId2();
49397
49688
  const destructive = options2.destructive ?? true;
@@ -49416,7 +49707,7 @@ function ConfirmCard({
49416
49707
  if (previous && document.contains(previous)) previous.focus();
49417
49708
  };
49418
49709
  }, []);
49419
- return /* @__PURE__ */ jsx30(
49710
+ return /* @__PURE__ */ jsx32(
49420
49711
  PanePrompt,
49421
49712
  {
49422
49713
  open: true,
@@ -49427,8 +49718,8 @@ function ConfirmCard({
49427
49718
  description: options2.description,
49428
49719
  tone: destructive ? "destructive" : "default",
49429
49720
  onCancel: () => onSettle(false),
49430
- actions: /* @__PURE__ */ jsxs8(Fragment13, { children: [
49431
- /* @__PURE__ */ jsx30(
49721
+ actions: /* @__PURE__ */ jsxs9(Fragment13, { children: [
49722
+ /* @__PURE__ */ jsx32(
49432
49723
  "button",
49433
49724
  {
49434
49725
  type: "button",
@@ -49437,7 +49728,7 @@ function ConfirmCard({
49437
49728
  children: options2.cancelLabel ?? t("common.cancel")
49438
49729
  }
49439
49730
  ),
49440
- /* @__PURE__ */ jsxs8(
49731
+ /* @__PURE__ */ jsxs9(
49441
49732
  "button",
49442
49733
  {
49443
49734
  ref: confirmRef,
@@ -49446,7 +49737,7 @@ function ConfirmCard({
49446
49737
  className: destructive ? PROMPT_DESTRUCTIVE_BUTTON : PROMPT_PRIMARY_BUTTON,
49447
49738
  children: [
49448
49739
  options2.confirmLabel ?? (destructive ? t("common.delete") : t("common.confirm")),
49449
- /* @__PURE__ */ jsx30("span", { "aria-hidden": true, className: "ml-1.5 opacity-60", children: "\u21B5" })
49740
+ /* @__PURE__ */ jsx32("span", { "aria-hidden": true, className: "ml-1.5 opacity-60", children: "\u21B5" })
49450
49741
  ]
49451
49742
  }
49452
49743
  )
@@ -49473,31 +49764,31 @@ function PanePrompt({
49473
49764
  const fallbackId = useId2();
49474
49765
  if (!open) return null;
49475
49766
  const headingId = titleId ?? fallbackId;
49476
- return /* @__PURE__ */ jsx30(
49767
+ return /* @__PURE__ */ jsx32(
49477
49768
  "div",
49478
49769
  {
49479
49770
  className: cn(
49480
- "motion-context-enter absolute inset-0 flex items-center justify-center overflow-y-auto p-4",
49481
- layer === "connection" ? "z-500 bg-canvas" : "z-50 bg-background/80 backdrop-blur-sm"
49771
+ "motion-fade-enter absolute inset-0 flex items-center justify-center overflow-y-auto p-4",
49772
+ layer === "connection" ? "z-500 bg-canvas" : "z-50 bg-background/85"
49482
49773
  ),
49483
49774
  style: layer === "connection" && backgroundColor ? { backgroundColor } : void 0,
49484
49775
  onMouseDown: (e) => {
49485
49776
  if (e.target === e.currentTarget) onCancel?.();
49486
49777
  },
49487
- children: /* @__PURE__ */ jsxs8(
49778
+ children: /* @__PURE__ */ jsxs9(
49488
49779
  "div",
49489
49780
  {
49490
49781
  role,
49491
49782
  "aria-modal": "true",
49492
49783
  "aria-labelledby": labelledBy ?? headingId,
49493
49784
  className: cn(
49494
- "my-auto flex w-full max-w-sm flex-col border bg-popover shadow-xl",
49785
+ "motion-context-enter my-auto flex w-full max-w-sm flex-col border bg-popover shadow-xl",
49495
49786
  tone === "destructive" ? "border-destructive/40" : "border-border",
49496
49787
  className
49497
49788
  ),
49498
49789
  children: [
49499
- /* @__PURE__ */ jsxs8("div", { className: "flex items-start gap-2.5 px-4 pt-4", children: [
49500
- icon && /* @__PURE__ */ jsx30(
49790
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-start gap-2.5 px-4 pt-4", children: [
49791
+ icon && /* @__PURE__ */ jsx32(
49501
49792
  "span",
49502
49793
  {
49503
49794
  className: cn(
@@ -49507,8 +49798,8 @@ function PanePrompt({
49507
49798
  children: icon
49508
49799
  }
49509
49800
  ),
49510
- /* @__PURE__ */ jsxs8("div", { className: "flex min-w-0 flex-col gap-1", children: [
49511
- /* @__PURE__ */ jsx30(
49801
+ /* @__PURE__ */ jsxs9("div", { className: "flex min-w-0 flex-col gap-1", children: [
49802
+ /* @__PURE__ */ jsx32(
49512
49803
  "p",
49513
49804
  {
49514
49805
  id: headingId,
@@ -49516,12 +49807,12 @@ function PanePrompt({
49516
49807
  children: title
49517
49808
  }
49518
49809
  ),
49519
- description && /* @__PURE__ */ jsx30("div", { className: "text-xs leading-snug text-muted-foreground", children: description })
49810
+ description && /* @__PURE__ */ jsx32("div", { className: "text-xs leading-snug text-muted-foreground", children: description })
49520
49811
  ] })
49521
49812
  ] }),
49522
- children && /* @__PURE__ */ jsx30("div", { className: "px-4 pt-3", children }),
49523
- actions4 && /* @__PURE__ */ jsx30("div", { className: "flex justify-end gap-2 px-4 py-3", children: actions4 }),
49524
- !actions4 && /* @__PURE__ */ jsx30("div", { className: "h-4" })
49813
+ children && /* @__PURE__ */ jsx32("div", { className: "px-4 pt-3", children }),
49814
+ actions4 && /* @__PURE__ */ jsx32("div", { className: "flex justify-end gap-2 px-4 py-3", children: actions4 }),
49815
+ !actions4 && /* @__PURE__ */ jsx32("div", { className: "h-4" })
49525
49816
  ]
49526
49817
  }
49527
49818
  )
@@ -49544,7 +49835,7 @@ function InlineView({
49544
49835
  className,
49545
49836
  children
49546
49837
  }) {
49547
- const scope = useContext5(ScopeContext);
49838
+ const scope = useContext6(ScopeContext);
49548
49839
  const id = useId2();
49549
49840
  const { openView, closeView } = scope ?? {};
49550
49841
  useLayoutEffect5(() => {
@@ -49558,7 +49849,7 @@ function InlineView({
49558
49849
  }, [onBeforeClose, onOpenChange]);
49559
49850
  if (!open) return null;
49560
49851
  const padded = scope?.kind === "tab" || scope?.kind === "pane";
49561
- const view = /* @__PURE__ */ jsx30(
49852
+ const view = /* @__PURE__ */ jsx32(
49562
49853
  "div",
49563
49854
  {
49564
49855
  className: cn(
@@ -49571,10 +49862,10 @@ function InlineView({
49571
49862
  void requestClose();
49572
49863
  }
49573
49864
  },
49574
- children: /* @__PURE__ */ jsx30(
49865
+ children: /* @__PURE__ */ jsx32(
49575
49866
  PanelShell,
49576
49867
  {
49577
- leading: /* @__PURE__ */ jsx30(BackButton, { onClick: () => void requestClose() }),
49868
+ leading: /* @__PURE__ */ jsx32(BackButton, { onClick: () => void requestClose() }),
49578
49869
  icon,
49579
49870
  title,
49580
49871
  status,
@@ -49582,7 +49873,7 @@ function InlineView({
49582
49873
  toolbar,
49583
49874
  footer,
49584
49875
  scroll,
49585
- children: bare ? children : /* @__PURE__ */ jsx30(
49876
+ children: bare ? children : /* @__PURE__ */ jsx32(
49586
49877
  "div",
49587
49878
  {
49588
49879
  className: cn(
@@ -49601,7 +49892,7 @@ function InlineView({
49601
49892
  if (scope?.overlay) return createPortal3(view, scope.overlay);
49602
49893
  if (scope) return null;
49603
49894
  return createPortal3(
49604
- /* @__PURE__ */ jsx30("div", { className: "fixed inset-0 z-50 bg-background", children: view }),
49895
+ /* @__PURE__ */ jsx32("div", { className: "fixed inset-0 z-50 bg-background", children: view }),
49605
49896
  document.body
49606
49897
  );
49607
49898
  }
@@ -50183,12 +50474,8 @@ var en_default = {
50183
50474
  offline: "Offline",
50184
50475
  unknown: "Unknown",
50185
50476
  continue: "Continue",
50186
- maintenance: "Maintenance",
50187
- degraded: "Degraded",
50188
50477
  error: "Error",
50189
- warning: "Warning",
50190
50478
  unsavedChanges: "Unsaved changes",
50191
- dismiss: "Dismiss",
50192
50479
  more: "More",
50193
50480
  loading: "Loading...",
50194
50481
  host: "Host",
@@ -50198,15 +50485,7 @@ var en_default = {
50198
50485
  connect: "Connect",
50199
50486
  copied: "Copied",
50200
50487
  connecting: "Connecting...",
50201
- updateAvailable: "Update Available",
50202
- appName: "Termix",
50203
50488
  openInNewTab: "Open in New Tab",
50204
- noReleases: "No Releases",
50205
- updatesAndReleases: "Updates & Releases",
50206
- newVersionAvailable: "A new version ({{version}}) is available.",
50207
- failedToFetchUpdateInfo: "Failed to fetch update information",
50208
- preRelease: "Pre-release",
50209
- noReleasesFound: "No releases found.",
50210
50489
  cancel: "Cancel",
50211
50490
  username: "Username",
50212
50491
  login: "Login",
@@ -50221,15 +50500,10 @@ var en_default = {
50221
50500
  rename: "Rename",
50222
50501
  edit: "Edit",
50223
50502
  confirm: "Confirm",
50224
- no: "No",
50225
- or: "OR",
50226
50503
  next: "Next",
50227
50504
  previous: "Previous",
50228
50505
  refresh: "Refresh",
50229
50506
  language: "Language",
50230
- checking: "Checking...",
50231
- checkingDatabase: "Checking database connection...",
50232
- checkingAuthentication: "Checking authentication...",
50233
50507
  backendReconnected: "Server connection restored",
50234
50508
  connectionDegraded: "Server connection lost, recovering\u2026",
50235
50509
  reload: "Reload",
@@ -50238,12 +50512,6 @@ var en_default = {
50238
50512
  copyFailed: "Failed to copy to clipboard",
50239
50513
  maximize: "Maximize",
50240
50514
  restore: "Restore",
50241
- of: "of",
50242
- saved: "Saved",
50243
- deleted: "Deleted",
50244
- deleteFailed: "Failed to delete",
50245
- saveFailed: "Failed to save",
50246
- required: "Required",
50247
50515
  remove: "Remove",
50248
50516
  add: "Add",
50249
50517
  update: "Update",
@@ -50252,7 +50520,8 @@ var en_default = {
50252
50520
  mobileSidebarDescription: "Displays the mobile sidebar.",
50253
50521
  toggleSidebar: "Toggle Sidebar",
50254
50522
  search: "Search...",
50255
- new: "New"
50523
+ new: "New",
50524
+ docs: "Docs"
50256
50525
  },
50257
50526
  onboarding: {
50258
50527
  stepCounter: "Step {{current}} of {{total}}",
@@ -50264,8 +50533,8 @@ var en_default = {
50264
50533
  skip: "Skip setup",
50265
50534
  next: "Next",
50266
50535
  finish: "Done",
50267
- pluginsTitle: "Choose your features",
50268
- pluginsIntro: "Pick the features to keep. The recommended ones cover what most people need.",
50536
+ pluginsTitle: "Choose your plugins",
50537
+ pluginsIntro: "Pick the plugins to keep. The recommended ones cover what most people need.",
50269
50538
  pluginsUpgradeNote: "Everything you use now is kept as it is. Turn off or remove what you do not need.",
50270
50539
  pluginsRemoveNote: "Plugins set to Remove are deleted from this server, with their data, when you continue. You can install any of them again at any time from the Plugins tab (this needs an internet connection). Pick Off to keep a plugin installed but turned off.",
50271
50540
  pluginsUseRecommended: "Recommended",
@@ -50296,24 +50565,17 @@ var en_default = {
50296
50565
  desktopLocalBody: "Everything is stored here. Nothing to set up.",
50297
50566
  desktopLinkTitle: "With my Termix server",
50298
50567
  desktopLinkBody: "Sign in to your server to use the same hosts and settings on every device.",
50299
- desktopLinked: "Linked to {{server}}"
50568
+ desktopLinked: "Linked to {{server}}",
50569
+ pluginsDocsLink: "Read about each one"
50300
50570
  },
50301
50571
  nav: {
50302
50572
  home: "Home",
50303
50573
  terminal: "Terminal",
50304
- fileManager: "File Manager",
50305
- serverStats: "Host Metrics",
50306
- admin: "Admin",
50307
- userProfile: "User Profile",
50308
- confirmClose: "Close this active session?",
50309
50574
  close: "Close",
50310
- cancel: "Cancel",
50311
- sshManager: "SSH Manager",
50312
50575
  hostTabTitle: "{{username}}@{{ip}}:{{port}}",
50313
50576
  copyPassword: "Copy Password",
50314
50577
  copySudoPassword: "Copy Sudo Password",
50315
50578
  passwordCopied: "Password copied to clipboard",
50316
- noPasswordAvailable: "No password available",
50317
50579
  failedToCopyPassword: "Failed to copy password",
50318
50580
  refreshTab: "Refresh connection",
50319
50581
  renameTab: "Rename tab",
@@ -50324,13 +50586,7 @@ var en_default = {
50324
50586
  openInRightDock: "Open in right dock",
50325
50587
  closeRightDock: "Close right dock",
50326
50588
  toggleRightDock: "Toggle right dock",
50327
- multiPanelHintTitle: "Work with more than one panel",
50328
- multiPanelHintBody: "Keep this panel open while the sidebar is used for something else. Two ways to move it:",
50329
- multiPanelHintTabHow: "Ctrl+click its rail icon, or use the button in this header.",
50330
- multiPanelHintDockHow: "Right-click its rail icon, or use the panel icon in the tab bar.",
50331
- sidebarSettings: "Sidebar Settings...",
50332
50589
  hosts: "Hosts",
50333
- hostManager: "Host Manager",
50334
50590
  credentials: "Credentials",
50335
50591
  connections: "Connections",
50336
50592
  keepSideMenuOpen: "Keep Menu Open",
@@ -50702,8 +50958,8 @@ var en_default = {
50702
50958
  showPinIconDesc: "Show a pin next to pinned hosts.",
50703
50959
  showSharedBadge: "Shared badge",
50704
50960
  showSharedBadgeDesc: "Show a badge on hosts shared with you.",
50705
- showBadges: "Feature badges",
50706
- showBadgesDesc: "Show badges that features add next to the host name.",
50961
+ showBadges: "Plugin badges",
50962
+ showBadgesDesc: "Show badges that plugins add next to the host name.",
50707
50963
  hostClickBehavior: "Clicking a host",
50708
50964
  hostClickBehaviorDesc: 'What happens when you click a host that already has an open tab. Ctrl/Cmd click, middle click, or "Open in new tab" always start a new one.',
50709
50965
  hostClickNewTab: "Always open a new tab",
@@ -50776,7 +51032,7 @@ var en_default = {
50776
51032
  groupTags: "Tags & pin",
50777
51033
  groupProxy: "Proxy",
50778
51034
  groupJumpHosts: "Jump hosts",
50779
- groupFeatureFlags: "Feature flags",
51035
+ groupFeatureFlags: "Plugin settings",
50780
51036
  groupAdvanced: "Advanced config",
50781
51037
  preview: "Preview",
50782
51038
  moreHosts: "... {{count}} more hosts",
@@ -50793,7 +51049,7 @@ var en_default = {
50793
51049
  failedToDeleteCredential2: "Failed to delete credential",
50794
51050
  noFolderOption: "(No folder)",
50795
51051
  nSelected: "{{count}} selected",
50796
- featuresMenu: "Features",
51052
+ featuresMenu: "Plugins",
50797
51053
  moveMenu: "Move",
50798
51054
  connectSelected: "Connect",
50799
51055
  cancelSelection: "Cancel",
@@ -50809,6 +51065,7 @@ var en_default = {
50809
51065
  noHostsYet: "No hosts yet",
50810
51066
  noHostsYetHint: "Add a server to open terminals, browse files and see metrics.",
50811
51067
  noHostsMatchSearch: "No hosts match your search",
51068
+ noHostSelected: "Pick a host to open this.",
50812
51069
  searchHosts: "Search hosts...",
50813
51070
  sortHosts: "Sort Hosts",
50814
51071
  sortDefault: "Default Order",
@@ -50934,12 +51191,12 @@ var en_default = {
50934
51191
  authFieldsAfterSave: "Save the host first, then fill in these fields.",
50935
51192
  enablePluginFeature: "Enable {{name}}",
50936
51193
  disablePluginFeature: "Disable {{name}}",
50937
- filterFeaturesGroup: "Features",
51194
+ filterFeaturesGroup: "Plugins",
50938
51195
  localOnly: "Keep on this device only",
50939
51196
  localOnlyDesc: "This host is not sent to the server and does not appear on your other devices.",
50940
51197
  folderLocalOnly: "Keep on this device only",
50941
51198
  folderLocalOnlyDesc: "This folder and every host in it stay on this device and are not sent to the server.",
50942
- sudoPasswordDesc: "Used when the terminal fills a sudo prompt, and by features that run commands with sudo.",
51199
+ sudoPasswordDesc: "Used when the terminal fills a sudo prompt, and by plugins that run commands with sudo.",
50943
51200
  editorCancel: "Cancel",
50944
51201
  editorSaving: "Saving...",
50945
51202
  editorUpdate: "Update Host",
@@ -50956,25 +51213,18 @@ var en_default = {
50956
51213
  quickActions: "Quick Actions",
50957
51214
  navigation: "Navigation",
50958
51215
  orShortcut: "or",
50959
- hostManager: "Host Manager",
50960
- hostManagerDesc: "Manage, add, or edit hosts",
50961
51216
  addNewHost: "Add New Host",
50962
- addNewHostDesc: "Register a new host",
50963
51217
  adminSettings: "Admin Settings",
50964
- adminSettingsDesc: "Configure system preferences and users",
50965
- userProfile: "User Profile",
50966
- userProfileDesc: "Manage your account and preferences",
50967
51218
  addCredential: "Add Credential",
50968
- addCredentialDesc: "Store SSH keys or passwords",
50969
51219
  recentActivity: "Recent Activity",
50970
51220
  serversAndHosts: "Servers & Hosts",
50971
51221
  noHostsFound: 'No hosts found matching "{{search}}"',
50972
51222
  links: "Links",
50973
51223
  navigate: "Navigate",
50974
51224
  select: "Select",
50975
- toggleWith: "Toggle with",
50976
51225
  pluginActions: "More actions",
50977
- noTargetTab: "No terminal tab is open"
51226
+ noTargetTab: "No terminal tab is open",
51227
+ documentation: "Documentation"
50978
51228
  },
50979
51229
  splitScreen: {
50980
51230
  title: "Split screen",
@@ -51006,7 +51256,6 @@ var en_default = {
51006
51256
  unsplit: "Unsplit",
51007
51257
  layouts: "Layouts",
51008
51258
  applyLayout: "Change layout",
51009
- newSplit: "New split",
51010
51259
  maxPanes: "A split can hold up to {{count}} panes",
51011
51260
  dropSplit: "Drop to split",
51012
51261
  dropHere: "Drop to show here",
@@ -51033,16 +51282,10 @@ var en_default = {
51033
51282
  connectedFor: "Connected for {{duration}}",
51034
51283
  connected: "Connected",
51035
51284
  disconnected: "Disconnected",
51036
- closeTab: "Close tab",
51037
- closeConnection: "Close connection",
51038
- forgetTab: "Forget",
51039
- removeBackground: "Remove",
51040
51285
  reconnect: "Reconnect",
51041
- reopenTab: "Reopen",
51042
51286
  sectionOpen: "Open",
51043
51287
  sectionBackground: "Background",
51044
51288
  backgroundDesc: "Sessions persist for {{minutes}} minutes after disconnect and can be reconnected.",
51045
- persisted: "Persisted in background",
51046
51289
  expiresIn: "Expires in {{duration}}",
51047
51290
  search: "Search connections...",
51048
51291
  noSearchResults: "No connections match your search",
@@ -51082,7 +51325,7 @@ var en_default = {
51082
51325
  empty: "No cards. Use Add below to place cards."
51083
51326
  },
51084
51327
  auth: {
51085
- tagline: "Self-hosted, plugin-based server management",
51328
+ tagline: "Self-hosted, plugin-based server management.",
51086
51329
  loginTitle: "Welcome back",
51087
51330
  registerTitle: "Create Account",
51088
51331
  forgotPassword: "Forgot Password?",
@@ -51104,36 +51347,22 @@ var en_default = {
51104
51347
  sshKeyPasswordDescription: "If your SSH key is encrypted, enter the passphrase here.",
51105
51348
  passphraseRequired: "Passphrase Required",
51106
51349
  passphraseRequiredDescription: "The SSH key is encrypted. Please enter the passphrase to unlock it.",
51107
- back: "Back",
51108
- firstUser: "First User",
51109
- firstUserMessage: "You are the first user and will be made an admin. You can view admin settings in the sidebar user dropdown. If you think this is a mistake, check the docker logs, or create a GitHub issue.",
51110
51350
  external: "External",
51111
51351
  loginWithExternal: "Login with External Provider",
51112
51352
  loginWithExternalDesc: "Login using your configured external identity provider",
51113
- externalNotSupportedInElectron: "External sign-in is not supported in the desktop app yet. Please use the web version.",
51114
51353
  loginWithProvider: "Login with {{name}}",
51115
- orContinueWith: "or continue with",
51116
51354
  resetPasswordButton: "Reset Password",
51117
51355
  sendResetCode: "Send Reset Code",
51118
51356
  resetCodeDesc: "Enter your username to receive a password reset code. The code will be logged in the docker container logs.",
51119
51357
  resetCode: "Reset Code",
51120
51358
  verifyCodeButton: "Verify Code",
51121
- enterResetCode: "Enter the 6-digit code from the docker container logs for user:",
51122
51359
  newPassword: "New Password",
51123
51360
  confirmNewPassword: "Confirm Password",
51124
- enterNewPassword: "Enter your new password for user:",
51125
51361
  signUp: "Sign Up",
51126
- desktopApp: "Desktop App",
51127
- loggingInToDesktopApp: "Logging in to the desktop app",
51128
51362
  loadingServer: "Loading server...",
51129
- dataLossWarning: "Resetting your password this way will delete all your saved SSH hosts, credentials, and other encrypted data. This action cannot be undone. Only use this if you have forgotten your password and are not logged in.",
51130
- authenticationDisabled: "Authentication Disabled",
51131
- authenticationDisabledDesc: "All authentication methods are currently disabled. Please contact your administrator.",
51132
- passwordLoginDisabledDesc: "Password login is disabled. Use a passkey or an external authentication provider.",
51133
- attemptsRemaining: "{{count}} attempts remaining",
51134
- confirmResetDataWipe: "This account has not logged in since the encryption upgrade, so its stored data cannot be recovered without the old password. Resetting will permanently delete its hosts, credentials and snippets. Continue?",
51135
51363
  password: "Password",
51136
51364
  secondFactorUnavailable: "Your account uses a second factor that is not available right now. Contact an admin to reset it.",
51365
+ externalLoginRequired: "This account signs in through single sign-on. Use that button to sign in.",
51137
51366
  secondFactorNoUI: "This second factor cannot be shown on this screen. Contact an admin.",
51138
51367
  serverAuthentication: "Server sign-in",
51139
51368
  minChars: "min. {{min}} characters",
@@ -51163,6 +51392,8 @@ var en_default = {
51163
51392
  databaseConnection: "Could not connect to the database",
51164
51393
  embeddedServerFailed: "Termix's local server could not start",
51165
51394
  unknownError: "Unknown error",
51395
+ syncedHostMissing: "The synced host does not exist on the linked server",
51396
+ linkedServerUnreachable: "The linked server is not reachable",
51166
51397
  loginFailed: "Login failed",
51167
51398
  failedPasswordReset: "Failed to initiate password reset",
51168
51399
  failedVerifyCode: "Failed to verify reset code",
@@ -51176,12 +51407,7 @@ var en_default = {
51176
51407
  requiredField: "This field is required",
51177
51408
  minLength: "Minimum length is {{min}}",
51178
51409
  passwordMismatch: "Passwords do not match",
51179
- passwordLoginDisabled: "Username/password login is currently disabled",
51180
51410
  sessionExpired: "Session expired - please log in again",
51181
- totpRateLimited: "Rate limited: Too many TOTP verification attempts. Please try again later.",
51182
- totpRateLimitedWithTime: "Rate limited: Too many TOTP verification attempts. Please wait {{time}} seconds before trying again.",
51183
- resetCodeRateLimited: "Rate limited: Too many verification attempts. Please try again later.",
51184
- resetCodeRateLimitedWithTime: "Rate limited: Too many verification attempts. Please wait {{time}} seconds before trying again.",
51185
51411
  authTokenSaveFailed: "Failed to save authentication token",
51186
51412
  authTokenMissing: "The server did not return a new authentication token. Please sign in again.",
51187
51413
  failedToLoadServer: "Failed to load server",
@@ -51201,7 +51427,6 @@ var en_default = {
51201
51427
  registrationSuccess: "Registration successful"
51202
51428
  },
51203
51429
  placeholders: {
51204
- language: "Language",
51205
51430
  keyPassword: "key password",
51206
51431
  pastePrivateKey: "Paste your private key here...",
51207
51432
  enterPassword: "Enter your password",
@@ -51214,7 +51439,6 @@ var en_default = {
51214
51439
  },
51215
51440
  dashboard: {
51216
51441
  title: "Dashboard",
51217
- loading: "Loading dashboard...",
51218
51442
  github: "GitHub",
51219
51443
  reportBug: "Report a bug",
51220
51444
  discord: "Discord",
@@ -51222,7 +51446,6 @@ var en_default = {
51222
51446
  donate: "Donate",
51223
51447
  serverOverview: "Server Overview",
51224
51448
  version: "Version",
51225
- upToDate: "Up to Date",
51226
51449
  updateAvailable: "Update Available",
51227
51450
  beta: "Beta",
51228
51451
  uptime: "Uptime",
@@ -51233,7 +51456,6 @@ var en_default = {
51233
51456
  totalCredentials: "Total Credentials",
51234
51457
  recentActivity: "Recent Activity",
51235
51458
  reset: "Reset",
51236
- loadingRecentActivity: "Loading recent activity...",
51237
51459
  noRecentActivity: "No recent activity",
51238
51460
  quickActions: "Quick Actions",
51239
51461
  addHost: "Add Host",
@@ -51241,48 +51463,21 @@ var en_default = {
51241
51463
  adminSettings: "Admin Settings",
51242
51464
  userProfile: "User Profile",
51243
51465
  serverStats: "Server Stats",
51244
- loadingServerStats: "Loading server stats...",
51245
- noServerData: "No server data available",
51246
51466
  customizeLayout: "Customize Dashboard",
51247
- dashboardSettings: "Dashboard Settings",
51248
- enableDisableCards: "Enable/Disable Cards",
51249
- resetLayout: "Reset to Default",
51250
- serverOverviewCard: "Server Overview",
51251
- recentActivityCard: "Recent Activity",
51252
- quickActionsCard: "Quick Actions",
51253
- serverStatsCard: "Server Stats",
51254
- panelMain: "Main",
51255
- panelSide: "Side",
51256
- justNow: "just now",
51257
- activityFileManager: "File manager"
51258
- },
51259
- donation: {
51260
- title: "Enjoying Termix?",
51261
- body: "Termix is free and open source, built and maintained by a two-person team in our spare time. If it's replaced a commercial tool you'd otherwise be paying for, a donation helps cover hosting costs and keeps development going. As of now, donations are crypto only.",
51262
- milestones: "Donations also help fund the time to research and learn what's needed to build features like SAML and Kubernetes support. See the progress on the donate page.",
51263
- cta: "Donate",
51264
- dismiss: "Maybe later"
51467
+ justNow: "just now"
51265
51468
  },
51266
51469
  dashboardTab: {
51267
51470
  stable: "STABLE",
51268
- hostsOnline: "Hosts Online",
51269
51471
  registerNewServer: "Register a new server",
51270
51472
  storeSshKeysOrPasswords: "Store SSH keys or passwords",
51271
51473
  manageUsersAndRoles: "Manage users and roles",
51272
51474
  manageYourAccount: "Manage your account",
51273
51475
  hostStatus: "Host Status",
51274
51476
  noHostsConfigured: "No hosts configured",
51275
- online: "ONLINE",
51276
- offline: "OFFLINE",
51277
51477
  checking: "CHECKING",
51278
- onlineLower: "Online",
51279
- add: "Add:",
51280
51478
  commandPalette: "Command Palette",
51281
51479
  done: "Done",
51282
- editModeInstructions: "Drag cards to reorder. Drag the column divider to resize columns. Drag the bottom edge of a card to change its height. Use the trash button to remove a card.",
51283
- empty: "Empty",
51284
51480
  clear: "Clear",
51285
- viewServerDetails: "View server details",
51286
51481
  dragToResizeColumns: "Drag to resize columns",
51287
51482
  hostsAvailable: "Hosts Available",
51288
51483
  removeSection: "Remove section",
@@ -51434,7 +51629,6 @@ var en_default = {
51434
51629
  noCustomRoles: "No custom roles defined",
51435
51630
  removeRoleFailed: "Failed to remove role",
51436
51631
  assignRoleFailed: "Failed to assign role",
51437
- deleteRoleFailed: "Failed to delete role",
51438
51632
  userAdminAccess: "Administrator",
51439
51633
  userAdminAccessDesc: "Full access to all admin settings",
51440
51634
  userRoles: "Roles",
@@ -51457,7 +51651,6 @@ var en_default = {
51457
51651
  createUserPassword: "Password",
51458
51652
  createUserPasswordHint: "Minimum 6 characters.",
51459
51653
  createUserEnterUsername: "Enter username",
51460
- createUserEnterPassword: "Enter password",
51461
51654
  createUserSubmit: "Create User",
51462
51655
  editUserTitle: "Manage User: {{username}}",
51463
51656
  editUserDesc: "Edit roles, admin status, sessions, and account settings.",
@@ -51488,7 +51681,6 @@ var en_default = {
51488
51681
  unlinkAccountInProgress: "Unlinking...",
51489
51682
  unlinkAccountSuccess: "External sign-in unlinked",
51490
51683
  unlinkAccountFailed: "Failed to unlink external sign-in",
51491
- saving: "Saving...",
51492
51684
  updateRegistrationFailed: "Failed to update registration setting",
51493
51685
  updatePasswordLoginFailed: "Failed to update password login setting",
51494
51686
  updateSecondFactorAfterExternalLoginFailed: "Failed to update second factor setting",
@@ -51570,7 +51762,6 @@ var en_default = {
51570
51762
  importFailed: "Import failed: {{error}}",
51571
51763
  importError: "Database import failed",
51572
51764
  sectionHostDefaults: "Host Defaults",
51573
- minutes: "minutes",
51574
51765
  rolePermissions: {
51575
51766
  count: "{{count}} permissions",
51576
51767
  editAction: "Edit permissions",
@@ -51579,7 +51770,7 @@ var en_default = {
51579
51770
  saveError: "Failed to save role permissions",
51580
51771
  save: "Save",
51581
51772
  saving: "Saving...",
51582
- pluginDisabled: "Feature disabled",
51773
+ pluginDisabled: "Plugin disabled",
51583
51774
  groups: {
51584
51775
  hosts: "Hosts",
51585
51776
  credentials: "Credentials",
@@ -51622,7 +51813,6 @@ var en_default = {
51622
51813
  hostLabel: "Host",
51623
51814
  hostPlaceholder: "192.168.1.1 or example.com",
51624
51815
  portLabel: "Port",
51625
- portPlaceholder: "22",
51626
51816
  usernameLabel: "Username",
51627
51817
  usernamePlaceholder: "username",
51628
51818
  authLabel: "Auth",
@@ -51640,7 +51830,6 @@ var en_default = {
51640
51830
  noTarget: "Nothing installed can connect to this host type"
51641
51831
  },
51642
51832
  keybindings: {
51643
- title: "Keyboard Shortcuts",
51644
51833
  description: "Bind keys to actions in the terminal and the app, or change the built-in shortcuts.",
51645
51834
  defaultsHeading: "Built-in Shortcuts",
51646
51835
  fixedHeading: "App Shortcuts",
@@ -51666,7 +51855,6 @@ var en_default = {
51666
51855
  customize: "Customize",
51667
51856
  resetToDefault: "Reset to default",
51668
51857
  resetAllToDefaults: "Reset all to defaults",
51669
- close: "Close",
51670
51858
  cancel: "Cancel",
51671
51859
  saveBinding: "Save Shortcut",
51672
51860
  saveError: "Failed to save keyboard shortcuts",
@@ -51737,7 +51925,6 @@ var en_default = {
51737
51925
  cliLearnMore: "Learn More",
51738
51926
  deleteAccount: "Delete Account",
51739
51927
  deleteAccountDescription: "Permanently delete your account",
51740
- changeServerDescription: "Switch to a different Termix backend server",
51741
51928
  deleteButton: "Delete",
51742
51929
  deleteAccountPermanent: "This action is permanent and cannot be undone.",
51743
51930
  deleteAccountWarning: "All sessions, hosts, credentials, and settings will be permanently deleted.",
@@ -51751,8 +51938,6 @@ var en_default = {
51751
51938
  keyboardShortcuts: "Keyboard Shortcuts",
51752
51939
  keyboardShortcutsDescription: "Customize terminal copy, paste, and command shortcuts",
51753
51940
  manageShortcuts: "Manage",
51754
- historyTracking: "History Tracking",
51755
- historyTrackingDesc: "Track terminal commands",
51756
51941
  commandPalette: "Command Palette",
51757
51942
  commandPaletteDesc: "Enable keyboard shortcut",
51758
51943
  reopenTabsOnLogin: "Reopen Tabs on Login",
@@ -51765,28 +51950,16 @@ var en_default = {
51765
51950
  sidebarSettingsMoved: "Host sidebar display settings have moved to the Customize sidebar panel, opened from the Hosts toolbar.",
51766
51951
  showHostTags: "Show Host Tags",
51767
51952
  showHostTagsDesc: "Display tags in host list",
51768
- hostTrayOnClick: "Click to Expand Host Actions",
51769
- hostTrayOnClickDesc: "Always show connection buttons; click to expand management options instead of hover",
51770
- compactHostView: "Compact Host View",
51771
- compactHostViewDesc: "Collapse each host to a single line showing only its name and address",
51772
51953
  statusColors: "Real Status Colors",
51773
51954
  statusColorsDesc: "Use green/red for online/offline status instead of the accent color",
51774
51955
  pinAppRail: "Pin App Rail",
51775
51956
  pinAppRailDesc: "Keep the left sidebar app rail always expanded instead of expanding on hover",
51776
- openFullscreenSettings: "Open settings full screen",
51777
- exitFullscreenSettings: "Exit full-screen settings",
51778
51957
  expandAppRailOnHover: "Expand App Rail on Hover",
51779
51958
  expandAppRailOnHoverDesc: "Allow the left sidebar app rail to expand when the pointer moves over it",
51780
51959
  showPinAppRailButton: "Show Pin App Rail Button",
51781
51960
  showPinAppRailButtonDesc: "Show a button in the app rail to pin it open. Off by default.",
51782
51961
  settingsNavigation: "Navigation",
51783
51962
  navigationTabsDesc: "Choose which tabs appear in the app rail",
51784
- showSnippetCommands: "Show Commands",
51785
- showSnippetCommandsDesc: "Show each snippet's command under its name. Turn off for a compact list.",
51786
- foldersCollapsed: "Folders Collapsed",
51787
- foldersCollapsedDesc: "Collapse folders by default",
51788
- confirmExecution: "Confirm Execution",
51789
- confirmExecutionDesc: "Confirm before running snippets",
51790
51963
  settingsUpdates: "Updates",
51791
51964
  disableUpdateChecks: "Disable Update Checks",
51792
51965
  disableUpdateChecksDesc: "Stop checking for updates",
@@ -51876,7 +52049,6 @@ var en_default = {
51876
52049
  }
51877
52050
  }
51878
52051
  },
51879
- terminalToolbar: {},
51880
52052
  plugins: {
51881
52053
  capabilities: {
51882
52054
  "credentials:read": {
@@ -51988,8 +52160,8 @@ var en_default = {
51988
52160
  consequence: "Server settings, not your hosts or credentials."
51989
52161
  },
51990
52162
  "plugins:read": {
51991
- title: "See installed features",
51992
- consequence: "It can see which features are installed and running."
52163
+ title: "See installed plugins",
52164
+ consequence: "It can see which plugins are installed and running."
51993
52165
  },
51994
52166
  "ui:surface": {
51995
52167
  title: "Add its own screens",
@@ -51998,9 +52170,9 @@ var en_default = {
51998
52170
  },
51999
52171
  runtime: {
52000
52172
  needsPlugin: "{{name}} is not available",
52001
- needsUnknownPlugin: "This feature is not available",
52173
+ needsUnknownPlugin: "This plugin is not available",
52002
52174
  disabledHint: "It is turned off on this server.",
52003
- missingHint: "The feature it came from has been removed.",
52175
+ missingHint: "The plugin it came from has been removed.",
52004
52176
  failed: "{{name}} could not start",
52005
52177
  failedHint: "An admin can check the server logs for details.",
52006
52178
  loading: "Loading {{name}}",
@@ -52180,6 +52352,8 @@ var en_default = {
52180
52352
  noneInstalled: "No plugins installed yet. Browse to add some.",
52181
52353
  nothingHere: "Nothing here.",
52182
52354
  nothingHereHint: "Try another category or search.",
52355
+ communitySoon: "Community plugins are coming in a later update. Plugin authors can already submit theirs to the community registry.",
52356
+ communitySubmit: "How to submit",
52183
52357
  upToDate: "Everything is up to date.",
52184
52358
  lastChecked: "Last checked {{time}}",
52185
52359
  updatesReady_one: "{{count}} update ready.",
@@ -52229,7 +52403,11 @@ var en_default = {
52229
52403
  waitingForStable: "Back on stable. Stays on {{version}} until a stable release is newer.",
52230
52404
  versionLabel: "{{version}} (beta)",
52231
52405
  sendFeedback: "Send beta feedback"
52232
- }
52406
+ },
52407
+ documentation: "Documentation",
52408
+ envVars: "Environment variables",
52409
+ envRequired: "Required",
52410
+ envDefault: "Default: {{value}}"
52233
52411
  }
52234
52412
  },
52235
52413
  settings: {
@@ -52368,7 +52546,7 @@ var en_default = {
52368
52546
  sshCredentials: "Credentials",
52369
52547
  sshFolders: "Folders",
52370
52548
  userPreferences: "Preferences",
52371
- pluginUserSettings: "Feature settings",
52549
+ pluginUserSettings: "Plugin settings",
52372
52550
  sharedHosts: "Hosts shared with you",
52373
52551
  sharedCredentials: "Credentials shared with you",
52374
52552
  vaultProfiles: "Vault profiles",
@@ -52467,7 +52645,7 @@ var en_default = {
52467
52645
  confirmNoChanges: "No existing host changes. New hosts will start with these defaults.",
52468
52646
  menuMyDefaults: "My host defaults",
52469
52647
  folderDefaultsAction: "Folder defaults",
52470
- adminSectionDescription: "Set the defaults every host on this server follows, for every setting a host has, including each feature's. Users and folders can set their own on top, and any host can still use its own value.",
52648
+ adminSectionDescription: "Set the defaults every host on this server follows, for every setting a host has, including each plugin's. Users and folders can set their own on top, and any host can still use its own value.",
52471
52649
  editServerDefaults: "Edit server defaults",
52472
52650
  resetMenu: "Reset",
52473
52651
  resetAll: "Everything to defaults",
@@ -52497,7 +52675,7 @@ var en_default = {
52497
52675
  needsAttention_one: "One thing needs attention.",
52498
52676
  needsAttention_other: "{{count}} things need attention.",
52499
52677
  bandHost: "Host",
52500
- bandFeatures: "Protocols and features",
52678
+ bandFeatures: "Protocols and plugins",
52501
52679
  bandCredential: "Credential",
52502
52680
  sshConnection: "SSH connection",
52503
52681
  errorAddressRequired: "An address is required.",
@@ -52514,6 +52692,7 @@ var en_default = {
52514
52692
  collapse: "Collapse",
52515
52693
  expand: "Expand",
52516
52694
  cannotBeUndone: "This cannot be undone.",
52695
+ hostNotFound: "Couldn't find that host. Try refreshing the list.",
52517
52696
  hostCount_one: "{{count}} host",
52518
52697
  hostCount_other: "{{count}} hosts",
52519
52698
  credentialCount_one: "{{count}} credential",
@@ -52676,23 +52855,23 @@ import { useMemo as useMemo14, useSyncExternalStore as useSyncExternalStore3 } f
52676
52855
 
52677
52856
  // src/ui/contexts/UiPreferencesContext.tsx
52678
52857
  import {
52679
- createContext as createContext7,
52858
+ createContext as createContext8,
52680
52859
  useCallback as useCallback19,
52681
- useContext as useContext6,
52860
+ useContext as useContext7,
52682
52861
  useEffect as useEffect28,
52683
52862
  useMemo as useMemo13,
52684
52863
  useRef as useRef30,
52685
52864
  useState as useState26
52686
52865
  } from "react";
52687
- import { jsx as jsx31 } from "react/jsx-runtime";
52688
- var UiPreferencesContext = createContext7(
52866
+ import { jsx as jsx33 } from "react/jsx-runtime";
52867
+ var UiPreferencesContext = createContext8(
52689
52868
  null
52690
52869
  );
52691
52870
  function useUiPreferencesContext() {
52692
- return useContext6(UiPreferencesContext);
52871
+ return useContext7(UiPreferencesContext);
52693
52872
  }
52694
52873
  function useAreaPreferences(area) {
52695
- const ctx = useContext6(UiPreferencesContext);
52874
+ const ctx = useContext7(UiPreferencesContext);
52696
52875
  if (!ctx) return PRESETS.balanced[area];
52697
52876
  return ctx.resolve(area);
52698
52877
  }
@@ -52713,8 +52892,8 @@ registeredRailItems.subscribe(() => {
52713
52892
 
52714
52893
  // src/ui/components/tab-strip.tsx
52715
52894
  import { useEffect as useEffect29, useRef as useRef31, useState as useState27 } from "react";
52716
- import { useTranslation as useTranslation3 } from "react-i18next";
52717
- import { jsx as jsx32, jsxs as jsxs9 } from "react/jsx-runtime";
52895
+ import { useTranslation as useTranslation5 } from "react-i18next";
52896
+ import { jsx as jsx34, jsxs as jsxs10 } from "react/jsx-runtime";
52718
52897
  function TabStrip({
52719
52898
  tabs,
52720
52899
  activeTab,
@@ -52725,7 +52904,7 @@ function TabStrip({
52725
52904
  className
52726
52905
  }) {
52727
52906
  const ref = useRef31(null);
52728
- const { t } = useTranslation3();
52907
+ const { t } = useTranslation5();
52729
52908
  const [scroll, setScroll] = useState27({ left: false, right: false });
52730
52909
  useEffect29(() => {
52731
52910
  const el = ref.current;
@@ -52757,7 +52936,7 @@ function TabStrip({
52757
52936
  }, []);
52758
52937
  const renderTab = (tab) => {
52759
52938
  const active = isActive ? isActive(tab.id) : activeTab === tab.id;
52760
- return /* @__PURE__ */ jsxs9(
52939
+ return /* @__PURE__ */ jsxs10(
52761
52940
  "button",
52762
52941
  {
52763
52942
  type: "button",
@@ -52766,14 +52945,14 @@ function TabStrip({
52766
52945
  children: [
52767
52946
  tab.icon,
52768
52947
  tab.label,
52769
- tab.count !== void 0 && /* @__PURE__ */ jsx32("span", { className: "text-[10px] tabular-nums opacity-60", children: tab.count })
52948
+ tab.count !== void 0 && /* @__PURE__ */ jsx34("span", { className: "text-[10px] tabular-nums opacity-60", children: tab.count })
52770
52949
  ]
52771
52950
  },
52772
52951
  tab.id
52773
52952
  );
52774
52953
  };
52775
52954
  const overflow = scroll.left || scroll.right;
52776
- return /* @__PURE__ */ jsxs9(
52955
+ return /* @__PURE__ */ jsxs10(
52777
52956
  "div",
52778
52957
  {
52779
52958
  className: cn(
@@ -52782,7 +52961,7 @@ function TabStrip({
52782
52961
  className
52783
52962
  ),
52784
52963
  children: [
52785
- overflow && /* @__PURE__ */ jsx32(
52964
+ overflow && /* @__PURE__ */ jsx34(
52786
52965
  "button",
52787
52966
  {
52788
52967
  type: "button",
@@ -52794,11 +52973,11 @@ function TabStrip({
52794
52973
  left: -ref.current.clientWidth * 0.75,
52795
52974
  behavior: "smooth"
52796
52975
  }),
52797
- children: /* @__PURE__ */ jsx32(ChevronLeft, { className: "size-4" })
52976
+ children: /* @__PURE__ */ jsx34(ChevronLeft, { className: "size-4" })
52798
52977
  }
52799
52978
  ),
52800
- /* @__PURE__ */ jsx32("div", { ref, className: "min-w-0 flex-1 overflow-x-auto scrollbar-none", children: /* @__PURE__ */ jsx32("div", { className: "flex min-w-max", children: tabs.map(renderTab) }) }),
52801
- overflow && /* @__PURE__ */ jsx32(
52979
+ /* @__PURE__ */ jsx34("div", { ref, className: "min-w-0 flex-1 overflow-x-auto scrollbar-none", children: /* @__PURE__ */ jsx34("div", { className: "flex min-w-max", children: tabs.map(renderTab) }) }),
52980
+ overflow && /* @__PURE__ */ jsx34(
52802
52981
  "button",
52803
52982
  {
52804
52983
  type: "button",
@@ -52810,17 +52989,17 @@ function TabStrip({
52810
52989
  left: ref.current.clientWidth * 0.75,
52811
52990
  behavior: "smooth"
52812
52991
  }),
52813
- children: /* @__PURE__ */ jsx32(ChevronRight, { className: "size-4" })
52992
+ children: /* @__PURE__ */ jsx34(ChevronRight, { className: "size-4" })
52814
52993
  }
52815
52994
  ),
52816
- trailing && /* @__PURE__ */ jsx32("div", { className: "flex shrink-0 items-center gap-1 px-1", children: trailing })
52995
+ trailing && /* @__PURE__ */ jsx34("div", { className: "flex shrink-0 items-center gap-1 px-1", children: trailing })
52817
52996
  ]
52818
52997
  }
52819
52998
  );
52820
52999
  }
52821
53000
 
52822
53001
  // src/ui/sidebar/HostManagerTabs.tsx
52823
- import { jsx as jsx33 } from "react/jsx-runtime";
53002
+ import { jsx as jsx35 } from "react/jsx-runtime";
52824
53003
  var sections = createRegistry(byOrderThenId);
52825
53004
  var registerHostEditorSection = sections.register;
52826
53005
  var unregisterHostEditorSection = sections.unregister;
@@ -53243,8 +53422,8 @@ function sectionPosition(section, order2 = 0) {
53243
53422
  }
53244
53423
 
53245
53424
  // src/ui/components/theme-provider.tsx
53246
- import { createContext as createContext8, useContext as useContext7, useEffect as useEffect30, useState as useState28 } from "react";
53247
- import { jsx as jsx34 } from "react/jsx-runtime";
53425
+ import { createContext as createContext9, useContext as useContext8, useEffect as useEffect30, useState as useState28 } from "react";
53426
+ import { jsx as jsx36 } from "react/jsx-runtime";
53248
53427
  var ALL_THEME_CLASSES = [
53249
53428
  "light",
53250
53429
  "dark",
@@ -53261,7 +53440,7 @@ var initialState = {
53261
53440
  setTheme: () => null,
53262
53441
  setThemePreview: () => null
53263
53442
  };
53264
- var ThemeProviderContext = createContext8(initialState);
53443
+ var ThemeProviderContext = createContext9(initialState);
53265
53444
  function ThemeProvider({
53266
53445
  children,
53267
53446
  defaultTheme = "dark",
@@ -53293,10 +53472,10 @@ function ThemeProvider({
53293
53472
  setPreviewTheme(preview);
53294
53473
  }
53295
53474
  };
53296
- return /* @__PURE__ */ jsx34(ThemeProviderContext.Provider, { ...props, value, children });
53475
+ return /* @__PURE__ */ jsx36(ThemeProviderContext.Provider, { ...props, value, children });
53297
53476
  }
53298
53477
  var useTheme = () => {
53299
- const context = useContext7(ThemeProviderContext);
53478
+ const context = useContext8(ThemeProviderContext);
53300
53479
  if (context === void 0)
53301
53480
  throw new Error("useTheme must be used within a ThemeProvider");
53302
53481
  return context;
@@ -53504,162 +53683,13 @@ async function getPlugins() {
53504
53683
  }
53505
53684
  var PLUGINS_CHANGED_EVENT = "termix:plugins-changed";
53506
53685
 
53507
- // src/ui/plugin-host/scope.tsx
53508
- import {
53509
- Component as Component2,
53510
- createContext as createContext9,
53511
- Suspense,
53512
- useContext as useContext8
53513
- } from "react";
53514
-
53515
- // src/ui/plugin-host/host-scope.ts
53516
- function isHostLike(value) {
53517
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
53518
- if (!("id" in value)) return false;
53519
- return "pluginSettings" in value || "ip" in value && "port" in value;
53520
- }
53521
- var cache = /* @__PURE__ */ new WeakMap();
53522
- function scopeHost(value, pluginId) {
53523
- if (!isHostLike(value)) return value;
53524
- const full = shellHost(value.id)?.pluginSettings ?? value.pluginSettings;
53525
- let perPlugin = cache.get(value);
53526
- const hit = perPlugin?.get(pluginId);
53527
- if (hit && hit.source === full) return hit.scoped;
53528
- const own = full?.[pluginId];
53529
- const scoped = { ...value, pluginSettings: own ? { [pluginId]: own } : {} };
53530
- if (!perPlugin) {
53531
- perPlugin = /* @__PURE__ */ new Map();
53532
- cache.set(value, perPlugin);
53533
- }
53534
- perPlugin.set(pluginId, { source: full, scoped });
53535
- return scoped;
53536
- }
53537
- function scopeHostArgs(args, pluginId) {
53538
- return args.map(
53539
- (arg) => isHostLike(arg) ? scopeHost(arg, pluginId) : scopeHostFields(arg, pluginId)
53540
- );
53541
- }
53542
- var HOST_FIELDS = ["host", "sshHost", "hostConfig"];
53543
- function scopeHostFields(value, pluginId) {
53544
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
53545
- const record = value;
53546
- let next = null;
53547
- for (const field of HOST_FIELDS) {
53548
- if (!(field in record)) continue;
53549
- const scoped = scopeHost(record[field], pluginId);
53550
- if (scoped === record[field]) continue;
53551
- next ??= { ...record };
53552
- next[field] = scoped;
53553
- }
53554
- return next ?? value;
53555
- }
53556
- function scopeHostCallback(fn, pluginId) {
53557
- if (!fn) return fn;
53558
- return ((...args) => fn(...scopeHostArgs(args, pluginId)));
53559
- }
53560
-
53561
- // src/ui/plugin-host/scope.tsx
53562
- import { useTranslation as useTranslation4 } from "react-i18next";
53563
- import { jsx as jsx35, jsxs as jsxs10 } from "react/jsx-runtime";
53564
- var PluginScopeContext = createContext9(null);
53565
- function usePluginScope() {
53566
- return useContext8(PluginScopeContext);
53567
- }
53568
- function CrashNotice({
53569
- pluginId,
53570
- onRetry
53571
- }) {
53572
- const { t } = useTranslation4();
53573
- const name = getPluginRecord(pluginId)?.summary.name ?? pluginId;
53574
- return /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center justify-center gap-2 p-4 text-center text-sm text-muted-foreground", children: [
53575
- /* @__PURE__ */ jsx35("span", { children: t("plugins.runtime.crashed", { name }) }),
53576
- /* @__PURE__ */ jsx35(
53577
- "button",
53578
- {
53579
- type: "button",
53580
- className: "text-xs underline underline-offset-2 hover:text-foreground",
53581
- onClick: onRetry,
53582
- children: t("plugins.runtime.retry")
53583
- }
53584
- )
53585
- ] });
53586
- }
53587
- var PluginErrorBoundary = class extends Component2 {
53588
- state = { error: false };
53589
- static getDerivedStateFromError() {
53590
- return { error: true };
53591
- }
53592
- componentDidCatch(error, info) {
53593
- console.error(
53594
- `[plugins] ${this.props.pluginId} crashed while rendering`,
53595
- error,
53596
- info.componentStack
53597
- );
53598
- }
53599
- render() {
53600
- if (this.state.error) {
53601
- return /* @__PURE__ */ jsx35(
53602
- CrashNotice,
53603
- {
53604
- pluginId: this.props.pluginId,
53605
- onRetry: () => this.setState({ error: false })
53606
- }
53607
- );
53608
- }
53609
- return this.props.children;
53610
- }
53611
- };
53612
- function PluginScope({
53613
- pluginId,
53614
- children
53615
- }) {
53616
- return /* @__PURE__ */ jsx35(PluginScopeContext.Provider, { value: pluginId, children: /* @__PURE__ */ jsx35(PluginErrorBoundary, { pluginId, children: /* @__PURE__ */ jsx35(Suspense, { fallback: null, children }) }) });
53617
- }
53618
- function withPluginScope(pluginId, Inner) {
53619
- function Scoped(props) {
53620
- return /* @__PURE__ */ jsx35(PluginScope, { pluginId, children: /* @__PURE__ */ jsx35(Inner, { ...scopeHostFields(props, pluginId) }) });
53621
- }
53622
- Scoped.displayName = `Plugin(${pluginId}:${Inner.displayName ?? Inner.name ?? "Component"})`;
53623
- return Scoped;
53624
- }
53625
- var IconBoundary = class extends Component2 {
53626
- state = { error: false };
53627
- static getDerivedStateFromError() {
53628
- return { error: true };
53629
- }
53630
- componentDidCatch(error) {
53631
- console.error(`[plugins] ${this.props.pluginId} icon failed`, error);
53632
- }
53633
- render() {
53634
- return this.state.error ? null : this.props.children;
53635
- }
53636
- };
53637
- function withIconBoundary(pluginId, Icon2) {
53638
- if (!Icon2) return void 0;
53639
- function SafeIcon(props) {
53640
- return /* @__PURE__ */ jsx35(IconBoundary, { pluginId, children: /* @__PURE__ */ jsx35(Icon2, { ...props }) });
53641
- }
53642
- SafeIcon.displayName = `PluginIcon(${pluginId})`;
53643
- return SafeIcon;
53644
- }
53645
- function guardCallback(pluginId, fn, fallback) {
53646
- if (!fn) return void 0;
53647
- return (...args) => {
53648
- try {
53649
- return fn(...args);
53650
- } catch (error) {
53651
- console.error(`[plugins] ${pluginId} callback failed`, error);
53652
- return fallback;
53653
- }
53654
- };
53655
- }
53656
-
53657
53686
  // src/ui/hooks/useSshAuthProviders.ts
53658
53687
  import { useEffect as useEffect32, useMemo as useMemo15, useState as useState30 } from "react";
53659
53688
 
53660
53689
  // src/ui/api/auth-methods-api.ts
53661
53690
  function remember(data) {
53662
53691
  if (data?.token) localStorage.setItem("jwt", data.token);
53692
+ if (data?.trustToken) localStorage.setItem(TRUST_TOKEN_KEY, data.trustToken);
53663
53693
  if (data?.success && !data.requires_totp) markUserAuthenticated();
53664
53694
  return data;
53665
53695
  }
@@ -53813,7 +53843,7 @@ function contextKey(context) {
53813
53843
  try {
53814
53844
  return JSON.stringify(context);
53815
53845
  } catch {
53816
- return String(Object.keys(context));
53846
+ return Object.entries(context).map(([key, value]) => `${key}:${value == null ? "-" : typeof value}`).join(",");
53817
53847
  }
53818
53848
  }
53819
53849
 
@@ -53828,7 +53858,7 @@ import {
53828
53858
  useSyncExternalStore as useSyncExternalStore6,
53829
53859
  useState as useState31
53830
53860
  } from "react";
53831
- import { jsx as jsx36 } from "react/jsx-runtime";
53861
+ import { jsx as jsx37 } from "react/jsx-runtime";
53832
53862
  var StatusStoreContext = createContext10(null);
53833
53863
  var ServerStatusContext = createContext10(null);
53834
53864
  var activeStore = null;
@@ -53950,6 +53980,9 @@ var pluginHostBridge = {
53950
53980
  }
53951
53981
  return pluginId;
53952
53982
  },
53983
+ useDocsUrl(pluginId, page, anchor) {
53984
+ return usePluginDocsUrl(pluginId, page, anchor);
53985
+ },
53953
53986
  useTranslation(pluginId) {
53954
53987
  const { t, i18n: i18n2 } = useI18nTranslation(pluginId);
53955
53988
  return {
@@ -54176,6 +54209,18 @@ function pluginKey(pluginId, key) {
54176
54209
 
54177
54210
  // src/ui/plugin-host/app.ts
54178
54211
  var ONBOARDING_ID = /^[a-z0-9][a-z0-9-]{0,63}$/;
54212
+ function createDocsApi(manifest) {
54213
+ const url = pluginDocsUrl(manifest?.docs);
54214
+ const page = (name, anchor) => url ? pluginDocsPage(url, name, anchor) : null;
54215
+ return {
54216
+ url,
54217
+ page,
54218
+ open(name, anchor) {
54219
+ const target = page(name, anchor);
54220
+ if (target) window.open(target, "_blank", "noopener,noreferrer");
54221
+ }
54222
+ };
54223
+ }
54179
54224
  function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54180
54225
  const bag = [];
54181
54226
  let disposed = false;
@@ -54580,6 +54625,7 @@ function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54580
54625
  onReady: (listener) => track(tabsApi.onReady(listener))
54581
54626
  },
54582
54627
  confirm: (options3) => shell.confirm(options3),
54628
+ docs: createDocsApi(manifest),
54583
54629
  desktop: {
54584
54630
  available: isElectron(),
54585
54631
  remoteServerUrl,
@@ -54622,8 +54668,8 @@ function createPluginApp(pluginId, manifest, contributes, options2 = {}) {
54622
54668
 
54623
54669
  // src/ui/shell/ActionSlot.tsx
54624
54670
  import React50 from "react";
54625
- import { useTranslation as useTranslation5 } from "react-i18next";
54626
- import { Fragment as Fragment14, jsx as jsx37, jsxs as jsxs11 } from "react/jsx-runtime";
54671
+ import { useTranslation as useTranslation6 } from "react-i18next";
54672
+ import { Fragment as Fragment14, jsx as jsx38, jsxs as jsxs11 } from "react/jsx-runtime";
54627
54673
  function ActionSlot({
54628
54674
  slotId,
54629
54675
  context,
@@ -54633,17 +54679,17 @@ function ActionSlot({
54633
54679
  hideLabels,
54634
54680
  when
54635
54681
  }) {
54636
- const { t } = useTranslation5();
54682
+ const { t } = useTranslation6();
54637
54683
  const contributions2 = useActionSlot(slotId, when).filter(
54638
54684
  (contribution) => contribution.kind !== "component"
54639
54685
  );
54640
54686
  if (!enabled || contributions2.length === 0) return null;
54641
- return /* @__PURE__ */ jsx37(Fragment14, { children: contributions2.map((contribution) => {
54687
+ return /* @__PURE__ */ jsx38(Fragment14, { children: contributions2.map((contribution) => {
54642
54688
  const invoke = () => {
54643
54689
  void invokeAction(contribution.actionId, ...context?.() ?? []);
54644
54690
  };
54645
54691
  if (renderItem) {
54646
- return /* @__PURE__ */ jsx37(React50.Fragment, { children: renderItem(contribution, invoke) }, contribution.actionId);
54692
+ return /* @__PURE__ */ jsx38(React50.Fragment, { children: renderItem(contribution, invoke) }, contribution.actionId);
54647
54693
  }
54648
54694
  const Icon2 = contribution.icon;
54649
54695
  const label = t(contribution.titleKey);
@@ -54656,7 +54702,7 @@ function ActionSlot({
54656
54702
  title: label,
54657
54703
  onClick: invoke,
54658
54704
  children: [
54659
- Icon2 && /* @__PURE__ */ jsx37(Icon2, { className: "size-4 shrink-0" }),
54705
+ Icon2 && /* @__PURE__ */ jsx38(Icon2, { className: "size-4 shrink-0" }),
54660
54706
  !hideLabels && label
54661
54707
  ]
54662
54708
  },
@@ -54673,37 +54719,37 @@ function ComponentSlot({
54673
54719
  (contribution) => contribution.kind === "component" && contribution.component
54674
54720
  );
54675
54721
  if (contributions2.length === 0) return null;
54676
- return /* @__PURE__ */ jsx37(Fragment14, { children: contributions2.map((contribution) => {
54722
+ return /* @__PURE__ */ jsx38(Fragment14, { children: contributions2.map((contribution) => {
54677
54723
  const Contributed = contribution.component;
54678
- return /* @__PURE__ */ jsx37(Contributed, { ...props ?? {} }, contribution.actionId);
54724
+ return /* @__PURE__ */ jsx38(Contributed, { ...props ?? {} }, contribution.actionId);
54679
54725
  }) });
54680
54726
  }
54681
54727
 
54682
54728
  // src/ui/plugin-host/PluginViewPlaceholder.tsx
54683
- import { useTranslation as useTranslation6 } from "react-i18next";
54729
+ import { useTranslation as useTranslation7 } from "react-i18next";
54684
54730
 
54685
54731
  // src/ui/plugins/open-plugins.ts
54686
54732
  var OPEN_PLUGINS_EVENT = "termix:open-plugins";
54687
54733
 
54688
54734
  // src/ui/plugin-host/PluginViewPlaceholder.tsx
54689
- import { jsx as jsx38, jsxs as jsxs12 } from "react/jsx-runtime";
54735
+ import { jsx as jsx39, jsxs as jsxs12 } from "react/jsx-runtime";
54690
54736
  function PluginViewPlaceholder({
54691
54737
  kind,
54692
54738
  viewId,
54693
54739
  compact = false
54694
54740
  }) {
54695
- const { t } = useTranslation6();
54741
+ const { t } = useTranslation7();
54696
54742
  usePluginStore();
54697
54743
  const { has } = usePermissions();
54698
54744
  const { status, owner } = unregisteredViewStatus(kind, viewId);
54699
54745
  const name = owner?.summary.name ?? viewId;
54700
54746
  if (status === "loading") {
54701
- return /* @__PURE__ */ jsx38(
54747
+ return /* @__PURE__ */ jsx39(
54702
54748
  "div",
54703
54749
  {
54704
54750
  className: "flex h-full w-full items-center justify-center",
54705
54751
  "aria-label": t("plugins.runtime.loading", { name }),
54706
- children: /* @__PURE__ */ jsx38("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" })
54752
+ children: /* @__PURE__ */ jsx39("div", { className: "size-5 rounded-full border-2 border-muted-foreground/30 border-t-muted-foreground/70 animate-spin" })
54707
54753
  }
54708
54754
  );
54709
54755
  }
@@ -54716,10 +54762,10 @@ function PluginViewPlaceholder({
54716
54762
  "data-status": status,
54717
54763
  className: `flex h-full w-full flex-col items-center justify-center gap-2 text-center ${compact ? "p-3" : "p-6"}`,
54718
54764
  children: [
54719
- /* @__PURE__ */ jsx38(CircleSlash, { className: "size-6 text-muted-foreground/40" }),
54720
- /* @__PURE__ */ jsx38("span", { className: "text-sm font-semibold text-muted-foreground", children: title }),
54721
- /* @__PURE__ */ jsx38("span", { className: "text-xs text-muted-foreground/70", children: hint }),
54722
- !compact && status !== "failed" && has("admin.plugins.manage") && /* @__PURE__ */ jsx38(
54765
+ /* @__PURE__ */ jsx39(CircleSlash, { className: "size-6 text-muted-foreground/40" }),
54766
+ /* @__PURE__ */ jsx39("span", { className: "text-sm font-semibold text-muted-foreground", children: title }),
54767
+ /* @__PURE__ */ jsx39("span", { className: "text-xs text-muted-foreground/70", children: hint }),
54768
+ !compact && status !== "failed" && has("admin.plugins.manage") && /* @__PURE__ */ jsx39(
54723
54769
  Button,
54724
54770
  {
54725
54771
  variant: "outline",
@@ -54825,6 +54871,7 @@ export {
54825
54871
  notifyShellReady,
54826
54872
  resetShellBridge,
54827
54873
  usePluginScope,
54874
+ DocsLink,
54828
54875
  PANEL,
54829
54876
  PanelShell,
54830
54877
  BackButton,