dsh-plugin 1.1.2 → 1.2.0
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/client/client.js +4469 -1445
- package/client/client.js.map +1 -1
- package/lib/http/routes.js +438 -22
- package/lib/index.js +1 -1
- package/lib/services/install/install.js +4 -0
- package/lib/services/install/npm-check.js +55 -0
- package/lib/services/{npm-resolve.js → install/npm-resolve.js} +7 -5
- package/lib/services/{preflight.js → install/preflight.js} +15 -4
- package/lib/services/{task-queue.js → install/task-queue.js} +155 -66
- package/lib/services/loader.js +56 -1
- package/lib/services/log.js +138 -0
- package/lib/services/probe.js +157 -0
- package/lib/services/{profile.js → profile/profile.js} +29 -1
- package/lib/services/settings.js +60 -0
- package/lib/types/http/routes.d.ts +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/services/{install-types.d.ts → install/install-types.d.ts} +3 -0
- package/lib/types/services/{install.d.ts → install/install.d.ts} +5 -5
- package/lib/types/services/install/npm-check.d.ts +12 -0
- package/lib/types/services/{npm-resolve.d.ts → install/npm-resolve.d.ts} +2 -1
- package/lib/types/services/{task-queue.d.ts → install/task-queue.d.ts} +5 -1
- package/lib/types/services/loader.d.ts +14 -8
- package/lib/types/services/log.d.ts +49 -0
- package/lib/types/services/probe.d.ts +30 -0
- package/lib/types/services/{pending-restart.d.ts → profile/pending-restart.d.ts} +1 -1
- package/lib/types/services/{profile.d.ts → profile/profile.d.ts} +2 -0
- package/lib/types/services/settings.d.ts +24 -0
- package/package.json +1 -1
- package/src/client/components/PluginHubSection.tsx +341 -120
- package/src/client/components/{CatalogList.tsx → catalog/CatalogList.tsx} +11 -7
- package/src/client/components/{PluginCard.tsx → catalog/PluginCard.tsx} +5 -4
- package/src/client/components/layout/CatalogControls.tsx +80 -0
- package/src/client/components/{CatalogHeader.tsx → layout/CatalogHeader.tsx} +4 -4
- package/src/client/components/{CategoryTabs.tsx → layout/CategoryTabs.tsx} +3 -3
- package/src/client/components/layout/SectionTabs.tsx +60 -0
- package/src/client/components/{AboutModal.tsx → modals/AboutModal.tsx} +7 -7
- package/src/client/components/{HubUpdateModal.tsx → modals/HubUpdateModal.tsx} +5 -5
- package/src/client/components/modals/InstalledDetailModal.tsx +193 -0
- package/src/client/components/modals/LogsModal.tsx +301 -0
- package/src/client/components/modals/LogsPathModal.tsx +161 -0
- package/src/client/components/{NotificationsModal.tsx → modals/NotificationsModal.tsx} +47 -20
- package/src/client/components/{ProgressView.tsx → modals/ProgressView.tsx} +2 -2
- package/src/client/components/{modals.tsx → modals/modals.tsx} +117 -40
- package/src/client/components/{Dropdown.tsx → ui/Dropdown.tsx} +14 -6
- package/src/client/components/ui/Toggle.tsx +28 -0
- package/src/client/components/ui/icons.tsx +343 -0
- package/src/client/components/views/CustomInstallView.tsx +250 -0
- package/src/client/components/views/DiagnosticsView.tsx +172 -0
- package/src/client/components/views/InstalledView.tsx +328 -0
- package/src/client/components/views/LogsView.tsx +150 -0
- package/src/client/components/views/MarketView.tsx +66 -0
- package/src/client/components/views/SettingsView.tsx +273 -0
- package/src/client/data/catalog.ts +38 -0
- package/src/client/data/host.ts +107 -0
- package/src/client/data/hub.ts +41 -0
- package/src/client/hooks/useCatalog.ts +71 -115
- package/src/client/hooks/useIncrementalList.ts +46 -0
- package/src/client/hooks/useLanguage.ts +40 -0
- package/src/client/hooks/useSettings.ts +90 -0
- package/src/client/hooks/useTaskQueue.ts +156 -39
- package/src/client/locales.ts +358 -38
- package/src/client/logic/constants.ts +87 -0
- package/src/client/{lib → logic}/failures.ts +95 -74
- package/src/client/logic/install-command.ts +56 -0
- package/src/client/logic/installed.ts +207 -0
- package/src/client/logic/normalize.ts +52 -0
- package/src/client/logic/urls.ts +125 -0
- package/src/client/styles/CustomInstallView.module.css +191 -0
- package/src/client/styles/DiagnosticsView.module.css +231 -0
- package/src/client/styles/Dropdown.module.css +42 -41
- package/src/client/styles/Header.module.css +100 -132
- package/src/client/styles/InstalledView.module.css +447 -0
- package/src/client/styles/List.module.css +6 -0
- package/src/client/styles/LogsView.module.css +503 -0
- package/src/client/styles/Modal.module.css +352 -70
- package/src/client/styles/SectionTabs.module.css +142 -0
- package/src/client/styles/SettingsView.module.css +232 -0
- package/src/client/styles/Toggle.module.css +60 -0
- package/src/client/styles/tokens.module.css +1 -0
- package/src/client/types.ts +23 -3
- package/src/server/http/routes.ts +414 -22
- package/src/server/index.ts +1 -1
- package/src/server/services/{install-types.ts → install/install-types.ts} +3 -1
- package/src/server/services/{install.ts → install/install.ts} +5 -5
- package/src/server/services/install/npm-check.ts +53 -0
- package/src/server/services/{npm-resolve.ts → install/npm-resolve.ts} +7 -5
- package/src/server/services/{preflight.ts → install/preflight.ts} +16 -4
- package/src/server/services/{task-queue.ts → install/task-queue.ts} +157 -63
- package/src/server/services/loader.ts +55 -1
- package/src/server/services/log.ts +167 -0
- package/src/server/services/probe.ts +157 -0
- package/src/server/services/{pending-restart.ts → profile/pending-restart.ts} +1 -1
- package/src/server/services/{profile.ts → profile/profile.ts} +31 -1
- package/src/server/services/settings.ts +79 -0
- package/lib/services/install.js +0 -4
- package/src/client/components/CatalogControls.tsx +0 -108
- package/src/client/components/icons.tsx +0 -129
- package/src/client/lib/catalog.ts +0 -293
- package/src/client/lib/env.ts +0 -20
- /package/lib/services/{install-types.js → install/install-types.js} +0 -0
- /package/lib/services/{installed-versions.js → profile/installed-versions.js} +0 -0
- /package/lib/services/{pending-restart.js → profile/pending-restart.js} +0 -0
- /package/lib/services/{progress.js → profile/progress.js} +0 -0
- /package/lib/types/services/{preflight.d.ts → install/preflight.d.ts} +0 -0
- /package/lib/types/services/{installed-versions.d.ts → profile/installed-versions.d.ts} +0 -0
- /package/lib/types/services/{progress.d.ts → profile/progress.d.ts} +0 -0
- /package/src/client/{lib → logic}/format.ts +0 -0
- /package/src/client/{lib → logic}/renderMarkdown.ts +0 -0
- /package/src/server/services/{installed-versions.ts → profile/installed-versions.ts} +0 -0
- /package/src/server/services/{progress.ts → profile/progress.ts} +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface HubSettings {
|
|
2
|
+
/** 启动时检查目录插件更新,发现可更新时在通知中心提示(安装不自动进行) */
|
|
3
|
+
checkUpdatesOnStart: boolean;
|
|
4
|
+
/** npm 镜像源 registry 地址(空串 = 官方 https://registry.npmjs.org) */
|
|
5
|
+
npmRegistry: string;
|
|
6
|
+
/** HTTP(S) 代理地址,用于 npm / git / 目录请求(空串 = 直连) */
|
|
7
|
+
proxy: string;
|
|
8
|
+
/** 启用命令行安装 NPM 包(默认开;关掉后输入 npm 包名安装被拦截) */
|
|
9
|
+
enableNpmInstall: boolean;
|
|
10
|
+
/** 启用命令行安装 GitHub 源码(默认开;关掉后输入 GitHub 地址安装被拦截) */
|
|
11
|
+
enableGitInstall: boolean;
|
|
12
|
+
/** 启用命令行粘贴 dsh plugin 命令安装(默认开;关掉后命令输入被禁用) */
|
|
13
|
+
enableDshInstall: boolean;
|
|
14
|
+
/** 日志存放位置覆盖(空串 = 默认 ~/.dsh/profiles/<profile>/hub.log;可填目录或 .log 文件路径) */
|
|
15
|
+
logPath: string;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_SETTINGS: HubSettings;
|
|
18
|
+
export declare function settingsFile(profile: string): string;
|
|
19
|
+
/** 读取当前 profile 的设置;文件缺失/损坏时回退默认值(只读路径绝不抛错)。 */
|
|
20
|
+
export declare function loadSettings(profile: string): HubSettings;
|
|
21
|
+
/** 合并并持久化设置(部分更新),返回合并后的完整设置。 */
|
|
22
|
+
export declare function saveSettings(profile: string, patch: Partial<HubSettings>): HubSettings;
|
|
23
|
+
/** 重置为默认设置:删除设置文件,下次读取全部回退默认值(陈旧/白名单外键一并清除)。 */
|
|
24
|
+
export declare function resetSettings(profile: string): HubSettings;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A community plugin marketplace for DeepSeek Harness, built to the official plugin spec — browse, search and install 4000+ human-curated community plugins without leaving the app. · DeepSeek Harness 社区插件市场(遵循官方开发规范):4000+ 人工精选社区插件,每日更新。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -8,43 +8,41 @@
|
|
|
8
8
|
* rendering is delegated to the small presentational components in this
|
|
9
9
|
* folder; it hosts the dialogs/toast and the section-level copy actions.
|
|
10
10
|
*/
|
|
11
|
-
import { createElement as h, useEffect, useState } from 'react'
|
|
11
|
+
import { createElement as h, useEffect, useRef, useState } from 'react'
|
|
12
12
|
import styles from '../styles/Header.module.css'
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import { getEnv } from '../
|
|
13
|
+
import type { EnvInfo, HubPlugin, SectionProps, ToastState } from '../types.ts'
|
|
14
|
+
import { installCommandOf, repoFromInstallTarget } from '../logic/install-command.ts'
|
|
15
|
+
import { PLUGIN_VERSION } from '../logic/constants.ts'
|
|
16
|
+
import { getEnv, revealInstallFolder } from '../data/host.ts'
|
|
17
17
|
import { useCatalog } from '../hooks/useCatalog.ts'
|
|
18
|
+
import { useLanguage } from '../hooks/useLanguage.ts'
|
|
19
|
+
import { useSettings } from '../hooks/useSettings.ts'
|
|
18
20
|
import { useTaskQueue } from '../hooks/useTaskQueue.ts'
|
|
19
|
-
import { ErrorModal, InstallModal, UninstallModal, Toast } from './modals.tsx'
|
|
20
|
-
import { AboutModal } from './AboutModal.tsx'
|
|
21
|
-
import { HubUpdateModal } from './HubUpdateModal.tsx'
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
21
|
+
import { ErrorModal, InstallModal, RestartConfirmModal, UninstallModal, Toast } from './modals/modals.tsx'
|
|
22
|
+
import { AboutModal } from './modals/AboutModal.tsx'
|
|
23
|
+
import { HubUpdateModal } from './modals/HubUpdateModal.tsx'
|
|
24
|
+
import { InstalledDetailModal } from './modals/InstalledDetailModal.tsx'
|
|
25
|
+
import { NotificationsModal } from './modals/NotificationsModal.tsx'
|
|
26
|
+
import { addFailure, addSuccess, addUpdateNotice, clearNotifications, loadNotifications, removeNotification } from '../logic/failures.ts'
|
|
27
|
+
import type { NotificationRecord } from '../logic/failures.ts'
|
|
28
|
+
import { CatalogHeader } from './layout/CatalogHeader.tsx'
|
|
29
|
+
import { MarketView } from './views/MarketView.tsx'
|
|
30
|
+
import { SectionTabs } from './layout/SectionTabs.tsx'
|
|
31
|
+
import type { SectionView } from './layout/SectionTabs.tsx'
|
|
32
|
+
import { InstalledView } from './views/InstalledView.tsx'
|
|
33
|
+
import { CustomInstallView } from './views/CustomInstallView.tsx'
|
|
34
|
+
import { SettingsView } from './views/SettingsView.tsx'
|
|
35
|
+
import type { InstalledItem } from '../logic/installed.ts'
|
|
36
|
+
import { pluginOfItem } from '../logic/installed.ts'
|
|
29
37
|
|
|
30
38
|
export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
31
|
-
|
|
32
|
-
const [manualLang, setManualLang] = useState<LocaleId | null>(null)
|
|
33
|
-
const lang: LocaleId = manualLang ?? locale.getSnapshot().active
|
|
34
|
-
const langKey: LocaleId = lang === 'en' ? 'en' : 'zh'
|
|
35
|
-
// dsh-plugin.org keeps zh pages under the /zh/ prefix; en is the root.
|
|
36
|
-
const langPath = langPathOf(lang)
|
|
37
|
-
|
|
38
|
-
// 界面语言跟随宿主(系统)locale 自动切换;宿主的 t() 绑定宿主 locale,
|
|
39
|
-
// 这里基于本地字典自建翻译函数,与宿主 locale 保持一致。
|
|
40
|
-
// (settings.section 的导航 label 仍在 apply 里用宿主 t(),跟随宿主语言。)
|
|
41
|
-
const dict: Record<string, string> = langKey === 'en' ? en : zh
|
|
42
|
-
const t = (key: string, params?: Record<string, string | number>): string => {
|
|
43
|
-
const raw = dict[key] ?? key
|
|
44
|
-
return params ? raw.replace(/\{(\w+)\}/g, (_, k) => String(params[k] ?? '')) : raw
|
|
45
|
-
}
|
|
46
|
-
|
|
39
|
+
const { lang, langKey, langPath, t, toggleLang } = useLanguage(locale)
|
|
47
40
|
const catalog = useCatalog(lang)
|
|
41
|
+
/** 设置状态:服务端 hub-settings.json 持久化,本地乐观更新即时生效 */
|
|
42
|
+
const { settings: hubSettings, ready: settingsReady, update: updateSettings, reset: resetSettings } = useSettings()
|
|
43
|
+
|
|
44
|
+
/** 一级导航:插件中心 / 已安装 / 自定义安装 / 设置 */
|
|
45
|
+
const [view, setView] = useState<SectionView>('market')
|
|
48
46
|
|
|
49
47
|
/** 全局反馈 Toast:{id} 用于重复触发时重新走入场动画,kind 决定文案与配色 */
|
|
50
48
|
const [toast, setToast] = useState<ToastState | null>(null)
|
|
@@ -54,12 +52,25 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
54
52
|
const [confirmPlugin, setConfirmPlugin] = useState<HubPlugin | null>(null)
|
|
55
53
|
/** 弹窗动作是否为「更新」:已安装插件点「更新」→ 走同一条 add 命令原位覆盖重装,文案区分安装/更新 */
|
|
56
54
|
const [confirmIsUpdate, setConfirmIsUpdate] = useState(false)
|
|
55
|
+
/** 命令行安装确认弹窗:记录待安装的裸目标(npm 包名 / GitHub 地址,DSH 命令已剥成裸目标)——
|
|
56
|
+
* 与应用商店同一套确认/进度/结果弹窗,提供时 plugin 传 null 走 customTarget 模式 */
|
|
57
|
+
const [confirmCustomTarget, setConfirmCustomTarget] = useState<string | null>(null)
|
|
58
|
+
/** 全局 npm 安装确认弹窗(官方 `npm install -g <pkgs>`):记录原始命令 + 解析出的包列表,
|
|
59
|
+
* 走与应用商店同一套确认/进度/结果弹窗(plugin 传 null,InstallModal 走 globalNpm 模式) */
|
|
60
|
+
const [confirmGlobalNpm, setConfirmGlobalNpm] = useState<{ raw: string; pkgs: string[] } | null>(null)
|
|
57
61
|
/** Hub 自我更新说明弹窗:点「可更新」徽标先展示版本 + Markdown 变更记录,确认后再进安装弹窗 */
|
|
58
62
|
const [showHubUpdate, setShowHubUpdate] = useState(false)
|
|
59
63
|
/** 「关注我们」弹窗:GitHub 图标后按钮点击打开,展示平台介绍 + 用户反馈群二维码(Worker /about 推送) */
|
|
60
64
|
const [showAbout, setShowAbout] = useState(false)
|
|
61
|
-
/**
|
|
65
|
+
/** 卸载确认弹窗:记录待卸载的插件(目录内) */
|
|
62
66
|
const [uninstallPlugin, setUninstallPlugin] = useState<HubPlugin | null>(null)
|
|
67
|
+
/** 卸载确认弹窗:记录待卸载的已安装项(自定义安装无目录数据,按包名直卸) */
|
|
68
|
+
const [uninstallItem, setUninstallItem] = useState<InstalledItem | null>(null)
|
|
69
|
+
/** 已安装详情弹窗:记录当前查看的已安装项(行点击 / 详情按钮打开) */
|
|
70
|
+
const [detailItem, setDetailItem] = useState<InstalledItem | null>(null)
|
|
71
|
+
/** 待重启确认弹窗:已安装列表行内「重启」先弹「立即重启 / 稍后重启」确认(与通知中心一致),
|
|
72
|
+
* 确认后才真正触发宿主重启,避免误触 */
|
|
73
|
+
const [showRestartConfirm, setShowRestartConfirm] = useState(false)
|
|
63
74
|
/** 安装/卸载完成后的结果视图:停留弹窗内,点「完成」关闭 */
|
|
64
75
|
const [installDone, setInstallDone] = useState(false)
|
|
65
76
|
const [uninstallDone, setUninstallDone] = useState(false)
|
|
@@ -71,8 +82,9 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
71
82
|
const [restarting, setRestarting] = useState(false)
|
|
72
83
|
/** 操作失败完整信息 + 所属插件仓库 + 失败类型(决定弹窗标题「安装失败/卸载失败」)+ 实际执行的安装命令 + 尝试过的安装方式(issue 预填用) */
|
|
73
84
|
const [errorMsg, setErrorMsg] = useState<{ message: string; repo: string | null; kind: 'install' | 'uninstall'; command?: string; attempts?: string[] } | null>(null)
|
|
74
|
-
/**
|
|
85
|
+
/** 通知中心记录:localStorage 持久化,每次安装/卸载任务成败都留痕(启动时读回) */
|
|
75
86
|
const [notifications, setNotifications] = useState<NotificationRecord[]>(() => loadNotifications())
|
|
87
|
+
/** 通知中心弹窗:一级导航「设置」tab 后边的铃铛入口按钮打开 */
|
|
76
88
|
const [showNotifications, setShowNotifications] = useState(false)
|
|
77
89
|
/** 宿主机器环境快照:提交 bug 的 issue 正文附带;取不到为 null(链接少环境段,不阻塞) */
|
|
78
90
|
const [env, setEnv] = useState<EnvInfo | null>(null)
|
|
@@ -104,7 +116,10 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
104
116
|
setNotifications(addFailure({ kind, repo: repo ?? '', message, command, attempts }))
|
|
105
117
|
},
|
|
106
118
|
installPlugin: confirmPlugin,
|
|
119
|
+
installCustomTarget: confirmCustomTarget,
|
|
120
|
+
installGlobalTarget: confirmGlobalNpm ? confirmGlobalNpm.pkgs.join(' ') : null,
|
|
107
121
|
uninstallPlugin,
|
|
122
|
+
uninstallName: uninstallItem ? uninstallItem.name : null,
|
|
108
123
|
installedName: catalog.installedName,
|
|
109
124
|
// 待重启项展示信息补齐:从目录按 owner/repo 解析插件简介/版本(找不到返回 null,行内只显示仓库名)
|
|
110
125
|
resolvePending: (repo) => {
|
|
@@ -113,13 +128,36 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
113
128
|
},
|
|
114
129
|
})
|
|
115
130
|
|
|
116
|
-
//
|
|
131
|
+
// 启动更新策略:目录数据与设置都就绪后触发一次 —— 开启「启动时检查更新」→ 发现可更新插件
|
|
132
|
+
// 即写入通知中心(每条一条,点击可去更新),toast 同步提示已入通知。
|
|
133
|
+
// 绝不自动安装:是否更新完全由用户决定。
|
|
134
|
+
// ref 守卫保证只跑一次,后续目录刷新/设置变化不会重复打扰。
|
|
135
|
+
const startupCheckedRef = useRef(false)
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (startupCheckedRef.current) return
|
|
138
|
+
if (!settingsReady || catalog.plugins === null || catalog.failed) return
|
|
139
|
+
startupCheckedRef.current = true
|
|
140
|
+
if (!hubSettings.checkUpdatesOnStart) return
|
|
141
|
+
const updatable = catalog.installedItems.filter((i) => i.hasUpdate && i.plugin)
|
|
142
|
+
if (updatable.length === 0) return
|
|
143
|
+
for (const item of updatable) {
|
|
144
|
+
if (item.repo) addUpdateNotice({ kind: 'update', repo: item.repo, version: item.catalogVersion ?? undefined })
|
|
145
|
+
}
|
|
146
|
+
setNotifications(loadNotifications())
|
|
147
|
+
setToast({ id: Date.now(), kind: 'updates', n: updatable.length })
|
|
148
|
+
}, [settingsReady, hubSettings, catalog.plugins, catalog.failed, catalog.installedItems, queue])
|
|
149
|
+
|
|
150
|
+
// 信任/卸载/更新说明弹窗打开时按 Esc 关闭;任务进入后台队列后可随时关闭(任务继续)
|
|
117
151
|
useEffect(() => {
|
|
118
|
-
if (!confirmPlugin && !uninstallPlugin && !
|
|
152
|
+
if (!confirmPlugin && !confirmCustomTarget && !confirmGlobalNpm && !uninstallPlugin && !uninstallItem && !detailItem && !showHubUpdate && !showAbout && !showNotifications) return
|
|
119
153
|
const onKey = (e: KeyboardEvent) => {
|
|
120
154
|
if (e.key === 'Escape') {
|
|
121
155
|
setConfirmPlugin(null)
|
|
156
|
+
setConfirmCustomTarget(null)
|
|
157
|
+
setConfirmGlobalNpm(null)
|
|
122
158
|
setUninstallPlugin(null)
|
|
159
|
+
setUninstallItem(null)
|
|
160
|
+
setDetailItem(null)
|
|
123
161
|
setUninstallDone(false)
|
|
124
162
|
setShowHubUpdate(false)
|
|
125
163
|
setShowAbout(false)
|
|
@@ -128,7 +166,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
128
166
|
}
|
|
129
167
|
window.addEventListener('keydown', onKey)
|
|
130
168
|
return () => window.removeEventListener('keydown', onKey)
|
|
131
|
-
}, [confirmPlugin, uninstallPlugin,
|
|
169
|
+
}, [confirmPlugin, confirmCustomTarget, uninstallPlugin, uninstallItem, detailItem, showHubUpdate, showAbout, showNotifications])
|
|
132
170
|
|
|
133
171
|
// Toast 统一自动消失
|
|
134
172
|
useEffect(() => {
|
|
@@ -162,6 +200,23 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
162
200
|
}
|
|
163
201
|
}
|
|
164
202
|
|
|
203
|
+
/** 命令行安装目标是否已安装(与服务端 already 判定同口径):GitHub 地址按仓库身份匹配
|
|
204
|
+
* (归一化后比对已装项的 repo/spec),npm 包名按依赖 key 直查。命中 → 弹窗转「更新」语义
|
|
205
|
+
* (mode=update 放行覆盖重装),避免重复点击撞 409 报错。 */
|
|
206
|
+
const customTargetInstalled = (raw: string): boolean => {
|
|
207
|
+
const target = raw.trim()
|
|
208
|
+
if (!target) return false
|
|
209
|
+
const repo = repoFromInstallTarget(target)
|
|
210
|
+
const isRepo = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(repo)
|
|
211
|
+
if (isRepo) {
|
|
212
|
+
const needle = repo.toLowerCase()
|
|
213
|
+
return catalog.installedItems.some((i) =>
|
|
214
|
+
(i.repo !== null && i.repo.toLowerCase() === needle) ||
|
|
215
|
+
repoFromInstallTarget(i.spec).toLowerCase() === needle)
|
|
216
|
+
}
|
|
217
|
+
return catalog.installedItems.some((i) => i.name === target)
|
|
218
|
+
}
|
|
219
|
+
|
|
165
220
|
/** 弹窗动作一:复制安装命令到剪贴板,引导去终端粘贴执行(npm 通道显示包名命令)。 */
|
|
166
221
|
const copyCommand = async (p: HubPlugin) => {
|
|
167
222
|
const repo = p.source?.repo ?? ''
|
|
@@ -174,14 +229,21 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
174
229
|
setConfirmPlugin(null)
|
|
175
230
|
}
|
|
176
231
|
|
|
177
|
-
/** 卸载弹窗动作:复制卸载命令,万一直接卸载失败可去终端手动执行。
|
|
232
|
+
/** 卸载弹窗动作:复制卸载命令,万一直接卸载失败可去终端手动执行。
|
|
233
|
+
* 自定义安装(无目录数据)按已安装项包名直卸,同样给复制通道。 */
|
|
178
234
|
const copyUninstallCommand = async () => {
|
|
179
|
-
const name = uninstallPlugin ? catalog.installedName(uninstallPlugin) : null
|
|
235
|
+
const name = uninstallItem ? uninstallItem.name : (uninstallPlugin ? catalog.installedName(uninstallPlugin) : null)
|
|
180
236
|
if (!name) return
|
|
181
237
|
const ok = await doCopy(`dsh plugin remove ${name}`)
|
|
182
238
|
if (ok) setToast({ id: Date.now(), kind: 'copied' })
|
|
183
239
|
}
|
|
184
240
|
|
|
241
|
+
/** 详情弹窗动作:在系统文件管理器里定位安装目录;服务端失败给 toast 提示。 */
|
|
242
|
+
const revealFolder = async (item: InstalledItem) => {
|
|
243
|
+
const ok = await revealInstallFolder(item.name)
|
|
244
|
+
if (!ok) setToast({ id: Date.now(), kind: 'revealFail' })
|
|
245
|
+
}
|
|
246
|
+
|
|
185
247
|
/** 「立即重启」:POST 同源 /restart,宿主进程自杀重启;随后轮询服务恢复后整页刷新。 */
|
|
186
248
|
const requestRestart = async () => {
|
|
187
249
|
if (restarting) return
|
|
@@ -223,8 +285,8 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
223
285
|
const count = catalog.visible.length
|
|
224
286
|
|
|
225
287
|
// Hub 自身条目(目录数据里的 dshplugin/dsh-plugin-hub):头部「可更新」徽标的更新目标。
|
|
226
|
-
//
|
|
227
|
-
const hubEntry = catalog.
|
|
288
|
+
// useCatalog 在过滤目录(自身不进插件列表)前单独保留该条目;目录可能尚未加载(此时为 null,徽标不渲染)。
|
|
289
|
+
const hubEntry = catalog.hubPlugin
|
|
228
290
|
|
|
229
291
|
return h('div', { className: styles.root },
|
|
230
292
|
h(CatalogHeader, {
|
|
@@ -232,7 +294,7 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
232
294
|
langPath,
|
|
233
295
|
statsTotal,
|
|
234
296
|
statsVerified,
|
|
235
|
-
onToggleLang:
|
|
297
|
+
onToggleLang: toggleLang,
|
|
236
298
|
// Hub 自身版本信息入口:版本号常驻可点(无更新也看得到当前版本更新内容),
|
|
237
299
|
// 有可用更新时版本号后紧跟红色「可更新」徽标(同一入口),点击都打开同一个更新说明弹窗。
|
|
238
300
|
// 有更新弹窗显示新版本 + Markdown 变更记录,点「直接更新」进安装弹窗覆盖重装。
|
|
@@ -240,64 +302,117 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
240
302
|
onVersionClick: () => setShowHubUpdate(true),
|
|
241
303
|
onAboutClick: () => setShowAbout(true),
|
|
242
304
|
}),
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
305
|
+
// 一级导航行:tab 占满左侧,通知入口靠右(「设置」tab 后边,右对齐)
|
|
306
|
+
h('div', { className: styles.tabsRow },
|
|
307
|
+
// 「已安装」徽标 = 目录插件 + 自定义安装总数(installedItems 已排除 hub 自身)
|
|
308
|
+
h(SectionTabs, {
|
|
309
|
+
view,
|
|
310
|
+
setView,
|
|
311
|
+
installedCount: catalog.installedItems.length,
|
|
312
|
+
t,
|
|
313
|
+
// 通知中心入口:红圈徽标 = 记录数 + 进行中任务 + 待重启(有动静才醒目)
|
|
314
|
+
noticeCount: notifications.length + queue.queue.length + queue.pendingRestarts.length,
|
|
315
|
+
onOpenNotifications: () => setShowNotifications(true),
|
|
316
|
+
}),
|
|
317
|
+
),
|
|
318
|
+
view === 'market'
|
|
319
|
+
? h(MarketView, {
|
|
320
|
+
catalog,
|
|
321
|
+
t,
|
|
322
|
+
langPath,
|
|
323
|
+
langKey,
|
|
324
|
+
copied,
|
|
325
|
+
// 列表头部「筛选出 N 条」:分类/搜索/安装状态筛选后的结果数;全部视图且未筛选时显示总数
|
|
326
|
+
resultText: catalog.plugins === null || catalog.failed
|
|
327
|
+
? null
|
|
328
|
+
: catalog.category === 'all' && count === total
|
|
329
|
+
? t('pluginsTotal', { n: count })
|
|
330
|
+
: t('filterResults', { n: count }),
|
|
331
|
+
onInstall: (p, opts) => {
|
|
332
|
+
setInstallDone(false)
|
|
333
|
+
setConfirmIsUpdate(opts?.update ?? false)
|
|
334
|
+
// 与卸载一致:重置弹窗关联任务,避免新弹窗误匹配到进行中的任务而禁用安装按钮
|
|
335
|
+
queue.clearModalTask()
|
|
336
|
+
setConfirmPlugin(p)
|
|
337
|
+
},
|
|
338
|
+
onUninstall: (p) => {
|
|
339
|
+
setUninstallDone(false)
|
|
340
|
+
queue.clearModalTask()
|
|
341
|
+
setUninstallPlugin(p)
|
|
342
|
+
},
|
|
343
|
+
})
|
|
344
|
+
: view === 'installed'
|
|
345
|
+
? h(InstalledView, {
|
|
346
|
+
items: catalog.installedItems,
|
|
347
|
+
langKey,
|
|
348
|
+
t,
|
|
349
|
+
// 支持系统文件管理器定位的平台(macOS / Linux)上,行内「打开详情」替换为
|
|
350
|
+
// 打开安装目录按钮(行点击本身即打开详情弹窗);Windows 不支持则按钮不显示
|
|
351
|
+
platform: env?.platform ?? '',
|
|
352
|
+
// 行点击 / 详情按钮:打开已安装详情弹窗(完整元数据 + 运行时信息)
|
|
353
|
+
onOpenDetail: (item) => setDetailItem(item),
|
|
354
|
+
onReveal: (item) => { void revealFolder(item) },
|
|
355
|
+
// 更新:已安装视图的「更新」= 目录插件覆盖重装,与市场列表内更新同一流程
|
|
356
|
+
onUpdate: (item) => {
|
|
357
|
+
if (!item.plugin) return
|
|
358
|
+
setDetailItem(null)
|
|
359
|
+
setInstallDone(false)
|
|
360
|
+
setConfirmIsUpdate(true)
|
|
361
|
+
queue.clearModalTask()
|
|
362
|
+
setConfirmPlugin(item.plugin)
|
|
363
|
+
},
|
|
364
|
+
// 卸载:目录插件与自定义安装统一按已安装项走卸载确认弹窗(自定义无目录数据,按包名直卸)
|
|
365
|
+
onUninstall: (item) => {
|
|
366
|
+
setDetailItem(null)
|
|
367
|
+
setUninstallDone(false)
|
|
368
|
+
queue.clearModalTask()
|
|
369
|
+
setUninstallItem(item)
|
|
370
|
+
},
|
|
371
|
+
// 重启:待重启条目行内按钮 → 先弹「立即重启 / 稍后重启」确认(与通知中心一致),确认后才触发宿主重启
|
|
372
|
+
onRestart: () => setShowRestartConfirm(true),
|
|
373
|
+
})
|
|
374
|
+
: view === 'custom'
|
|
375
|
+
? h(CustomInstallView, {
|
|
376
|
+
t,
|
|
377
|
+
// 命令行安装:与应用商店同一套确认/进度/结果弹窗 —— 输入 npm 包名 / GitHub 地址 /
|
|
378
|
+
// dsh plugin 命令即装(custom 源,受安全设置三开关控制)。点安装先弹确认窗,
|
|
379
|
+
// 确认后走队列安装(实时进度 + 成功结果视图);目标已安装 → 弹窗转「更新」覆盖重装,
|
|
380
|
+
// 不再撞「already installed」报错(第一次安装没有反馈才导致用户重复点击)。
|
|
381
|
+
// 安全信任开关关掉对应通道 → 卡片禁用并提示去设置打开(onOpenSettings 跳到设置页)。
|
|
382
|
+
enableNpm: hubSettings.enableNpmInstall,
|
|
383
|
+
enableGit: hubSettings.enableGitInstall,
|
|
384
|
+
enableDsh: hubSettings.enableDshInstall,
|
|
385
|
+
onOpenSettings: () => setView('settings'),
|
|
386
|
+
onInstallCustom: (raw, opts) => {
|
|
387
|
+
const target = raw.trim()
|
|
388
|
+
if (!target) return
|
|
389
|
+
setInstallDone(false)
|
|
390
|
+
// 与目录安装一致:重置弹窗关联任务,避免新弹窗误匹配到进行中的任务
|
|
391
|
+
queue.clearModalTask()
|
|
392
|
+
// 官方全局 npm 安装(npm install -g <pkgs>):弹窗走 globalNpm 模式(系统级 CLI 工具,
|
|
393
|
+
// 不进插件列表,不做「已安装→更新」判定 —— npm install -g 对已装包是原位覆盖更新,天然幂等)
|
|
394
|
+
if (opts?.globalNpm && opts.globalNpm.length > 0) {
|
|
395
|
+
setConfirmCustomTarget(null)
|
|
396
|
+
setConfirmIsUpdate(false)
|
|
397
|
+
setConfirmGlobalNpm({ raw, pkgs: opts.globalNpm })
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
setConfirmGlobalNpm(null)
|
|
401
|
+
setConfirmIsUpdate(customTargetInstalled(target))
|
|
402
|
+
setConfirmCustomTarget(target)
|
|
403
|
+
},
|
|
404
|
+
})
|
|
405
|
+
: h(SettingsView, {
|
|
406
|
+
t,
|
|
407
|
+
settings: hubSettings,
|
|
408
|
+
update: updateSettings,
|
|
409
|
+
reset: resetSettings,
|
|
410
|
+
env,
|
|
411
|
+
onCopy: (text: string) => {
|
|
412
|
+
doCopy(text)
|
|
413
|
+
setToast({ id: Date.now(), kind: 'copied' })
|
|
414
|
+
},
|
|
415
|
+
}),
|
|
301
416
|
// Hub 版本信息弹窗:版本号/徽标点击打开 —— 有更新显示新版本 + 变更记录 + 「直接更新」,
|
|
302
417
|
// 无更新显示当前版本 + 记录 + 「已是最新」。Worker 拉取失败时兜底展示当前版本号。
|
|
303
418
|
showHubUpdate && h(HubUpdateModal, {
|
|
@@ -326,6 +441,35 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
326
441
|
t,
|
|
327
442
|
onClose: () => setShowAbout(false),
|
|
328
443
|
}),
|
|
444
|
+
// 安装/卸载任务通知中心:本地持久化的成败清单,随时可回来查看/复制/清空;
|
|
445
|
+
// 进行中的任务(实时进度)与待重启插件也一并展示,关掉任务弹窗后仍可盯着
|
|
446
|
+
showNotifications && h(NotificationsModal, {
|
|
447
|
+
records: notifications,
|
|
448
|
+
tasks: queue.queue,
|
|
449
|
+
pendingRestarts: queue.pendingRestarts,
|
|
450
|
+
t,
|
|
451
|
+
env,
|
|
452
|
+
onClose: () => setShowNotifications(false),
|
|
453
|
+
onCopy: (text) => {
|
|
454
|
+
doCopy(text)
|
|
455
|
+
setToast({ id: Date.now(), kind: 'errCopied' })
|
|
456
|
+
},
|
|
457
|
+
onClear: () => setNotifications(clearNotifications()),
|
|
458
|
+
onRemove: (id: number) => setNotifications(removeNotification(id)),
|
|
459
|
+
// 更新提醒通知:点击直接进该插件的更新确认弹窗(与已安装列表「更新」同一流程)
|
|
460
|
+
onUpdate: (repo: string) => {
|
|
461
|
+
const plugin = catalog.installedItems.find((i) => i.repo === repo)?.plugin
|
|
462
|
+
if (!plugin) return
|
|
463
|
+
setShowNotifications(false)
|
|
464
|
+
setInstallDone(false)
|
|
465
|
+
setConfirmIsUpdate(true)
|
|
466
|
+
queue.clearModalTask()
|
|
467
|
+
setConfirmPlugin(plugin)
|
|
468
|
+
},
|
|
469
|
+
cancelTask: queue.cancelTask,
|
|
470
|
+
restarting,
|
|
471
|
+
onRestart: () => { void requestRestart() },
|
|
472
|
+
}),
|
|
329
473
|
// 安装确认弹窗:锁定/进度/结果视图逻辑收敛在 modals.tsx;更新与安装同入口,仅标记 update
|
|
330
474
|
confirmPlugin && h(InstallModal, {
|
|
331
475
|
plugin: confirmPlugin,
|
|
@@ -344,9 +488,61 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
344
488
|
onInstall: () => queue.installNow(confirmPlugin, confirmIsUpdate ? { update: true } : undefined),
|
|
345
489
|
onRestart: () => { void requestRestart() },
|
|
346
490
|
}),
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
491
|
+
// 命令行安装确认弹窗:与目录安装同一套确认/进度/结果流程(plugin 传 null 走 customTarget 模式)。
|
|
492
|
+
// 已安装目标 → update 标记,确认后走覆盖重装(服务端 mode=update 放行),不再 409 报错。
|
|
493
|
+
confirmCustomTarget && h(InstallModal, {
|
|
494
|
+
plugin: null,
|
|
495
|
+
customTarget: confirmCustomTarget,
|
|
496
|
+
done: installDone,
|
|
497
|
+
task: queue.installModalTask,
|
|
498
|
+
t,
|
|
499
|
+
langPath,
|
|
500
|
+
restarting,
|
|
501
|
+
update: confirmIsUpdate,
|
|
502
|
+
cliOnly: false,
|
|
503
|
+
submitting: queue.submitting,
|
|
504
|
+
needsRestart: installNeedsRestart,
|
|
505
|
+
onClose: () => {
|
|
506
|
+
setInstallDone(false)
|
|
507
|
+
setConfirmCustomTarget(null)
|
|
508
|
+
},
|
|
509
|
+
onCopy: () => {
|
|
510
|
+
doCopy(`pnpm add ${confirmCustomTarget}`)
|
|
511
|
+
setToast({ id: Date.now(), kind: 'copied' })
|
|
512
|
+
},
|
|
513
|
+
onInstall: () => void queue.installCustom(confirmCustomTarget, confirmIsUpdate ? { update: true } : undefined),
|
|
514
|
+
onRestart: () => { void requestRestart() },
|
|
515
|
+
}),
|
|
516
|
+
// 全局 npm 安装确认弹窗(官方 `npm install -g <pkgs>`):与应用商店同一套确认/进度/结果流程。
|
|
517
|
+
// 装的是系统级 CLI 工具(如 @deepseek-ai/dsh),不进任何 profile、无需宿主重启;
|
|
518
|
+
// 服务端任务终态 needsRestart=false → 结果视图仅「完成」。
|
|
519
|
+
confirmGlobalNpm && h(InstallModal, {
|
|
520
|
+
plugin: null,
|
|
521
|
+
globalNpm: confirmGlobalNpm.pkgs,
|
|
522
|
+
done: installDone,
|
|
523
|
+
task: queue.installModalTask,
|
|
524
|
+
t,
|
|
525
|
+
langPath,
|
|
526
|
+
restarting,
|
|
527
|
+
update: false,
|
|
528
|
+
cliOnly: false,
|
|
529
|
+
submitting: queue.submitting,
|
|
530
|
+
needsRestart: installNeedsRestart,
|
|
531
|
+
onClose: () => {
|
|
532
|
+
setInstallDone(false)
|
|
533
|
+
setConfirmGlobalNpm(null)
|
|
534
|
+
},
|
|
535
|
+
onCopy: () => {
|
|
536
|
+
doCopy(`npm install -g ${confirmGlobalNpm.pkgs.join(' ')}`)
|
|
537
|
+
setToast({ id: Date.now(), kind: 'copied' })
|
|
538
|
+
},
|
|
539
|
+
onInstall: () => void queue.installGlobalNpm(confirmGlobalNpm.pkgs),
|
|
540
|
+
onRestart: () => { void requestRestart() },
|
|
541
|
+
}),
|
|
542
|
+
// 卸载确认弹窗:确认/进度/结果视图逻辑收敛在 modals.tsx。
|
|
543
|
+
// 已安装视图的自定义安装(目录外)没有完整目录数据,按已安装项伪插件适配后走同一弹窗
|
|
544
|
+
(uninstallPlugin || uninstallItem) && h(UninstallModal, {
|
|
545
|
+
plugin: uninstallPlugin ?? pluginOfItem(uninstallItem!),
|
|
350
546
|
done: uninstallDone,
|
|
351
547
|
task: queue.uninstallModalTask,
|
|
352
548
|
t,
|
|
@@ -357,12 +553,46 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
357
553
|
onClose: () => {
|
|
358
554
|
setUninstallDone(false)
|
|
359
555
|
setUninstallPlugin(null)
|
|
556
|
+
setUninstallItem(null)
|
|
557
|
+
},
|
|
558
|
+
onCancel: () => {
|
|
559
|
+
setUninstallPlugin(null)
|
|
560
|
+
setUninstallItem(null)
|
|
360
561
|
},
|
|
361
|
-
onCancel: () => setUninstallPlugin(null),
|
|
362
562
|
onCopyCommand: copyUninstallCommand,
|
|
363
|
-
onConfirm: () =>
|
|
563
|
+
onConfirm: () => {
|
|
564
|
+
if (uninstallItem) void queue.uninstallItem(uninstallItem)
|
|
565
|
+
else if (uninstallPlugin) void queue.uninstallNow(uninstallPlugin)
|
|
566
|
+
},
|
|
364
567
|
onRestart: () => { void requestRestart() },
|
|
365
568
|
}),
|
|
569
|
+
// 已安装详情弹窗:目录元数据 + 宿主运行时信息的完整展示,操作(更新/卸载/复制路径)由此发起
|
|
570
|
+
detailItem && h(InstalledDetailModal, {
|
|
571
|
+
item: detailItem,
|
|
572
|
+
t,
|
|
573
|
+
lang: langKey,
|
|
574
|
+
langPath,
|
|
575
|
+
onClose: () => setDetailItem(null),
|
|
576
|
+
onUpdate: (item) => {
|
|
577
|
+
if (!item.plugin) return
|
|
578
|
+
setDetailItem(null)
|
|
579
|
+
setInstallDone(false)
|
|
580
|
+
setConfirmIsUpdate(true)
|
|
581
|
+
queue.clearModalTask()
|
|
582
|
+
setConfirmPlugin(item.plugin)
|
|
583
|
+
},
|
|
584
|
+
onUninstall: (item) => {
|
|
585
|
+
setDetailItem(null)
|
|
586
|
+
setUninstallDone(false)
|
|
587
|
+
queue.clearModalTask()
|
|
588
|
+
setUninstallItem(item)
|
|
589
|
+
},
|
|
590
|
+
onCopyPath: (path) => {
|
|
591
|
+
doCopy(path)
|
|
592
|
+
setToast({ id: Date.now(), kind: 'copied' })
|
|
593
|
+
},
|
|
594
|
+
onReveal: (item) => { void revealFolder(item) },
|
|
595
|
+
}),
|
|
366
596
|
toast && h(Toast, { toast, t }),
|
|
367
597
|
// 操作失败:完整错误弹窗(重复安装、CLI 失败、请求错误等),可复制并去插件仓库反馈
|
|
368
598
|
errorMsg && h(ErrorModal, {
|
|
@@ -379,24 +609,15 @@ export function PluginHubSection({ t: _hostT, locale }: SectionProps) {
|
|
|
379
609
|
},
|
|
380
610
|
onClose: () => setErrorMsg(null),
|
|
381
611
|
}),
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
showNotifications && h(NotificationsModal, {
|
|
385
|
-
records: notifications,
|
|
386
|
-
tasks: queue.queue,
|
|
387
|
-
pendingRestarts: queue.pendingRestarts,
|
|
612
|
+
// 待重启确认弹窗:已安装列表行内「重启」按钮点击后弹出,稍后重启 / 立即重启
|
|
613
|
+
showRestartConfirm && h(RestartConfirmModal, {
|
|
388
614
|
t,
|
|
389
|
-
env,
|
|
390
|
-
onClose: () => setShowNotifications(false),
|
|
391
|
-
onCopy: (text) => {
|
|
392
|
-
doCopy(text)
|
|
393
|
-
setToast({ id: Date.now(), kind: 'errCopied' })
|
|
394
|
-
},
|
|
395
|
-
onClear: () => setNotifications(clearNotifications()),
|
|
396
|
-
onRemove: (id: number) => setNotifications(removeNotification(id)),
|
|
397
|
-
cancelTask: queue.cancelTask,
|
|
398
615
|
restarting,
|
|
399
|
-
|
|
616
|
+
onClose: () => setShowRestartConfirm(false),
|
|
617
|
+
onRestartNow: () => {
|
|
618
|
+
setShowRestartConfirm(false)
|
|
619
|
+
void requestRestart()
|
|
620
|
+
},
|
|
400
621
|
}),
|
|
401
622
|
)
|
|
402
623
|
}
|