@termix-ssh/plugin-sdk 1.0.1 → 1.0.2
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.
- package/CHANGELOG.md +54 -0
- package/README.md +1 -1
- package/dist/changelog.d.ts +3 -8
- package/dist/changelog.js +6 -14
- package/dist/host/{chunk-COGRSHKO.js → chunk-LKYAVM4T.js} +2 -2
- package/dist/host/{chunk-7GL25GFS.js → chunk-SS43S6VI.js} +81 -16
- package/dist/host/{chunk-3KG44G55.js → chunk-XKMRKQFN.js} +1 -1
- package/dist/host/{linked-server-BD573BEG.js → linked-server-DW5P3NLZ.js} +1 -1
- package/dist/host/{rbac-api-7GHDJLU4.js → rbac-api-JKISXZEP.js} +1 -1
- package/dist/host/testing-host.js +3 -3
- package/dist/host/types/src/ui/api/plugins-api.d.ts +17 -0
- package/dist/host/types/src/ui/api/system-status-api.d.ts +24 -0
- package/dist/host/types/src/ui/hooks/use-installed-plugins.d.ts +6 -0
- package/dist/host/types/src/ui/settings/settings-fields-util.d.ts +10 -0
- package/dist/host/ui.js +136 -110
- package/dist/host/{use-permissions-SKWXHWDJ.js → use-permissions-5CZEA3P4.js} +1 -1
- package/dist/manifest.d.ts +9 -0
- package/dist/manifest.js +36 -0
- package/package.json +3 -2
- package/schema/manifest.schema.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `features` manifest field: up to 20 short lines about what the plugin does, shown on its page. `pluginFeatures()` reads them
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `parseChangelog()` and `validateChangelog()` flag a `## Unreleased` section. Notes go under the version they ship in
|
|
12
|
+
|
|
13
|
+
### Removed
|
|
14
|
+
|
|
15
|
+
- `Changelog.unreleased`
|
|
16
|
+
|
|
17
|
+
## 1.0.1
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Plugin API 1.1
|
|
22
|
+
- `app.registerOnboardingStep()` adds a step to onboarding, with `OnboardingStepProps` and `OnboardingStepContribution`. Needs `engine.api` 1.1
|
|
23
|
+
- `video` manifest field: a YouTube link shown at the top of the plugin's page, read with `youtubeVideoId()`
|
|
24
|
+
- `@termix-ssh/plugin-sdk/changelog` parses and checks a plugin's `CHANGELOG.md`
|
|
25
|
+
- `termix-plugin changelog` prints one version's notes from `CHANGELOG.md`
|
|
26
|
+
- `termix-plugin dev` installs a plugin on a running server and reinstalls it on every change. Needs developer mode and an admin API key
|
|
27
|
+
- `renderOnboardingStep()` and `registered.onboardingSteps()` in `renderWithApp()`
|
|
28
|
+
- `AddButton`, `FormFooter`, `ListRow`, `ListRowAction`, `ListRowFolder`, `ListBadge` and `PanelList` in `@termix-ssh/plugin-sdk/ui`
|
|
29
|
+
- `PanelSearch` takes `disabled`, `title`, `onEnter` and `inputRef`, and `SurfaceScope` takes `onClose`
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Release notes moved from `CHANGELOG.json` to `CHANGELOG.md`, which `pack` now ships. `validate` flags a leftover `CHANGELOG.json`
|
|
34
|
+
- `ConnectionScreen` dropped `errorMessage` and `retryLabel` and gained `onClose`. `ConnectionLogPanel` dropped `position`
|
|
35
|
+
- Plugin tests run in worker threads, since forked workers crash now and then on Windows
|
|
36
|
+
- The test setup stubs `ResizeObserver` for Radix controls under jsdom
|
|
37
|
+
- `onboarding` is a reserved plugin id
|
|
38
|
+
|
|
39
|
+
### Deprecated
|
|
40
|
+
|
|
41
|
+
- The `onboarding.*` action slots. Use `registerOnboardingStep()`
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- `schema/changelog.schema.json`
|
|
46
|
+
|
|
47
|
+
## 1.0.0
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Backend, frontend, database, manifest, capability and settings types for plugin API 1.0
|
|
52
|
+
- Testing helpers and a Vitest preset for plugin tests
|
|
53
|
+
- The `@termix-ssh/plugin-sdk/ui` component set
|
|
54
|
+
- The `termix-plugin` CLI to build, validate, test, pack, sign and write migrations
|
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ in place of the flags. Reload the page to pick up frontend changes.
|
|
|
48
48
|
|
|
49
49
|
## Support
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Bugs and ideas for the SDK or the `termix-plugin` CLI go in the [Termix repo](https://github.com/Termix-SSH/Termix/issues/new/choose). You need to be logged in to GitHub. Please be as detailed as possible, preferably in English.
|
|
52
52
|
|
|
53
53
|
For discussions and questions, join the [Discord](https://discord.gg/jVQGdvHDrf) server.
|
|
54
54
|
|
package/dist/changelog.d.ts
CHANGED
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* # Changelog
|
|
5
5
|
*
|
|
6
|
-
* ##
|
|
7
|
-
*
|
|
8
|
-
* ### Added
|
|
9
|
-
* - Something merged but not shipped yet
|
|
10
|
-
*
|
|
11
|
-
* ## 1.1.0 - 2026-10-06
|
|
6
|
+
* ## 1.1.0
|
|
12
7
|
*
|
|
13
8
|
* An optional summary paragraph.
|
|
14
9
|
*
|
|
@@ -19,7 +14,8 @@
|
|
|
19
14
|
* - Something broken
|
|
20
15
|
*
|
|
21
16
|
* Releases are listed newest first, and the newest one has to be the
|
|
22
|
-
* manifest version.
|
|
17
|
+
* manifest version. Notes go straight under the version they ship in, there
|
|
18
|
+
* is no Unreleased section.
|
|
23
19
|
*/
|
|
24
20
|
export declare const CHANGE_TYPES: readonly ["added", "changed", "fixed", "removed", "deprecated", "security"];
|
|
25
21
|
export type ChangeType = (typeof CHANGE_TYPES)[number];
|
|
@@ -34,7 +30,6 @@ export interface ChangelogRelease {
|
|
|
34
30
|
changes: ChangelogChange[];
|
|
35
31
|
}
|
|
36
32
|
export interface Changelog {
|
|
37
|
-
unreleased: ChangelogChange[];
|
|
38
33
|
/** Newest first, as written. */
|
|
39
34
|
releases: ChangelogRelease[];
|
|
40
35
|
}
|
package/dist/changelog.js
CHANGED
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* # Changelog
|
|
5
5
|
*
|
|
6
|
-
* ##
|
|
7
|
-
*
|
|
8
|
-
* ### Added
|
|
9
|
-
* - Something merged but not shipped yet
|
|
10
|
-
*
|
|
11
|
-
* ## 1.1.0 - 2026-10-06
|
|
6
|
+
* ## 1.1.0
|
|
12
7
|
*
|
|
13
8
|
* An optional summary paragraph.
|
|
14
9
|
*
|
|
@@ -19,7 +14,8 @@
|
|
|
19
14
|
* - Something broken
|
|
20
15
|
*
|
|
21
16
|
* Releases are listed newest first, and the newest one has to be the
|
|
22
|
-
* manifest version.
|
|
17
|
+
* manifest version. Notes go straight under the version they ship in, there
|
|
18
|
+
* is no Unreleased section.
|
|
23
19
|
*/
|
|
24
20
|
import semver from "semver";
|
|
25
21
|
export const CHANGE_TYPES = [
|
|
@@ -94,7 +90,7 @@ export function parseReleaseNotes(body, where = "release") {
|
|
|
94
90
|
/** Parses a whole CHANGELOG.md. Problems are reported, never thrown. */
|
|
95
91
|
export function parseChangelog(markdown) {
|
|
96
92
|
const problems = [];
|
|
97
|
-
const changelog = {
|
|
93
|
+
const changelog = { releases: [] };
|
|
98
94
|
const lines = markdown.replace(/\r/g, "").split("\n");
|
|
99
95
|
const blocks = [];
|
|
100
96
|
let fence = false;
|
|
@@ -109,13 +105,9 @@ export function parseChangelog(markdown) {
|
|
|
109
105
|
}
|
|
110
106
|
});
|
|
111
107
|
const seen = new Set();
|
|
112
|
-
blocks.forEach((block
|
|
108
|
+
blocks.forEach((block) => {
|
|
113
109
|
if (UNRELEASED_HEADING.test(block.heading)) {
|
|
114
|
-
|
|
115
|
-
problems.push(`"## Unreleased" has to come first`);
|
|
116
|
-
const notes = parseReleaseNotes(block.body.join("\n"), "Unreleased");
|
|
117
|
-
problems.push(...notes.problems);
|
|
118
|
-
changelog.unreleased = notes.changes;
|
|
110
|
+
problems.push(`line ${block.line}: drop "## Unreleased", notes go under the version they ship in`);
|
|
119
111
|
return;
|
|
120
112
|
}
|
|
121
113
|
const match = RELEASE_HEADING.exec(block.heading);
|
|
@@ -5037,7 +5037,7 @@ async function updateRole(roleId, roleData) {
|
|
|
5037
5037
|
try {
|
|
5038
5038
|
const response = await rbacApi.put(`/rbac/roles/${roleId}`, roleData);
|
|
5039
5039
|
if (roleData.permissions) {
|
|
5040
|
-
const { notifyPermissionsChanged } = await import("./use-permissions-
|
|
5040
|
+
const { notifyPermissionsChanged } = await import("./use-permissions-5CZEA3P4.js");
|
|
5041
5041
|
notifyPermissionsChanged();
|
|
5042
5042
|
}
|
|
5043
5043
|
return response.data;
|
|
@@ -5587,7 +5587,7 @@ function createRemoteOriginApiInstance(path) {
|
|
|
5587
5587
|
});
|
|
5588
5588
|
instance.interceptors.request.use(
|
|
5589
5589
|
async (config) => {
|
|
5590
|
-
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-
|
|
5590
|
+
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-DW5P3NLZ.js");
|
|
5591
5591
|
const linked = await getLinkedSession2();
|
|
5592
5592
|
config.baseURL = linked ? `${linked.serverUrl}${path}` : "http://no-server-configured";
|
|
5593
5593
|
if (config.headers.set) {
|
|
@@ -2,7 +2,7 @@ const __termixNoGlob = () => ({});
|
|
|
2
2
|
import {
|
|
3
3
|
hasPermission,
|
|
4
4
|
usePermissions
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-XKMRKQFN.js";
|
|
6
6
|
import {
|
|
7
7
|
PRESETS,
|
|
8
8
|
SYNC_CHANGED_EVENT,
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
usePluginStore,
|
|
39
39
|
useTabTypes,
|
|
40
40
|
websocketAuthProtocols
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-LKYAVM4T.js";
|
|
42
42
|
import {
|
|
43
43
|
__export,
|
|
44
44
|
define_import_meta_env_default
|
|
@@ -51082,7 +51082,7 @@ var en_default = {
|
|
|
51082
51082
|
empty: "No cards. Use Add below to place cards."
|
|
51083
51083
|
},
|
|
51084
51084
|
auth: {
|
|
51085
|
-
tagline: "Self-hosted
|
|
51085
|
+
tagline: "Self-hosted, plugin-based server management",
|
|
51086
51086
|
loginTitle: "Welcome back",
|
|
51087
51087
|
registerTitle: "Create Account",
|
|
51088
51088
|
forgotPassword: "Forgot Password?",
|
|
@@ -51216,7 +51216,7 @@ var en_default = {
|
|
|
51216
51216
|
title: "Dashboard",
|
|
51217
51217
|
loading: "Loading dashboard...",
|
|
51218
51218
|
github: "GitHub",
|
|
51219
|
-
|
|
51219
|
+
reportBug: "Report a bug",
|
|
51220
51220
|
discord: "Discord",
|
|
51221
51221
|
docs: "Docs",
|
|
51222
51222
|
donate: "Donate",
|
|
@@ -51732,10 +51732,6 @@ var en_default = {
|
|
|
51732
51732
|
twoFaOn: "On",
|
|
51733
51733
|
twoFaOff: "Off",
|
|
51734
51734
|
versionLabel: "Version",
|
|
51735
|
-
betaProgramTitle: "Beta Program",
|
|
51736
|
-
betaProgramDescription: "Try upcoming features early with the weekly rolling :beta Docker tag. Unstable, not for production.",
|
|
51737
|
-
betaProgramFeedback: "Found a bug? Report it here.",
|
|
51738
|
-
betaProgramLearnMore: "Learn More",
|
|
51739
51735
|
cliTitle: "Termix CLI",
|
|
51740
51736
|
cliDescription: "Open terminals, run commands and move files from your own shell.",
|
|
51741
51737
|
cliLearnMore: "Learn More",
|
|
@@ -51870,7 +51866,13 @@ var en_default = {
|
|
|
51870
51866
|
linkedProfileDescription: "This device uses your account on the server and keeps a copy of your data, so it still works offline.",
|
|
51871
51867
|
manageSync: "Manage sync",
|
|
51872
51868
|
setUpSync: "Link to a server",
|
|
51873
|
-
settingsTabsAndShortcuts: "Tabs and Shortcuts"
|
|
51869
|
+
settingsTabsAndShortcuts: "Tabs and Shortcuts",
|
|
51870
|
+
onBetaTitle: "You are on a beta",
|
|
51871
|
+
onBetaDescription: "Thanks for testing. Tell us what breaks.",
|
|
51872
|
+
tryBetaTitle: "Try new features early",
|
|
51873
|
+
tryBetaDescription: "Join the beta program in Updates.",
|
|
51874
|
+
sendBetaFeedback: "Send beta feedback",
|
|
51875
|
+
openUpdates: "Updates"
|
|
51874
51876
|
}
|
|
51875
51877
|
}
|
|
51876
51878
|
},
|
|
@@ -52152,8 +52154,9 @@ var en_default = {
|
|
|
52152
52154
|
releaseNotes: "Release notes",
|
|
52153
52155
|
noReleaseNotes: "No notes for this version.",
|
|
52154
52156
|
viewRelease: "View on GitHub",
|
|
52155
|
-
|
|
52156
|
-
|
|
52157
|
+
showOlderReleases: "Older releases ({{count}})",
|
|
52158
|
+
hideOlderReleases: "Hide older releases",
|
|
52159
|
+
showMoreChanges: "Show {{count}} more",
|
|
52157
52160
|
videoTitle: "{{name}} video",
|
|
52158
52161
|
changeTypes: {
|
|
52159
52162
|
added: "Added",
|
|
@@ -52165,6 +52168,7 @@ var en_default = {
|
|
|
52165
52168
|
},
|
|
52166
52169
|
installedBadge: "Installed",
|
|
52167
52170
|
incompatible: "Needs a newer Termix",
|
|
52171
|
+
features: "Features",
|
|
52168
52172
|
whatItCanDo: "What it can do",
|
|
52169
52173
|
addsToTermix: "Adds to Termix",
|
|
52170
52174
|
addsToTermixHint: "Uninstall and every one of these goes away.",
|
|
@@ -52209,7 +52213,23 @@ var en_default = {
|
|
|
52209
52213
|
unverifiedHint: "Installed from a file. Termix did not check or sign it.",
|
|
52210
52214
|
reportIssue: "Report an issue",
|
|
52211
52215
|
reportTo: "Report to {{author}}",
|
|
52212
|
-
|
|
52216
|
+
requestFeature: "Request a feature",
|
|
52217
|
+
openSettings: "Open its settings",
|
|
52218
|
+
beta: {
|
|
52219
|
+
badge: "Beta",
|
|
52220
|
+
badgeHint: "This is a beta version. It can have bugs.",
|
|
52221
|
+
try: "Try beta {{version}}",
|
|
52222
|
+
confirmTitle: "Use betas for {{name}}?",
|
|
52223
|
+
confirmBody: "Betas get new features and fixes first, but they can have bugs. You can switch back to stable at any time.",
|
|
52224
|
+
confirmInstall: "Install {{version}}",
|
|
52225
|
+
confirmSwitch: "Use betas",
|
|
52226
|
+
leftWhileOnBeta: "{{name}} stays on {{version}} until a stable release is newer.",
|
|
52227
|
+
channel: "Beta versions",
|
|
52228
|
+
channelHint: "Get new versions early. They can have bugs.",
|
|
52229
|
+
waitingForStable: "Back on stable. Stays on {{version}} until a stable release is newer.",
|
|
52230
|
+
versionLabel: "{{version}} (beta)",
|
|
52231
|
+
sendFeedback: "Send beta feedback"
|
|
52232
|
+
}
|
|
52213
52233
|
}
|
|
52214
52234
|
},
|
|
52215
52235
|
settings: {
|
|
@@ -52248,7 +52268,39 @@ var en_default = {
|
|
|
52248
52268
|
instanceSettings: "Server settings",
|
|
52249
52269
|
nothingHere: "Nothing to set here.",
|
|
52250
52270
|
searchPage: "Search this page",
|
|
52251
|
-
noPageMatches: "Nothing on this page matches."
|
|
52271
|
+
noPageMatches: "Nothing on this page matches.",
|
|
52272
|
+
updates: {
|
|
52273
|
+
title: "Updates",
|
|
52274
|
+
betaProgram: "Beta program",
|
|
52275
|
+
betaIntro: "Betas are early builds of the next Termix version. They come out on Tuesdays and Fridays when there is something new to try. They can have bugs, and your feedback is what makes the stable release better.",
|
|
52276
|
+
termixBetas: "Termix betas",
|
|
52277
|
+
termixBetasHint: "Tell me about new betas, not only stable releases.",
|
|
52278
|
+
runningBetaBuild: "You are running a beta build. To leave, switch back to a stable image or installer.",
|
|
52279
|
+
howToInstall: "How to get the beta",
|
|
52280
|
+
desktopInstall: "Download the beta installer and install it over this one.",
|
|
52281
|
+
downloadBeta: "Download {{version}}",
|
|
52282
|
+
dockerInstall: "Change your image to the beta tag and restart the container:",
|
|
52283
|
+
copy: "Copy",
|
|
52284
|
+
copied: "Copied",
|
|
52285
|
+
copyFailed: "Could not copy",
|
|
52286
|
+
learnMore: "Learn more about betas",
|
|
52287
|
+
feedback: "Feedback",
|
|
52288
|
+
feedbackHint: "Found a bug or have an idea? It goes straight to the developers with your version filled in.",
|
|
52289
|
+
sendFeedback: "Send beta feedback",
|
|
52290
|
+
releases: "Releases",
|
|
52291
|
+
thisVersion: "This version",
|
|
52292
|
+
newestStable: "Newest stable",
|
|
52293
|
+
newestBeta: "Newest beta",
|
|
52294
|
+
noRelease: "None right now",
|
|
52295
|
+
pluginBetas: "Plugin betas",
|
|
52296
|
+
pluginBetasIntro: "Each plugin can use betas on its own. To try one, open it and press Try beta.",
|
|
52297
|
+
openPlugins: "Open Plugins",
|
|
52298
|
+
waitingForStable: "Back on stable. Stays on {{version}} until a stable release is newer.",
|
|
52299
|
+
backToStable: "Back to stable",
|
|
52300
|
+
allPluginsBeta: "Use betas for all plugins",
|
|
52301
|
+
allPluginsStable: "All plugins back to stable",
|
|
52302
|
+
saveFailed: "Could not save the change"
|
|
52303
|
+
}
|
|
52252
52304
|
},
|
|
52253
52305
|
managerCard: {
|
|
52254
52306
|
refresh: "Refresh",
|
|
@@ -52593,7 +52645,9 @@ i18n.use(localeBackend).use(Browser).use(initReactI18next).init({
|
|
|
52593
52645
|
escapeValue: false
|
|
52594
52646
|
},
|
|
52595
52647
|
react: {
|
|
52596
|
-
useSuspense: false
|
|
52648
|
+
useSuspense: false,
|
|
52649
|
+
// Repaint when a plugin enabled later loads its strings.
|
|
52650
|
+
bindI18n: "languageChanged loaded"
|
|
52597
52651
|
}
|
|
52598
52652
|
});
|
|
52599
52653
|
async function changeAppLanguage(language) {
|
|
@@ -53072,7 +53126,7 @@ function createPluginApi(pluginId) {
|
|
|
53072
53126
|
get(target, property, receiver) {
|
|
53073
53127
|
const value = Reflect.get(target, property, receiver);
|
|
53074
53128
|
if (typeof value !== "function") return value;
|
|
53075
|
-
if (property === "get" || property === "delete" || property === "head" || property === "options" || property === "post" || property === "put" || property === "patch") {
|
|
53129
|
+
if (property === "get" || property === "delete" || property === "head" || property === "options" || property === "post" || property === "put" || property === "patch" || property === "postForm" || property === "putForm" || property === "patchForm") {
|
|
53076
53130
|
return (url, ...rest) => value.call(
|
|
53077
53131
|
target,
|
|
53078
53132
|
`${prefix}${url.startsWith("/") ? url : `/${url}`}`,
|
|
@@ -53448,6 +53502,7 @@ async function getPlugins() {
|
|
|
53448
53502
|
const response = await rbacApi.get("/plugins");
|
|
53449
53503
|
return Array.isArray(response.data) ? response.data : [];
|
|
53450
53504
|
}
|
|
53505
|
+
var PLUGINS_CHANGED_EVENT = "termix:plugins-changed";
|
|
53451
53506
|
|
|
53452
53507
|
// src/ui/plugin-host/scope.tsx
|
|
53453
53508
|
import {
|
|
@@ -53912,13 +53967,22 @@ var pluginHostBridge = {
|
|
|
53912
53967
|
useEffect34(() => {
|
|
53913
53968
|
let cancelled = false;
|
|
53914
53969
|
if (scope === "host" && !Number.isFinite(numericHostId)) return;
|
|
53915
|
-
SETTINGS_READERS[scope](pluginId, numericHostId).then((values) => {
|
|
53970
|
+
const read = () => SETTINGS_READERS[scope](pluginId, numericHostId).then((values) => {
|
|
53916
53971
|
if (!cancelled) setState({ values, loaded: true });
|
|
53917
53972
|
}).catch(() => {
|
|
53918
53973
|
if (!cancelled) setState((prev) => ({ ...prev, loaded: true }));
|
|
53919
53974
|
});
|
|
53975
|
+
void read();
|
|
53976
|
+
const onChanged = (event) => {
|
|
53977
|
+
const detail = event.detail;
|
|
53978
|
+
if (detail?.pluginId !== pluginId || detail.scope !== scope) return;
|
|
53979
|
+
if (scope === "host" && detail.hostId !== numericHostId) return;
|
|
53980
|
+
void read();
|
|
53981
|
+
};
|
|
53982
|
+
window.addEventListener(PLUGIN_SETTINGS_CHANGED_EVENT, onChanged);
|
|
53920
53983
|
return () => {
|
|
53921
53984
|
cancelled = true;
|
|
53985
|
+
window.removeEventListener(PLUGIN_SETTINGS_CHANGED_EVENT, onChanged);
|
|
53922
53986
|
};
|
|
53923
53987
|
}, [pluginId, scope, numericHostId]);
|
|
53924
53988
|
const save = useCallback21(
|
|
@@ -54797,6 +54861,7 @@ export {
|
|
|
54797
54861
|
isRedactedSecret,
|
|
54798
54862
|
isFieldActive,
|
|
54799
54863
|
getPlugins,
|
|
54864
|
+
PLUGINS_CHANGED_EVENT,
|
|
54800
54865
|
useAreaPreferences,
|
|
54801
54866
|
getPanel,
|
|
54802
54867
|
listPanels,
|
|
@@ -21,7 +21,7 @@ var inFlight = null;
|
|
|
21
21
|
function load() {
|
|
22
22
|
if (cache) return Promise.resolve(cache);
|
|
23
23
|
if (inFlight) return inFlight;
|
|
24
|
-
inFlight = import("./rbac-api-
|
|
24
|
+
inFlight = import("./rbac-api-JKISXZEP.js").then(({ getMyPermissions }) => getMyPermissions()).then((result) => {
|
|
25
25
|
cache = {
|
|
26
26
|
permissions: result.permissions ?? [],
|
|
27
27
|
isAdmin: !!result.isAdmin
|
|
@@ -40,16 +40,16 @@ import {
|
|
|
40
40
|
setShellCallbacks,
|
|
41
41
|
setShellHosts,
|
|
42
42
|
setShellLayoutProvider
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-SS43S6VI.js";
|
|
44
44
|
import {
|
|
45
45
|
setPermissionsForTesting
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-XKMRKQFN.js";
|
|
47
47
|
import {
|
|
48
48
|
getTabType,
|
|
49
49
|
listTabTypes,
|
|
50
50
|
setFrontendState,
|
|
51
51
|
setPluginSummaries
|
|
52
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-LKYAVM4T.js";
|
|
53
53
|
import "./chunk-YC2MAKSP.js";
|
|
54
54
|
|
|
55
55
|
// src/ui/plugin-host/testing-host.tsx
|
|
@@ -130,18 +130,24 @@ export interface PluginSummary {
|
|
|
130
130
|
registryId?: string | null;
|
|
131
131
|
autoUpdate?: boolean;
|
|
132
132
|
pinnedVersion?: string | null;
|
|
133
|
+
channel?: PluginChannel;
|
|
133
134
|
description?: string;
|
|
134
135
|
author?: string;
|
|
135
136
|
repository?: string;
|
|
136
137
|
/** A YouTube video id from the manifest. Admins only. */
|
|
137
138
|
videoId?: string;
|
|
139
|
+
/** What the plugin does, from the manifest. Admins only. */
|
|
140
|
+
features?: string[];
|
|
138
141
|
signedBy?: string | null;
|
|
139
142
|
}
|
|
140
143
|
export declare function getPlugins(): Promise<PluginSummary[]>;
|
|
141
144
|
/** Fired after any change to installed plugins; the loader resyncs on it. */
|
|
142
145
|
export declare const PLUGINS_CHANGED_EVENT = "termix:plugins-changed";
|
|
146
|
+
export type PluginChannel = "stable" | "beta";
|
|
143
147
|
export interface RegistryPluginVersion {
|
|
144
148
|
version: string;
|
|
149
|
+
/** A beta, offered only to plugins on the beta channel. */
|
|
150
|
+
prerelease?: boolean;
|
|
145
151
|
compatible: boolean;
|
|
146
152
|
capabilities: string[];
|
|
147
153
|
publishedAt?: string;
|
|
@@ -158,8 +164,13 @@ export interface RegistryPluginEntry {
|
|
|
158
164
|
repository?: string;
|
|
159
165
|
icon?: string;
|
|
160
166
|
videoId?: string;
|
|
167
|
+
features?: string[];
|
|
161
168
|
versions: RegistryPluginVersion[];
|
|
169
|
+
/** Newest release on the plugin's channel. */
|
|
162
170
|
latestVersion: string | null;
|
|
171
|
+
/** Newest beta, when one is newer than stable. */
|
|
172
|
+
latestBeta?: string | null;
|
|
173
|
+
channel?: PluginChannel;
|
|
163
174
|
installed: boolean;
|
|
164
175
|
installedVersion: string | null;
|
|
165
176
|
updateAvailable: boolean;
|
|
@@ -254,9 +265,14 @@ export declare function retryPlugin(pluginId: string): Promise<void>;
|
|
|
254
265
|
export declare function setPluginOptions(pluginId: string, options: {
|
|
255
266
|
autoUpdate?: boolean;
|
|
256
267
|
pinned?: boolean;
|
|
268
|
+
channel?: PluginChannel;
|
|
257
269
|
}): Promise<{
|
|
258
270
|
autoUpdate: boolean;
|
|
259
271
|
pinnedVersion: string | null;
|
|
272
|
+
channel: PluginChannel;
|
|
273
|
+
}>;
|
|
274
|
+
export declare function setAllPluginChannels(channel: PluginChannel): Promise<{
|
|
275
|
+
changed: string[];
|
|
260
276
|
}>;
|
|
261
277
|
export declare function getPluginData(pluginId: string): Promise<PluginDataSummary>;
|
|
262
278
|
export declare function getPluginChangelog(pluginId: string): Promise<ChangelogRelease[]>;
|
|
@@ -277,6 +293,7 @@ export interface PluginUploadPreview {
|
|
|
277
293
|
version: string;
|
|
278
294
|
description: string;
|
|
279
295
|
author: string;
|
|
296
|
+
features?: string[];
|
|
280
297
|
capabilities: string[];
|
|
281
298
|
replaces: string | null;
|
|
282
299
|
}
|
|
@@ -29,6 +29,7 @@ export interface ReleasesRSSResponse {
|
|
|
29
29
|
export declare function getReleasesRSS(perPage?: number): Promise<ReleasesRSSResponse>;
|
|
30
30
|
export interface VersionInfo {
|
|
31
31
|
status?: "up_to_date" | "requires_update" | "beta" | "unknown";
|
|
32
|
+
channel?: "stable" | "beta";
|
|
32
33
|
/** Same value as remoteVersion; the endpoint sends both. */
|
|
33
34
|
version?: string;
|
|
34
35
|
localVersion?: string;
|
|
@@ -46,4 +47,27 @@ export interface VersionInfo {
|
|
|
46
47
|
}
|
|
47
48
|
export declare function releaseUrlFrom(info: VersionInfo | null | undefined): string;
|
|
48
49
|
export declare function getVersionInfo(checkRemote?: boolean): Promise<VersionInfo>;
|
|
50
|
+
export type UpdateChannel = "stable" | "beta";
|
|
51
|
+
export interface UpdateChannelState {
|
|
52
|
+
channel: UpdateChannel;
|
|
53
|
+
/** The admin's choice; a beta build reports beta whatever it is. */
|
|
54
|
+
stored: UpdateChannel;
|
|
55
|
+
runningBeta: boolean;
|
|
56
|
+
}
|
|
57
|
+
export declare function getUpdateChannel(): Promise<UpdateChannelState>;
|
|
58
|
+
export declare function setUpdateChannel(channel: UpdateChannel): Promise<UpdateChannelState>;
|
|
59
|
+
export interface ReleaseInfo {
|
|
60
|
+
version: string;
|
|
61
|
+
tagName: string;
|
|
62
|
+
name: string;
|
|
63
|
+
url: string;
|
|
64
|
+
publishedAt: string;
|
|
65
|
+
prerelease: boolean;
|
|
66
|
+
notes: string;
|
|
67
|
+
}
|
|
68
|
+
export declare function getChannelReleases(): Promise<{
|
|
69
|
+
localVersion: string | null;
|
|
70
|
+
stable: ReleaseInfo | null;
|
|
71
|
+
beta: ReleaseInfo | null;
|
|
72
|
+
}>;
|
|
49
73
|
export declare function getDatabaseHealth(): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PluginSummary } from "../api/plugins-api";
|
|
2
|
+
/**
|
|
3
|
+
* Every installed plugin, read again whenever one is enabled, disabled,
|
|
4
|
+
* installed or removed, so what a plugin adds shows up without a reload.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useInstalledPlugins(): PluginSummary[];
|
|
@@ -9,3 +9,13 @@ import type { PluginSettingsField } from "../api/plugins-api";
|
|
|
9
9
|
export declare function isFieldShown(field: PluginSettingsField, values: Record<string, unknown>): boolean;
|
|
10
10
|
/** Whether a plugin has any field the generic form would ever draw. */
|
|
11
11
|
export declare function hasVisibleFields(fields: PluginSettingsField[]): boolean;
|
|
12
|
+
/** Whether Settings lists a page for this plugin, by the same rule it uses. */
|
|
13
|
+
export declare function hasSettingsPage(plugin: {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
contributes?: {
|
|
16
|
+
settings?: {
|
|
17
|
+
user?: PluginSettingsField[];
|
|
18
|
+
admin?: PluginSettingsField[];
|
|
19
|
+
};
|
|
20
|
+
} | null;
|
|
21
|
+
}, isAdmin: boolean): boolean;
|
package/dist/host/ui.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
LoaderCircle,
|
|
36
36
|
Overlay2,
|
|
37
37
|
PANEL,
|
|
38
|
+
PLUGINS_CHANGED_EVENT,
|
|
38
39
|
PROMPT_BUTTON,
|
|
39
40
|
PROMPT_DESTRUCTIVE_BUTTON,
|
|
40
41
|
PROMPT_PRIMARY_BUTTON,
|
|
@@ -136,8 +137,8 @@ import {
|
|
|
136
137
|
useSurfaceKind,
|
|
137
138
|
useTheme,
|
|
138
139
|
verifySecondFactor
|
|
139
|
-
} from "./chunk-
|
|
140
|
-
import "./chunk-
|
|
140
|
+
} from "./chunk-SS43S6VI.js";
|
|
141
|
+
import "./chunk-XKMRKQFN.js";
|
|
141
142
|
import {
|
|
142
143
|
TabProvider,
|
|
143
144
|
buildOriginWsUrl,
|
|
@@ -176,7 +177,7 @@ import {
|
|
|
176
177
|
useTabs,
|
|
177
178
|
useTabsSafe,
|
|
178
179
|
verifyPasswordResetCode
|
|
179
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-LKYAVM4T.js";
|
|
180
181
|
import "./chunk-YC2MAKSP.js";
|
|
181
182
|
|
|
182
183
|
// src/ui/components/alert.tsx
|
|
@@ -1328,7 +1329,7 @@ function Select22({
|
|
|
1328
1329
|
"aria-expanded": open,
|
|
1329
1330
|
"aria-required": required,
|
|
1330
1331
|
className: cn(
|
|
1331
|
-
"border-input bg-
|
|
1332
|
+
"border-input bg-transparent dark:bg-input/30 dark:hover:bg-input/50 text-foreground flex h-9 w-full items-center justify-between gap-2 rounded-none border px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1332
1333
|
className
|
|
1333
1334
|
),
|
|
1334
1335
|
onKeyDown: (event) => {
|
|
@@ -3216,7 +3217,7 @@ function ConnectionMark({
|
|
|
3216
3217
|
}
|
|
3217
3218
|
|
|
3218
3219
|
// src/ui/components/FullScreenAppWrapper.tsx
|
|
3219
|
-
import { useEffect as
|
|
3220
|
+
import { useEffect as useEffect10, useState as useState13 } from "react";
|
|
3220
3221
|
|
|
3221
3222
|
// src/ui/components/sidebar.tsx
|
|
3222
3223
|
import { useTranslation as useTranslation8 } from "react-i18next";
|
|
@@ -3462,7 +3463,7 @@ function isInElectronWebView() {
|
|
|
3462
3463
|
}
|
|
3463
3464
|
|
|
3464
3465
|
// src/ui/auth/Auth.tsx
|
|
3465
|
-
import { useState as
|
|
3466
|
+
import { useState as useState12, useEffect as useEffect9, useCallback as useCallback6, useRef as useRef4 } from "react";
|
|
3466
3467
|
import { toast as toast2 } from "sonner";
|
|
3467
3468
|
import { useTranslation as useTranslation11 } from "react-i18next";
|
|
3468
3469
|
|
|
@@ -3492,9 +3493,33 @@ function PluginIcon({
|
|
|
3492
3493
|
}
|
|
3493
3494
|
|
|
3494
3495
|
// src/ui/settings/HostPluginSections.tsx
|
|
3495
|
-
import {
|
|
3496
|
+
import { useMemo as useMemo4 } from "react";
|
|
3496
3497
|
import { useTranslation as useTranslation10 } from "react-i18next";
|
|
3497
3498
|
|
|
3499
|
+
// src/ui/hooks/use-installed-plugins.ts
|
|
3500
|
+
import { useEffect as useEffect7, useState as useState10 } from "react";
|
|
3501
|
+
function useInstalledPlugins() {
|
|
3502
|
+
const [plugins, setPlugins] = useState10([]);
|
|
3503
|
+
useEffect7(() => {
|
|
3504
|
+
let cancelled = false;
|
|
3505
|
+
let request = 0;
|
|
3506
|
+
const load = () => {
|
|
3507
|
+
const current = ++request;
|
|
3508
|
+
void getPlugins().then((loaded) => {
|
|
3509
|
+
if (!cancelled && current === request) setPlugins(loaded);
|
|
3510
|
+
}).catch(() => {
|
|
3511
|
+
});
|
|
3512
|
+
};
|
|
3513
|
+
load();
|
|
3514
|
+
window.addEventListener(PLUGINS_CHANGED_EVENT, load);
|
|
3515
|
+
return () => {
|
|
3516
|
+
cancelled = true;
|
|
3517
|
+
window.removeEventListener(PLUGINS_CHANGED_EVENT, load);
|
|
3518
|
+
};
|
|
3519
|
+
}, []);
|
|
3520
|
+
return plugins;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3498
3523
|
// src/ui/settings/SettingsFields.tsx
|
|
3499
3524
|
import { useTranslation as useTranslation9 } from "react-i18next";
|
|
3500
3525
|
import { Fragment as Fragment4, jsx as jsx35, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
@@ -3734,17 +3759,7 @@ function hasVisibleFields(fields) {
|
|
|
3734
3759
|
// src/ui/settings/HostPluginSections.tsx
|
|
3735
3760
|
import { jsx as jsx36, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
3736
3761
|
function usePluginHostSections() {
|
|
3737
|
-
const
|
|
3738
|
-
useEffect7(() => {
|
|
3739
|
-
let cancelled = false;
|
|
3740
|
-
void getPlugins().then((loaded) => {
|
|
3741
|
-
if (!cancelled) setPlugins(loaded);
|
|
3742
|
-
}).catch(() => {
|
|
3743
|
-
});
|
|
3744
|
-
return () => {
|
|
3745
|
-
cancelled = true;
|
|
3746
|
-
};
|
|
3747
|
-
}, []);
|
|
3762
|
+
const plugins = useInstalledPlugins();
|
|
3748
3763
|
return useMemo4(
|
|
3749
3764
|
() => plugins.filter((plugin) => {
|
|
3750
3765
|
if (!plugin.enabled) return false;
|
|
@@ -3859,7 +3874,7 @@ function HostPluginSection({
|
|
|
3859
3874
|
}
|
|
3860
3875
|
|
|
3861
3876
|
// src/ui/sidebar/host-defaults/catalog.ts
|
|
3862
|
-
import {
|
|
3877
|
+
import { useMemo as useMemo5 } from "react";
|
|
3863
3878
|
|
|
3864
3879
|
// src/types/host-defaults.ts
|
|
3865
3880
|
var HOST_DEFAULTS_LEVELS = [
|
|
@@ -4446,9 +4461,9 @@ import {
|
|
|
4446
4461
|
createContext as createContext4,
|
|
4447
4462
|
useCallback as useCallback5,
|
|
4448
4463
|
useContext as useContext4,
|
|
4449
|
-
useEffect as
|
|
4464
|
+
useEffect as useEffect8,
|
|
4450
4465
|
useMemo as useMemo6,
|
|
4451
|
-
useState as
|
|
4466
|
+
useState as useState11
|
|
4452
4467
|
} from "react";
|
|
4453
4468
|
import { jsx as jsx38 } from "react/jsx-runtime";
|
|
4454
4469
|
var DEFAULT_APP_NAME = "Termix";
|
|
@@ -4609,7 +4624,7 @@ function PasswordInput2({
|
|
|
4609
4624
|
disabled,
|
|
4610
4625
|
id
|
|
4611
4626
|
}) {
|
|
4612
|
-
const [show, setShow] =
|
|
4627
|
+
const [show, setShow] = useState12(false);
|
|
4613
4628
|
return /* @__PURE__ */ jsxs26("div", { className: "relative", children: [
|
|
4614
4629
|
/* @__PURE__ */ jsx40(
|
|
4615
4630
|
Input,
|
|
@@ -4662,22 +4677,22 @@ function Auth({ onLogin }) {
|
|
|
4662
4677
|
const { t: t2 } = useTranslation11();
|
|
4663
4678
|
const branding = useBranding();
|
|
4664
4679
|
const localDesktopAuth = isElectron() && !isInElectronWebView();
|
|
4665
|
-
const [view, setView] =
|
|
4666
|
-
const [loading, setLoading] =
|
|
4667
|
-
const [username, setUsername] =
|
|
4668
|
-
const [password, setPassword] =
|
|
4669
|
-
const [confirmPassword, setConfirmPassword] =
|
|
4670
|
-
const [rememberMe, setRememberMe] =
|
|
4680
|
+
const [view, setView] = useState12("login");
|
|
4681
|
+
const [loading, setLoading] = useState12(false);
|
|
4682
|
+
const [username, setUsername] = useState12("");
|
|
4683
|
+
const [password, setPassword] = useState12("");
|
|
4684
|
+
const [confirmPassword, setConfirmPassword] = useState12("");
|
|
4685
|
+
const [rememberMe, setRememberMe] = useState12(() => {
|
|
4671
4686
|
try {
|
|
4672
4687
|
return localStorage.getItem("rememberMe") === "true";
|
|
4673
4688
|
} catch {
|
|
4674
4689
|
return false;
|
|
4675
4690
|
}
|
|
4676
4691
|
});
|
|
4677
|
-
const [pendingToken, setPendingToken] =
|
|
4678
|
-
const [secondFactors, setSecondFactors] =
|
|
4679
|
-
const [activeFactorId, setActiveFactorId] =
|
|
4680
|
-
const [authPluginsLoading, setAuthPluginsLoading] =
|
|
4692
|
+
const [pendingToken, setPendingToken] = useState12("");
|
|
4693
|
+
const [secondFactors, setSecondFactors] = useState12([]);
|
|
4694
|
+
const [activeFactorId, setActiveFactorId] = useState12("");
|
|
4695
|
+
const [authPluginsLoading, setAuthPluginsLoading] = useState12(true);
|
|
4681
4696
|
const retryAuthPlugins = async () => {
|
|
4682
4697
|
setAuthPluginsLoading(true);
|
|
4683
4698
|
try {
|
|
@@ -4694,12 +4709,12 @@ function Auth({ onLogin }) {
|
|
|
4694
4709
|
labelKey: ui.titleKey
|
|
4695
4710
|
}));
|
|
4696
4711
|
const currentFactorId = activeFactorId || shownFactors[0]?.id || "";
|
|
4697
|
-
const [resetStep, setResetStep] =
|
|
4698
|
-
const [resetCode, setResetCode] =
|
|
4699
|
-
const [newPassword, setNewPassword] =
|
|
4700
|
-
const [confirmNewPassword, setConfirmNewPassword] =
|
|
4701
|
-
const [resetTempToken, setResetTempToken] =
|
|
4702
|
-
const [language2, setLanguage] =
|
|
4712
|
+
const [resetStep, setResetStep] = useState12("email");
|
|
4713
|
+
const [resetCode, setResetCode] = useState12("");
|
|
4714
|
+
const [newPassword, setNewPassword] = useState12("");
|
|
4715
|
+
const [confirmNewPassword, setConfirmNewPassword] = useState12("");
|
|
4716
|
+
const [resetTempToken, setResetTempToken] = useState12("");
|
|
4717
|
+
const [language2, setLanguage] = useState12(
|
|
4703
4718
|
() => normalizeLanguageCode(localStorage.getItem("i18nextLng"))
|
|
4704
4719
|
);
|
|
4705
4720
|
function handleLanguageChange(code2) {
|
|
@@ -4707,11 +4722,11 @@ function Auth({ onLogin }) {
|
|
|
4707
4722
|
void changeAppLanguage(language3).then((language4) => setLanguage(language4)).catch(() => {
|
|
4708
4723
|
});
|
|
4709
4724
|
}
|
|
4710
|
-
const [registrationAllowed, setRegistrationAllowed] =
|
|
4711
|
-
const [passwordLoginAllowed, setPasswordLoginAllowed] =
|
|
4712
|
-
const [passwordResetAllowed, setPasswordResetAllowed] =
|
|
4713
|
-
const [authMethods, setAuthMethods] =
|
|
4714
|
-
const [authMethodsLoaded, setAuthMethodsLoaded] =
|
|
4725
|
+
const [registrationAllowed, setRegistrationAllowed] = useState12(true);
|
|
4726
|
+
const [passwordLoginAllowed, setPasswordLoginAllowed] = useState12(true);
|
|
4727
|
+
const [passwordResetAllowed, setPasswordResetAllowed] = useState12(true);
|
|
4728
|
+
const [authMethods, setAuthMethods] = useState12([]);
|
|
4729
|
+
const [authMethodsLoaded, setAuthMethodsLoaded] = useState12(false);
|
|
4715
4730
|
const inlineMethodIds = new Set(
|
|
4716
4731
|
loginMethodUIs.filter((ui) => ui.placement === "inline").map((ui) => ui.id)
|
|
4717
4732
|
);
|
|
@@ -4723,19 +4738,19 @@ function Auth({ onLogin }) {
|
|
|
4723
4738
|
);
|
|
4724
4739
|
const silentSigninHandledRef = useRef4(false);
|
|
4725
4740
|
const proxySigninHandledRef = useRef4(false);
|
|
4726
|
-
const [firstUser, setFirstUser] =
|
|
4727
|
-
const [dbConnectionFailed, setDbConnectionFailed] =
|
|
4728
|
-
const [dbHealthChecking, setDbHealthChecking] =
|
|
4741
|
+
const [firstUser, setFirstUser] = useState12(false);
|
|
4742
|
+
const [dbConnectionFailed, setDbConnectionFailed] = useState12(false);
|
|
4743
|
+
const [dbHealthChecking, setDbHealthChecking] = useState12(
|
|
4729
4744
|
() => !localDesktopAuth || !hasDesktopManualLogout()
|
|
4730
4745
|
);
|
|
4731
|
-
const [webviewAuthSuccess, setWebviewAuthSuccess] =
|
|
4732
|
-
const [desktopManualLogoutActive, setDesktopManualLogoutActive] =
|
|
4746
|
+
const [webviewAuthSuccess, setWebviewAuthSuccess] = useState12(false);
|
|
4747
|
+
const [desktopManualLogoutActive, setDesktopManualLogoutActive] = useState12(
|
|
4733
4748
|
() => localDesktopAuth && hasDesktopManualLogout()
|
|
4734
4749
|
);
|
|
4735
|
-
const [desktopAutoSessionDone, setDesktopAutoSessionDone] =
|
|
4736
|
-
const [desktopAutoSessionRetries, setDesktopAutoSessionRetries] =
|
|
4737
|
-
const [embeddedServerFailure, setEmbeddedServerFailure] =
|
|
4738
|
-
|
|
4750
|
+
const [desktopAutoSessionDone, setDesktopAutoSessionDone] = useState12(!localDesktopAuth || hasDesktopManualLogout() ? true : null);
|
|
4751
|
+
const [desktopAutoSessionRetries, setDesktopAutoSessionRetries] = useState12(0);
|
|
4752
|
+
const [embeddedServerFailure, setEmbeddedServerFailure] = useState12(null);
|
|
4753
|
+
useEffect9(() => {
|
|
4739
4754
|
if (proxySigninHandledRef.current || isElectron()) return;
|
|
4740
4755
|
proxySigninHandledRef.current = true;
|
|
4741
4756
|
requestTrustedProxyLogin().then((result) => {
|
|
@@ -4749,13 +4764,13 @@ function Auth({ onLogin }) {
|
|
|
4749
4764
|
}).catch(() => {
|
|
4750
4765
|
});
|
|
4751
4766
|
}, [onLogin]);
|
|
4752
|
-
|
|
4767
|
+
useEffect9(() => {
|
|
4753
4768
|
try {
|
|
4754
4769
|
localStorage.setItem("rememberMe", rememberMe.toString());
|
|
4755
4770
|
} catch {
|
|
4756
4771
|
}
|
|
4757
4772
|
}, [rememberMe]);
|
|
4758
|
-
|
|
4773
|
+
useEffect9(() => {
|
|
4759
4774
|
if (!isInElectronWebView()) return;
|
|
4760
4775
|
const handleExternalBrowserResult = (event) => {
|
|
4761
4776
|
if (event.source !== window.parent) return;
|
|
@@ -4775,7 +4790,7 @@ function Auth({ onLogin }) {
|
|
|
4775
4790
|
window.addEventListener("message", handleExternalBrowserResult);
|
|
4776
4791
|
return () => window.removeEventListener("message", handleExternalBrowserResult);
|
|
4777
4792
|
}, [t2]);
|
|
4778
|
-
|
|
4793
|
+
useEffect9(() => {
|
|
4779
4794
|
if (localDesktopAuth) {
|
|
4780
4795
|
setAuthMethodsLoaded(true);
|
|
4781
4796
|
return;
|
|
@@ -4789,7 +4804,7 @@ function Auth({ onLogin }) {
|
|
|
4789
4804
|
}).finally(() => setAuthPluginsLoading(false));
|
|
4790
4805
|
getLoginMethods().then((methods) => setAuthMethods(methods)).catch(() => setAuthMethods([])).finally(() => setAuthMethodsLoaded(true));
|
|
4791
4806
|
}, [localDesktopAuth]);
|
|
4792
|
-
|
|
4807
|
+
useEffect9(() => {
|
|
4793
4808
|
if (desktopAutoSessionDone !== true) return;
|
|
4794
4809
|
if (localDesktopAuth) {
|
|
4795
4810
|
setDbHealthChecking(false);
|
|
@@ -4827,7 +4842,7 @@ function Auth({ onLogin }) {
|
|
|
4827
4842
|
if (retryTimer) clearTimeout(retryTimer);
|
|
4828
4843
|
};
|
|
4829
4844
|
}, [desktopAutoSessionDone, localDesktopAuth]);
|
|
4830
|
-
|
|
4845
|
+
useEffect9(() => {
|
|
4831
4846
|
if (desktopAutoSessionDone !== null) return;
|
|
4832
4847
|
if (embeddedServerFailure) return;
|
|
4833
4848
|
let cancelled = false;
|
|
@@ -4875,13 +4890,13 @@ function Auth({ onLogin }) {
|
|
|
4875
4890
|
embeddedServerFailure,
|
|
4876
4891
|
onLogin
|
|
4877
4892
|
]);
|
|
4878
|
-
|
|
4893
|
+
useEffect9(() => {
|
|
4879
4894
|
if (!authMethodsLoaded) return;
|
|
4880
4895
|
if (!passwordLoginAllowed && externalMethods.length > 0 && view === "login") {
|
|
4881
4896
|
setView("external");
|
|
4882
4897
|
}
|
|
4883
4898
|
}, [authMethodsLoaded, passwordLoginAllowed, externalMethods.length, view]);
|
|
4884
|
-
|
|
4899
|
+
useEffect9(() => {
|
|
4885
4900
|
const urlParams = new URLSearchParams(window.location.search);
|
|
4886
4901
|
const success = urlParams.get("success");
|
|
4887
4902
|
const error = urlParams.get("error");
|
|
@@ -5292,7 +5307,7 @@ function Auth({ onLogin }) {
|
|
|
5292
5307
|
}
|
|
5293
5308
|
return null;
|
|
5294
5309
|
}
|
|
5295
|
-
|
|
5310
|
+
useEffect9(() => {
|
|
5296
5311
|
if (!authMethodsLoaded || silentSigninHandledRef.current) return;
|
|
5297
5312
|
const autoStart = externalMethods.filter((method) => method.kind === "redirect").flatMap(
|
|
5298
5313
|
(method) => method.instances.filter((instance) => instance.autoStart).map((instance) => ({ method, instance }))
|
|
@@ -5922,12 +5937,12 @@ var FullScreenAppWrapper = ({
|
|
|
5922
5937
|
children
|
|
5923
5938
|
}) => {
|
|
5924
5939
|
const { t: t2 } = useTranslation12();
|
|
5925
|
-
const [hostConfig, setHostConfig] =
|
|
5926
|
-
const [loading, setLoading] =
|
|
5927
|
-
const [isAuthenticated, setIsAuthenticated] =
|
|
5928
|
-
const [authLoading, setAuthLoading] =
|
|
5929
|
-
const [, setIsAdmin] =
|
|
5930
|
-
|
|
5940
|
+
const [hostConfig, setHostConfig] = useState13(null);
|
|
5941
|
+
const [loading, setLoading] = useState13(true);
|
|
5942
|
+
const [isAuthenticated, setIsAuthenticated] = useState13(false);
|
|
5943
|
+
const [authLoading, setAuthLoading] = useState13(true);
|
|
5944
|
+
const [, setIsAdmin] = useState13(false);
|
|
5945
|
+
useEffect10(() => {
|
|
5931
5946
|
const handleSessionExpired = () => {
|
|
5932
5947
|
setIsAuthenticated(false);
|
|
5933
5948
|
setIsAdmin(false);
|
|
@@ -5936,7 +5951,7 @@ var FullScreenAppWrapper = ({
|
|
|
5936
5951
|
dbHealthMonitor.on("session-expired", handleSessionExpired);
|
|
5937
5952
|
return () => dbHealthMonitor.off("session-expired", handleSessionExpired);
|
|
5938
5953
|
}, []);
|
|
5939
|
-
|
|
5954
|
+
useEffect10(() => {
|
|
5940
5955
|
let cancelled = false;
|
|
5941
5956
|
let retryTimer = null;
|
|
5942
5957
|
const maxAttempts = 10;
|
|
@@ -5993,7 +6008,7 @@ var FullScreenAppWrapper = ({
|
|
|
5993
6008
|
if (retryTimer) clearTimeout(retryTimer);
|
|
5994
6009
|
};
|
|
5995
6010
|
}, []);
|
|
5996
|
-
|
|
6011
|
+
useEffect10(() => {
|
|
5997
6012
|
const fetchHost = async () => {
|
|
5998
6013
|
if (!hostId || !isAuthenticated) {
|
|
5999
6014
|
setLoading(false);
|
|
@@ -6056,7 +6071,7 @@ var FullScreenAppWrapper = ({
|
|
|
6056
6071
|
};
|
|
6057
6072
|
|
|
6058
6073
|
// src/ui/ssh/dialogs/TOTPDialog.tsx
|
|
6059
|
-
import { useEffect as
|
|
6074
|
+
import { useEffect as useEffect11, useState as useState14 } from "react";
|
|
6060
6075
|
import { useTranslation as useTranslation13 } from "react-i18next";
|
|
6061
6076
|
import { jsx as jsx43, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
6062
6077
|
var CODE_INPUT = "rounded-none bg-muted/50 border-border text-center text-sm tracking-widest";
|
|
@@ -6070,13 +6085,14 @@ function TOTPDialog({
|
|
|
6070
6085
|
backgroundColor
|
|
6071
6086
|
}) {
|
|
6072
6087
|
const { t: t2 } = useTranslation13();
|
|
6073
|
-
const [pushSubmitted, setPushSubmitted] =
|
|
6074
|
-
|
|
6088
|
+
const [pushSubmitted, setPushSubmitted] = useState14(false);
|
|
6089
|
+
useEffect11(() => {
|
|
6075
6090
|
if (isOpen && !waiting) {
|
|
6076
6091
|
setPushSubmitted(false);
|
|
6077
6092
|
}
|
|
6078
6093
|
}, [isOpen, waiting]);
|
|
6079
6094
|
if (!isOpen) return null;
|
|
6095
|
+
const isPassword = mode === "password";
|
|
6080
6096
|
const isPush = mode === "push";
|
|
6081
6097
|
const isMenu = mode === "menu";
|
|
6082
6098
|
const isTotp = !isPush && !isMenu && mode !== "password";
|
|
@@ -6092,11 +6108,10 @@ function TOTPDialog({
|
|
|
6092
6108
|
const input = e.currentTarget.elements.namedItem(
|
|
6093
6109
|
"totpCode"
|
|
6094
6110
|
);
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
}
|
|
6111
|
+
const value = isPassword ? input?.value : input?.value.trim();
|
|
6112
|
+
if (value) onSubmit(value);
|
|
6098
6113
|
};
|
|
6099
|
-
const title = isPush ? t2("sshAuth.mfaPushRequired") : isMenu ? t2("sshAuth.mfaPromptRequired") : t2("sshAuth.totpRequired");
|
|
6114
|
+
const title = isPush ? t2("sshAuth.mfaPushRequired") : isPassword ? t2("common.password") : isMenu ? t2("sshAuth.mfaPromptRequired") : t2("sshAuth.totpRequired");
|
|
6100
6115
|
const label = prompt || (isTotp ? t2("sshAuth.totpCodeLabel") : void 0);
|
|
6101
6116
|
const cancel = /* @__PURE__ */ jsx43("button", { type: "button", onClick: onCancel, className: PROMPT_BUTTON, children: t2("common.cancel") });
|
|
6102
6117
|
return /* @__PURE__ */ jsx43(
|
|
@@ -6115,7 +6130,18 @@ function TOTPDialog({
|
|
|
6115
6130
|
] }),
|
|
6116
6131
|
/* @__PURE__ */ jsx43("div", { className: "flex justify-end gap-2", children: cancel })
|
|
6117
6132
|
] }) : /* @__PURE__ */ jsxs28("form", { onSubmit: handleSubmit, className: "flex flex-col gap-3", children: [
|
|
6118
|
-
isPush ? null :
|
|
6133
|
+
isPush ? null : isPassword ? /* @__PURE__ */ jsx43(
|
|
6134
|
+
Input,
|
|
6135
|
+
{
|
|
6136
|
+
id: "totpCode",
|
|
6137
|
+
name: "totpCode",
|
|
6138
|
+
type: "password",
|
|
6139
|
+
autoComplete: "current-password",
|
|
6140
|
+
autoFocus: true,
|
|
6141
|
+
placeholder: t2("placeholders.enterPassword"),
|
|
6142
|
+
className: "rounded-none bg-muted/50 border-border text-sm"
|
|
6143
|
+
}
|
|
6144
|
+
) : isMenu ? /* @__PURE__ */ jsx43(
|
|
6119
6145
|
Input,
|
|
6120
6146
|
{
|
|
6121
6147
|
id: "totpCode",
|
|
@@ -6151,7 +6177,7 @@ function TOTPDialog({
|
|
|
6151
6177
|
),
|
|
6152
6178
|
/* @__PURE__ */ jsxs28("div", { className: "flex justify-end gap-2", children: [
|
|
6153
6179
|
cancel,
|
|
6154
|
-
/* @__PURE__ */ jsx43("button", { type: "submit", className: PROMPT_PRIMARY_BUTTON, children: isPush ? t2("sshAuth.mfaSendRequest") : t2("sshAuth.totpVerify") })
|
|
6180
|
+
/* @__PURE__ */ jsx43("button", { type: "submit", className: PROMPT_PRIMARY_BUTTON, children: isPush ? t2("sshAuth.mfaSendRequest") : isPassword ? t2("common.connect") : t2("sshAuth.totpVerify") })
|
|
6155
6181
|
] })
|
|
6156
6182
|
] })
|
|
6157
6183
|
}
|
|
@@ -6159,7 +6185,7 @@ function TOTPDialog({
|
|
|
6159
6185
|
}
|
|
6160
6186
|
|
|
6161
6187
|
// src/ui/ssh/dialogs/SSHAuthDialog.tsx
|
|
6162
|
-
import { useState as
|
|
6188
|
+
import { useState as useState16 } from "react";
|
|
6163
6189
|
|
|
6164
6190
|
// src/ui/components/tabs.tsx
|
|
6165
6191
|
import { jsx as jsx44 } from "react/jsx-runtime";
|
|
@@ -6257,7 +6283,7 @@ function _objectWithoutPropertiesLoose(r, e) {
|
|
|
6257
6283
|
import React10, { useRef as useRef5, forwardRef as forwardRef3, useImperativeHandle, useCallback as useCallback7 } from "react";
|
|
6258
6284
|
|
|
6259
6285
|
// node_modules/@uiw/react-codemirror/esm/useCodeMirror.js
|
|
6260
|
-
import { useEffect as
|
|
6286
|
+
import { useEffect as useEffect12, useLayoutEffect as useLayoutEffect2, useState as useState15 } from "react";
|
|
6261
6287
|
|
|
6262
6288
|
// node_modules/@marijn/find-cluster-break/src/index.js
|
|
6263
6289
|
var rangeFrom = [];
|
|
@@ -29312,13 +29338,13 @@ var TYPING_TIMOUT = 200;
|
|
|
29312
29338
|
var emptyExtensions = [];
|
|
29313
29339
|
function useCodeMirror(props) {
|
|
29314
29340
|
var value = props.value, selection2 = props.selection, onChange = props.onChange, onStatistics = props.onStatistics, onCreateEditor = props.onCreateEditor, onUpdate = props.onUpdate, _props$extensions = props.extensions, extensions = _props$extensions === void 0 ? emptyExtensions : _props$extensions, autoFocus = props.autoFocus, _props$theme = props.theme, theme2 = _props$theme === void 0 ? "light" : _props$theme, _props$height = props.height, height = _props$height === void 0 ? null : _props$height, _props$minHeight = props.minHeight, minHeight = _props$minHeight === void 0 ? null : _props$minHeight, _props$maxHeight = props.maxHeight, maxHeight = _props$maxHeight === void 0 ? null : _props$maxHeight, _props$width = props.width, width = _props$width === void 0 ? null : _props$width, _props$minWidth = props.minWidth, minWidth = _props$minWidth === void 0 ? null : _props$minWidth, _props$maxWidth = props.maxWidth, maxWidth = _props$maxWidth === void 0 ? null : _props$maxWidth, _props$placeholder = props.placeholder, placeholderStr = _props$placeholder === void 0 ? "" : _props$placeholder, _props$editable = props.editable, editable2 = _props$editable === void 0 ? true : _props$editable, _props$readOnly = props.readOnly, readOnly2 = _props$readOnly === void 0 ? false : _props$readOnly, _props$indentWithTab = props.indentWithTab, defaultIndentWithTab = _props$indentWithTab === void 0 ? true : _props$indentWithTab, _props$basicSetup = props.basicSetup, defaultBasicSetup = _props$basicSetup === void 0 ? true : _props$basicSetup, root = props.root, initialState = props.initialState;
|
|
29315
|
-
var _useState =
|
|
29316
|
-
var _useState2 =
|
|
29317
|
-
var _useState3 =
|
|
29318
|
-
var typingLatch =
|
|
29341
|
+
var _useState = useState15(), container = _useState[0], setContainer = _useState[1];
|
|
29342
|
+
var _useState2 = useState15(), view = _useState2[0], setView = _useState2[1];
|
|
29343
|
+
var _useState3 = useState15(), state = _useState3[0], setState = _useState3[1];
|
|
29344
|
+
var typingLatch = useState15(() => ({
|
|
29319
29345
|
current: null
|
|
29320
29346
|
}))[0];
|
|
29321
|
-
var pendingUpdate =
|
|
29347
|
+
var pendingUpdate = useState15(() => ({
|
|
29322
29348
|
current: null
|
|
29323
29349
|
}))[0];
|
|
29324
29350
|
var defaultThemeOption = getDimensionTheme(height, minHeight, maxHeight, width, minWidth, maxWidth);
|
|
@@ -29384,12 +29410,12 @@ function useCodeMirror(props) {
|
|
|
29384
29410
|
}
|
|
29385
29411
|
};
|
|
29386
29412
|
}, [container, state]);
|
|
29387
|
-
|
|
29413
|
+
useEffect12(() => {
|
|
29388
29414
|
if (props.container) {
|
|
29389
29415
|
setContainer(props.container);
|
|
29390
29416
|
}
|
|
29391
29417
|
}, [props.container]);
|
|
29392
|
-
|
|
29418
|
+
useEffect12(() => () => {
|
|
29393
29419
|
if (view) {
|
|
29394
29420
|
view.destroy();
|
|
29395
29421
|
setView(void 0);
|
|
@@ -29399,19 +29425,19 @@ function useCodeMirror(props) {
|
|
|
29399
29425
|
typingLatch.current = null;
|
|
29400
29426
|
}
|
|
29401
29427
|
}, [view]);
|
|
29402
|
-
|
|
29428
|
+
useEffect12(() => {
|
|
29403
29429
|
if (autoFocus && view) {
|
|
29404
29430
|
view.focus();
|
|
29405
29431
|
}
|
|
29406
29432
|
}, [autoFocus, view]);
|
|
29407
|
-
|
|
29433
|
+
useEffect12(() => {
|
|
29408
29434
|
if (view) {
|
|
29409
29435
|
view.dispatch({
|
|
29410
29436
|
effects: StateEffect.reconfigure.of(getExtensions)
|
|
29411
29437
|
});
|
|
29412
29438
|
}
|
|
29413
29439
|
}, [theme2, extensions, height, minHeight, maxHeight, width, minWidth, maxWidth, placeholderStr, editable2, readOnly2, defaultIndentWithTab, defaultBasicSetup, onChange, onUpdate]);
|
|
29414
|
-
|
|
29440
|
+
useEffect12(() => {
|
|
29415
29441
|
if (value === void 0) {
|
|
29416
29442
|
return;
|
|
29417
29443
|
}
|
|
@@ -29509,11 +29535,11 @@ function SSHAuthDialog({
|
|
|
29509
29535
|
backgroundColor
|
|
29510
29536
|
}) {
|
|
29511
29537
|
const { t: t2 } = useTranslation14();
|
|
29512
|
-
const [authTab, setAuthTab] =
|
|
29513
|
-
const [password, setPassword] =
|
|
29514
|
-
const [sshKey, setSshKey] =
|
|
29515
|
-
const [keyPassword, setKeyPassword] =
|
|
29516
|
-
const [loading, setLoading] =
|
|
29538
|
+
const [authTab, setAuthTab] = useState16("password");
|
|
29539
|
+
const [password, setPassword] = useState16("");
|
|
29540
|
+
const [sshKey, setSshKey] = useState16("");
|
|
29541
|
+
const [keyPassword, setKeyPassword] = useState16("");
|
|
29542
|
+
const [loading, setLoading] = useState16(false);
|
|
29517
29543
|
if (!isOpen) return null;
|
|
29518
29544
|
const hostDisplay = hostInfo.name ? `${hostInfo.name} (${hostInfo.username}@${hostInfo.ip}:${hostInfo.port})` : `${hostInfo.username}@${hostInfo.ip}:${hostInfo.port}`;
|
|
29519
29545
|
const getReasonMessage = () => {
|
|
@@ -29774,7 +29800,7 @@ function SSHAuthDialog({
|
|
|
29774
29800
|
}
|
|
29775
29801
|
|
|
29776
29802
|
// src/ui/ssh/dialogs/BrowserSignInDialog.tsx
|
|
29777
|
-
import { useState as
|
|
29803
|
+
import { useState as useState17 } from "react";
|
|
29778
29804
|
import { useTranslation as useTranslation15 } from "react-i18next";
|
|
29779
29805
|
import { toast as toast3 } from "sonner";
|
|
29780
29806
|
import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
@@ -29789,7 +29815,7 @@ function BrowserSignInDialog({
|
|
|
29789
29815
|
backgroundColor
|
|
29790
29816
|
}) {
|
|
29791
29817
|
const { t: t2 } = useTranslation15();
|
|
29792
|
-
const [copied, setCopied] =
|
|
29818
|
+
const [copied, setCopied] = useState17(false);
|
|
29793
29819
|
if (!isOpen) return null;
|
|
29794
29820
|
const handleCopyUrl = async () => {
|
|
29795
29821
|
const ok = await copyToClipboard(url);
|
|
@@ -29966,7 +29992,7 @@ function ManagerSearch({
|
|
|
29966
29992
|
}
|
|
29967
29993
|
|
|
29968
29994
|
// src/ui/hooks/use-adaptive-polling.ts
|
|
29969
|
-
import { useEffect as
|
|
29995
|
+
import { useEffect as useEffect13, useRef as useRef6 } from "react";
|
|
29970
29996
|
function useAdaptivePolling(poll, policy, enabled = true, options = {}) {
|
|
29971
29997
|
const pollRef = useRef6(poll);
|
|
29972
29998
|
const errorRef = useRef6(options.onError);
|
|
@@ -29980,7 +30006,7 @@ function useAdaptivePolling(poll, policy, enabled = true, options = {}) {
|
|
|
29980
30006
|
maxRequestDutyCycle
|
|
29981
30007
|
} = policy;
|
|
29982
30008
|
const runImmediately = options.runImmediately;
|
|
29983
|
-
|
|
30009
|
+
useEffect13(
|
|
29984
30010
|
() => runAdaptivePolling(
|
|
29985
30011
|
() => pollRef.current(),
|
|
29986
30012
|
{
|
|
@@ -30137,7 +30163,7 @@ function PassphraseDialog({
|
|
|
30137
30163
|
}
|
|
30138
30164
|
|
|
30139
30165
|
// src/ui/ssh/dialogs/HostKeyVerificationDialog.tsx
|
|
30140
|
-
import { useState as
|
|
30166
|
+
import { useState as useState18 } from "react";
|
|
30141
30167
|
import { useTranslation as useTranslation18 } from "react-i18next";
|
|
30142
30168
|
import { Fragment as Fragment7, jsx as jsx51, jsxs as jsxs35 } from "react/jsx-runtime";
|
|
30143
30169
|
var formatFingerprint = (fp) => fp.match(/.{1,2}/g)?.join(":") || fp;
|
|
@@ -30180,8 +30206,8 @@ function HostKeyVerificationDialog({
|
|
|
30180
30206
|
backgroundColor
|
|
30181
30207
|
}) {
|
|
30182
30208
|
const { t: t2 } = useTranslation18();
|
|
30183
|
-
const [copiedFingerprint, setCopiedFingerprint] =
|
|
30184
|
-
const [copiedOldFingerprint, setCopiedOldFingerprint] =
|
|
30209
|
+
const [copiedFingerprint, setCopiedFingerprint] = useState18(false);
|
|
30210
|
+
const [copiedOldFingerprint, setCopiedOldFingerprint] = useState18(false);
|
|
30185
30211
|
if (!isOpen) return null;
|
|
30186
30212
|
const copyFingerprint = (text, isOld = false) => {
|
|
30187
30213
|
copyToClipboard(text);
|
|
@@ -30762,7 +30788,7 @@ function createQuickConnectHost(input) {
|
|
|
30762
30788
|
}
|
|
30763
30789
|
|
|
30764
30790
|
// src/ui/components/list-kit.tsx
|
|
30765
|
-
import { useState as
|
|
30791
|
+
import { useState as useState19 } from "react";
|
|
30766
30792
|
import { useTranslation as useTranslation19 } from "react-i18next";
|
|
30767
30793
|
import { jsx as jsx53, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
30768
30794
|
var STRIPE = {
|
|
@@ -30928,7 +30954,7 @@ function ListRowFolder({
|
|
|
30928
30954
|
className,
|
|
30929
30955
|
children
|
|
30930
30956
|
}) {
|
|
30931
|
-
const [dragOver, setDragOver] =
|
|
30957
|
+
const [dragOver, setDragOver] = useState19(false);
|
|
30932
30958
|
const empty2 = count === 0;
|
|
30933
30959
|
return /* @__PURE__ */ jsxs36("div", { className: cn("border-b border-border/40", className), children: [
|
|
30934
30960
|
/* @__PURE__ */ jsxs36(
|
|
@@ -31195,7 +31221,7 @@ import {
|
|
|
31195
31221
|
useLayoutEffect as useLayoutEffect3,
|
|
31196
31222
|
useMemo as useMemo7,
|
|
31197
31223
|
useRef as useRef7,
|
|
31198
|
-
useState as
|
|
31224
|
+
useState as useState20
|
|
31199
31225
|
} from "react";
|
|
31200
31226
|
import { jsx as jsx55 } from "react/jsx-runtime";
|
|
31201
31227
|
var COLUMN_STEPS = {
|
|
@@ -31226,7 +31252,7 @@ function CardMasonry({
|
|
|
31226
31252
|
children
|
|
31227
31253
|
}) {
|
|
31228
31254
|
const ref = useRef7(null);
|
|
31229
|
-
const [width, setWidth] =
|
|
31255
|
+
const [width, setWidth] = useState20(0);
|
|
31230
31256
|
useLayoutEffect3(() => {
|
|
31231
31257
|
const el = ref.current;
|
|
31232
31258
|
if (!el) return;
|
|
@@ -31315,7 +31341,7 @@ function UsagePair({
|
|
|
31315
31341
|
}
|
|
31316
31342
|
|
|
31317
31343
|
// src/ui/components/form-fields.tsx
|
|
31318
|
-
import { useState as
|
|
31344
|
+
import { useState as useState21 } from "react";
|
|
31319
31345
|
import { useTranslation as useTranslation20 } from "react-i18next";
|
|
31320
31346
|
import { jsx as jsx57, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
31321
31347
|
function Field2({
|
|
@@ -31658,7 +31684,7 @@ import {
|
|
|
31658
31684
|
useContext as useContext5,
|
|
31659
31685
|
useMemo as useMemo8,
|
|
31660
31686
|
useRef as useRef8,
|
|
31661
|
-
useState as
|
|
31687
|
+
useState as useState22
|
|
31662
31688
|
} from "react";
|
|
31663
31689
|
import { useConnectionRetry } from "@termix-ssh/plugin-sdk/frontend";
|
|
31664
31690
|
import { jsx as jsx58, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
@@ -31689,11 +31715,11 @@ function Gate({
|
|
|
31689
31715
|
children
|
|
31690
31716
|
}) {
|
|
31691
31717
|
const { addLog, clearLogs } = useConnectionLog();
|
|
31692
|
-
const [error, setError] =
|
|
31693
|
-
const [unavailable, setUnavailable] =
|
|
31718
|
+
const [error, setError] = useState22(null);
|
|
31719
|
+
const [unavailable, setUnavailable] = useState22(
|
|
31694
31720
|
null
|
|
31695
31721
|
);
|
|
31696
|
-
const [dropped, setDropped] =
|
|
31722
|
+
const [dropped, setDropped] = useState22(false);
|
|
31697
31723
|
const maxAttempts = retry?.maxAttempts ?? 8;
|
|
31698
31724
|
const gateRef = useRef8(null);
|
|
31699
31725
|
const write = useCallback8(
|
package/dist/manifest.d.ts
CHANGED
|
@@ -27,6 +27,13 @@ export declare function isApiCompatible(range: string): boolean;
|
|
|
27
27
|
* core (2.9.0-beta.1) counts as that release.
|
|
28
28
|
*/
|
|
29
29
|
export declare function isTermixCompatible(range: string, coreVersion: string | null): boolean;
|
|
30
|
+
export declare const MAX_FEATURES = 20;
|
|
31
|
+
export declare const MAX_FEATURE_LENGTH = 160;
|
|
32
|
+
/**
|
|
33
|
+
* The usable feature lines of a manifest or registry entry. Anything that is
|
|
34
|
+
* not a short non-empty string is dropped, so an index cannot flood the page.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pluginFeatures(value: unknown): string[];
|
|
30
37
|
/**
|
|
31
38
|
* The video id of a YouTube link, or null for anything else. Only the id is
|
|
32
39
|
* ever used, so a manifest can never point the embed at another site.
|
|
@@ -416,6 +423,8 @@ export interface PluginManifest {
|
|
|
416
423
|
icon?: string;
|
|
417
424
|
/** A YouTube link shown at the top of the plugin's page. */
|
|
418
425
|
video?: string;
|
|
426
|
+
/** Short lines listing what the plugin does, shown on its page. */
|
|
427
|
+
features?: string[];
|
|
419
428
|
engine: PluginEngine;
|
|
420
429
|
/** Catalog capability ids. */
|
|
421
430
|
capabilities: string[];
|
package/dist/manifest.js
CHANGED
|
@@ -47,6 +47,22 @@ const YOUTUBE_HOSTS = new Set([
|
|
|
47
47
|
"youtube-nocookie.com",
|
|
48
48
|
"www.youtube-nocookie.com",
|
|
49
49
|
]);
|
|
50
|
+
export const MAX_FEATURES = 20;
|
|
51
|
+
export const MAX_FEATURE_LENGTH = 160;
|
|
52
|
+
/**
|
|
53
|
+
* The usable feature lines of a manifest or registry entry. Anything that is
|
|
54
|
+
* not a short non-empty string is dropped, so an index cannot flood the page.
|
|
55
|
+
*/
|
|
56
|
+
export function pluginFeatures(value) {
|
|
57
|
+
if (!Array.isArray(value))
|
|
58
|
+
return [];
|
|
59
|
+
return value
|
|
60
|
+
.filter((f) => typeof f === "string" &&
|
|
61
|
+
f.trim().length > 0 &&
|
|
62
|
+
f.length <= MAX_FEATURE_LENGTH)
|
|
63
|
+
.map((f) => f.trim())
|
|
64
|
+
.slice(0, MAX_FEATURES);
|
|
65
|
+
}
|
|
50
66
|
/**
|
|
51
67
|
* The video id of a YouTube link, or null for anything else. Only the id is
|
|
52
68
|
* ever used, so a manifest can never point the embed at another site.
|
|
@@ -190,6 +206,7 @@ const ALLOWED_TOP_LEVEL = new Set([
|
|
|
190
206
|
"repository",
|
|
191
207
|
"icon",
|
|
192
208
|
"video",
|
|
209
|
+
"features",
|
|
193
210
|
"dependencies",
|
|
194
211
|
"optionalDependencies",
|
|
195
212
|
"provides",
|
|
@@ -319,6 +336,7 @@ export function validateManifest(manifest) {
|
|
|
319
336
|
if ("video" in m && youtubeVideoId(m.video) === null) {
|
|
320
337
|
errors.push(`Field "video" must be a YouTube link such as https://www.youtube.com/watch?v=..., got: ${JSON.stringify(m.video)}`);
|
|
321
338
|
}
|
|
339
|
+
validateFeatures(m.features, errors);
|
|
322
340
|
for (const field of ["backend", "frontend", "locales"]) {
|
|
323
341
|
if (field in m && requireString(m[field], field, errors)) {
|
|
324
342
|
requireRelativePath(m[field], field, errors);
|
|
@@ -343,6 +361,24 @@ export function validateManifest(manifest) {
|
|
|
343
361
|
validateContributes(m.contributes, typeof m.id === "string" ? m.id : undefined, errors);
|
|
344
362
|
return errors;
|
|
345
363
|
}
|
|
364
|
+
function validateFeatures(features, errors) {
|
|
365
|
+
if (features === undefined)
|
|
366
|
+
return;
|
|
367
|
+
if (!Array.isArray(features)) {
|
|
368
|
+
errors.push('Field "features" must be an array of strings');
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (features.length > MAX_FEATURES) {
|
|
372
|
+
errors.push(`Field "features" may list at most ${MAX_FEATURES} items`);
|
|
373
|
+
}
|
|
374
|
+
features.forEach((feature, i) => {
|
|
375
|
+
if (typeof feature !== "string" ||
|
|
376
|
+
feature.trim().length === 0 ||
|
|
377
|
+
feature.length > MAX_FEATURE_LENGTH) {
|
|
378
|
+
errors.push(`Field "features[${i}]" must be a non-empty string of at most ${MAX_FEATURE_LENGTH} characters`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
346
382
|
function validateAuthor(author, errors) {
|
|
347
383
|
if (author === undefined)
|
|
348
384
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@termix-ssh/plugin-sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "The contract between Termix core and a plugin: types, the capability catalog and the manifest schema.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"node": ">=22"
|
|
15
15
|
},
|
|
16
16
|
"bin": {
|
|
17
|
-
"termix-plugin": "
|
|
17
|
+
"termix-plugin": "cli/index.mjs"
|
|
18
18
|
},
|
|
19
19
|
"exports": {
|
|
20
20
|
"./backend": {
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"schema",
|
|
95
95
|
"tsconfig.plugin.json",
|
|
96
96
|
"README.md",
|
|
97
|
+
"CHANGELOG.md",
|
|
97
98
|
"client.d.ts"
|
|
98
99
|
],
|
|
99
100
|
"scripts": {
|
|
@@ -79,6 +79,12 @@
|
|
|
79
79
|
"pattern": "^https://(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/",
|
|
80
80
|
"description": "A YouTube link shown at the top of the plugin's page."
|
|
81
81
|
},
|
|
82
|
+
"features": {
|
|
83
|
+
"type": "array",
|
|
84
|
+
"maxItems": 20,
|
|
85
|
+
"items": { "type": "string", "minLength": 1, "maxLength": 160 },
|
|
86
|
+
"description": "Short lines listing what the plugin does, shown on its page."
|
|
87
|
+
},
|
|
82
88
|
"engine": {
|
|
83
89
|
"type": "object",
|
|
84
90
|
"additionalProperties": false,
|