@modesto_ma/dsh-channels 0.5.0-beta.3 → 0.5.0-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -27
- package/docs/attachment-pipeline-v2.md +1 -1
- package/docs/feishu-onboarding.md +3 -3
- package/lib/client.js +1427 -1338
- package/lib/client.js.map +1 -1
- package/lib/index.js +7958 -6710
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/access/controller.ts +32 -1
- package/src/analyze/contract.ts +121 -0
- package/src/analyze/launcher.ts +353 -0
- package/src/analyze/server.ts +201 -0
- package/src/cards/analyze.ts +116 -0
- package/src/cards/index.ts +1 -0
- package/src/cards/streaming.ts +58 -0
- package/src/client/api.ts +15 -0
- package/src/client/number-field.ts +97 -0
- package/src/client/panel/access-tab.tsx +61 -20
- package/src/client/panel/advanced-tab.tsx +59 -68
- package/src/client/panel/connection-tab.tsx +147 -164
- package/src/client/panel/credentials-card.tsx +124 -87
- package/src/client/panel/fields-tab.tsx +27 -0
- package/src/client/panel/index.tsx +126 -55
- package/src/client/panel/shared.tsx +369 -252
- package/src/client/panel/whitelist.tsx +197 -0
- package/src/client/panel/wiki-tab.tsx +48 -0
- package/src/client/styles.ts +108 -91
- package/src/config.ts +30 -4
- package/src/domain.ts +55 -0
- package/src/feishu/scopes.ts +13 -7
- package/src/feishu/tools.ts +3 -1
- package/src/health.ts +12 -0
- package/src/host/agent.ts +5 -1
- package/src/index.ts +2 -2
- package/src/interactions/pending.ts +11 -2
- package/src/routing/inbound.ts +57 -14
- package/src/service.ts +161 -28
- package/src/settings/contract.ts +222 -146
- package/src/settings/routes.ts +66 -14
- package/src/settings/surface.ts +234 -203
- package/src/streamer.ts +156 -7
- package/src/workspaces.ts +31 -0
- package/src/client/panel/access-card.tsx +0 -303
- package/src/client/panel/model-tab.tsx +0 -23
- package/src/client/panel/sessions-tab.tsx +0 -21
- package/src/client/panel/workspace-tab.tsx +0 -24
|
@@ -1,80 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「高级」tab
|
|
3
|
-
*
|
|
2
|
+
* 「高级」tab:日志分析接口(监听地址字段 + 访问令牌凭据行)、SDK 日志级别
|
|
3
|
+
* 与 Web 覆盖文件(「恢复 profile 配置」删除整个覆盖层,不走 draft)。
|
|
4
4
|
*
|
|
5
5
|
* @module dsh-channels/client/panel/advanced-tab
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
8
|
+
import { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
|
|
9
|
+
import { CredentialRow } from './credentials-card.tsx'
|
|
10
|
+
import { FieldSections, Row, Section } from './shared.tsx'
|
|
11
|
+
import type { AnalyzeTokenView, FormView } from './shared.tsx'
|
|
12
12
|
|
|
13
|
-
export function AdvancedTab(props: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
<div className="dsh-channels-cfg__tabBody">
|
|
17
|
-
<TabHelp text={PANEL_COPY.advancedTabHelp} />
|
|
18
|
-
<FieldsCard form={form} groups={TAB_FIELD_GROUPS.advanced} />
|
|
19
|
-
<SaveBar form={form} tab="advanced" />
|
|
20
|
-
<AdvancedCard panel={form.panel} busy={form.busy} locked={form.locked} onClear={onClear} />
|
|
21
|
-
</div>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Read-only deployment facts; the only write action here drops the overlay file. */
|
|
26
|
-
function AdvancedCard(props: {
|
|
27
|
-
panel: PanelWire
|
|
28
|
-
busy: BusyAction | null
|
|
29
|
-
locked: boolean
|
|
13
|
+
export function AdvancedTab(props: {
|
|
14
|
+
form: FormView
|
|
15
|
+
analyzeToken: AnalyzeTokenView
|
|
30
16
|
onClear: () => void
|
|
31
17
|
}): JSX.Element {
|
|
32
|
-
const {
|
|
33
|
-
const {
|
|
18
|
+
const { form, analyzeToken, onClear } = props
|
|
19
|
+
const { panel, busy, locked } = form
|
|
20
|
+
const tokenState = panel.config.credentials.analyzeToken
|
|
34
21
|
const overridden = panel.config.overriddenKeys.length
|
|
35
22
|
return (
|
|
36
|
-
<
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
<span className="dsh-channels-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
23
|
+
<div className="dsh-channels-cfg__tabBody">
|
|
24
|
+
<FieldSections
|
|
25
|
+
form={form}
|
|
26
|
+
groups={TAB_FIELD_GROUPS.advanced}
|
|
27
|
+
extra={{
|
|
28
|
+
analyze: (
|
|
29
|
+
<CredentialRow
|
|
30
|
+
label={PANEL_COPY.analyzeTokenLabel}
|
|
31
|
+
summary={PANEL_COPY.analyzeTokenSummary}
|
|
32
|
+
state={tokenState}
|
|
33
|
+
value={analyzeToken.value}
|
|
34
|
+
secret
|
|
35
|
+
placeholder={PANEL_COPY.analyzeTokenPlaceholder}
|
|
36
|
+
issue={analyzeToken.issues.find(item => item.key === 'analyzeToken')}
|
|
37
|
+
busy={analyzeToken.busy}
|
|
38
|
+
disabled={analyzeToken.busy !== null || analyzeToken.locked || !tokenState.writable}
|
|
39
|
+
onChange={analyzeToken.onChange}
|
|
40
|
+
onSave={analyzeToken.onSave}
|
|
41
|
+
onClear={analyzeToken.onClear}
|
|
42
|
+
/>
|
|
43
|
+
),
|
|
44
|
+
}}
|
|
45
|
+
/>
|
|
46
|
+
<Section title={PANEL_COPY.overlayTitle}>
|
|
47
|
+
<Row
|
|
48
|
+
label={PANEL_COPY.overlayPath}
|
|
49
|
+
summary={<span className="dsh-channels-cfg__mono">{panel.status.overlayPath}</span>}
|
|
50
|
+
details={(
|
|
51
|
+
<>
|
|
52
|
+
{PANEL_COPY.overlayPathHelp}
|
|
53
|
+
{' '}
|
|
54
|
+
{overridden === 0 ? PANEL_COPY.noOverrides : fillCopy(PANEL_COPY.overlayCount, { n: overridden })}
|
|
55
|
+
</>
|
|
56
|
+
)}
|
|
57
|
+
control={(
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--danger"
|
|
61
|
+
onClick={onClear}
|
|
62
|
+
disabled={overridden === 0 || busy !== null || locked}
|
|
63
|
+
>
|
|
64
|
+
{busy === 'clear' ? PANEL_COPY.clearing : PANEL_COPY.clear}
|
|
65
|
+
</button>
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
</Section>
|
|
69
|
+
</div>
|
|
79
70
|
)
|
|
80
71
|
}
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 「连接」tab:按 Host
|
|
2
|
+
* 「连接」tab:按 Host 派生的生命周期阶段渲染。
|
|
3
3
|
*
|
|
4
|
-
* - unconfigured
|
|
5
|
-
* - idle
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - connecting:禁用按钮显示「连接中…」
|
|
9
|
-
* - 已连接且权限自检完成:权限自检卡(缺失权限红字 + 开发者后台深链)
|
|
4
|
+
* - unconfigured:上手三步 + 应用凭据
|
|
5
|
+
* - idle / error / connecting / connected:状态块(当前阶段的唯一操作 + 运行数字)
|
|
6
|
+
* - 连接后:自检(权限缺口可展开、斜杠指令面板同步结果)
|
|
7
|
+
* - 有需重连才生效的已保存配置:顶部「重启连接」横幅
|
|
10
8
|
*
|
|
11
9
|
* @module dsh-channels/client/panel/connection-tab
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
import {
|
|
12
|
+
import { useState } from 'react'
|
|
13
|
+
import { fillCopy, PANEL_COPY } from '../../settings/contract.ts'
|
|
15
14
|
import type { ChannelStatusWire, PanelWire, ScopeCheckWire, SlashCommandSyncWire } from '../../settings/contract.ts'
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { CredentialsSection } from './credentials-card.tsx'
|
|
16
|
+
import { fieldLabel, phaseBadge, phaseSummary, Pill, Row, Section } from './shared.tsx'
|
|
18
17
|
import type { BusyAction, CredentialsView } from './shared.tsx'
|
|
19
18
|
|
|
20
19
|
export interface ConnectionTabProps {
|
|
@@ -27,24 +26,30 @@ export interface ConnectionTabProps {
|
|
|
27
26
|
|
|
28
27
|
export function ConnectionTab(props: ConnectionTabProps): JSX.Element {
|
|
29
28
|
const { panel, status, busy, credentials, onReconnect } = props
|
|
29
|
+
const checks = status.scopeCheck !== undefined || status.slashCommandSync !== undefined
|
|
30
30
|
return (
|
|
31
31
|
<div className="dsh-channels-cfg__tabBody">
|
|
32
|
-
<TabHelp text={PANEL_COPY.connectionTabHelp} />
|
|
33
32
|
{panel.config.restartPendingKeys.length === 0
|
|
34
33
|
? null
|
|
35
34
|
: <RestartBanner panel={panel} busy={busy} locked={status.busy} onReconnect={onReconnect} />}
|
|
36
35
|
{status.phase === 'unconfigured'
|
|
37
|
-
? <
|
|
38
|
-
: <
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
? <OnboardingSection status={status} />
|
|
37
|
+
: <StatusBlock status={status} busy={busy} onReconnect={onReconnect} />}
|
|
38
|
+
{checks
|
|
39
|
+
? (
|
|
40
|
+
<Section title={PANEL_COPY.checksTitle}>
|
|
41
|
+
{status.scopeCheck === undefined ? null : <ScopeCheckRow check={status.scopeCheck} />}
|
|
42
|
+
{status.slashCommandSync === undefined ? null : <SlashCommandSyncRow sync={status.slashCommandSync} />}
|
|
43
|
+
</Section>
|
|
44
|
+
)
|
|
45
|
+
: null}
|
|
46
|
+
<CredentialsSection panel={panel} credentials={credentials} />
|
|
42
47
|
</div>
|
|
43
48
|
)
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/** First-run guide: create the app, fill the credentials, save and wait. */
|
|
47
|
-
function
|
|
52
|
+
function OnboardingSection(props: { status: ChannelStatusWire }): JSX.Element {
|
|
48
53
|
const { status } = props
|
|
49
54
|
const steps = [
|
|
50
55
|
{ title: PANEL_COPY.onboardingStep1Title, text: PANEL_COPY.onboardingStep1Text },
|
|
@@ -52,21 +57,17 @@ function OnboardingCard(props: { status: ChannelStatusWire }): JSX.Element {
|
|
|
52
57
|
{ title: PANEL_COPY.onboardingStep3Title, text: PANEL_COPY.onboardingStep3Text },
|
|
53
58
|
]
|
|
54
59
|
return (
|
|
55
|
-
<
|
|
56
|
-
<div className="dsh-channels-
|
|
57
|
-
<
|
|
58
|
-
<span className="dsh-channels-cfg__badge dsh-channels-cfg__badge--muted">
|
|
59
|
-
{PANEL_COPY.phaseUnconfigured}
|
|
60
|
-
</span>
|
|
60
|
+
<div className="dsh-channels-cfg__hero">
|
|
61
|
+
<div className="dsh-channels-cfg__heroTop">
|
|
62
|
+
<strong>{PANEL_COPY.onboardingTitle}</strong>
|
|
61
63
|
</div>
|
|
62
|
-
<span className="dsh-channels-cfg__help">{PANEL_COPY.onboardingIntro}</span>
|
|
63
64
|
<ol className="dsh-channels-cfg__steps">
|
|
64
65
|
{steps.map((step, index) => (
|
|
65
66
|
<li className="dsh-channels-cfg__step" key={step.title}>
|
|
66
67
|
<span className="dsh-channels-cfg__stepIndex">{index + 1}</span>
|
|
67
68
|
<span className="dsh-channels-cfg__stepBody">
|
|
68
69
|
<span className="dsh-channels-cfg__stepTitle">{step.title}</span>
|
|
69
|
-
<span className="dsh-channels-
|
|
70
|
+
<span className="dsh-channels-cfg__rowSummary">{step.text}</span>
|
|
70
71
|
</span>
|
|
71
72
|
</li>
|
|
72
73
|
))}
|
|
@@ -74,19 +75,16 @@ function OnboardingCard(props: { status: ChannelStatusWire }): JSX.Element {
|
|
|
74
75
|
{status.missingConfig.length === 0
|
|
75
76
|
? null
|
|
76
77
|
: (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
</div>
|
|
78
|
+
<span className="dsh-channels-cfg__rowSummary">
|
|
79
|
+
{PANEL_COPY.missingConfig}:{status.missingConfig.join('、')}
|
|
80
|
+
</span>
|
|
81
81
|
)}
|
|
82
|
-
|
|
83
|
-
<span className="dsh-channels-cfg__help">{PANEL_COPY.onboardingDocsHint}</span>
|
|
84
|
-
</section>
|
|
82
|
+
</div>
|
|
85
83
|
)
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
/**
|
|
89
|
-
function
|
|
86
|
+
/** Phase, the one lifecycle action available in it, and the runtime counters. */
|
|
87
|
+
function StatusBlock(props: {
|
|
90
88
|
status: ChannelStatusWire
|
|
91
89
|
busy: BusyAction | null
|
|
92
90
|
onReconnect: () => void
|
|
@@ -96,163 +94,149 @@ function ConnectionInfoCard(props: {
|
|
|
96
94
|
// `status.busy` covers a connection attempt started outside this panel; the
|
|
97
95
|
// local busy flag covers the panel's own action. Either way the button waits.
|
|
98
96
|
const actionBusy = busy !== null || status.busy
|
|
99
|
-
const
|
|
100
|
-
const facts = [
|
|
101
|
-
{ label: PANEL_COPY.statusCard, value: badge.text },
|
|
102
|
-
{ label: PANEL_COPY.connectionStateLabel, value: connection.text },
|
|
103
|
-
{ label: PANEL_COPY.botOpenId, value: status.botOpenIdTail ?? PANEL_COPY.unknownValue },
|
|
104
|
-
{ label: PANEL_COPY.uptime, value: formatUptime(status.uptimeMs) },
|
|
97
|
+
const stats = [
|
|
105
98
|
{ label: PANEL_COPY.routeCount, value: String(status.routeCount) },
|
|
106
99
|
{ label: PANEL_COPY.liveAgentCount, value: String(status.liveAgentCount) },
|
|
107
100
|
{ label: PANEL_COPY.inflightTurnCount, value: String(status.inflightTurnCount) },
|
|
101
|
+
{ label: PANEL_COPY.botOpenId, value: status.botOpenIdTail ?? PANEL_COPY.unknownValue, mono: true },
|
|
108
102
|
]
|
|
103
|
+
let action: JSX.Element | null = null
|
|
104
|
+
if (status.phase === 'connecting') {
|
|
105
|
+
action = <button type="button" className="dsh-channels-cfg__btn dsh-channels-cfg__btn--primary" disabled>{PANEL_COPY.connecting}</button>
|
|
106
|
+
} else if (status.phase === 'idle' || status.phase === 'error') {
|
|
107
|
+
action = (
|
|
108
|
+
<button type="button" className="dsh-channels-cfg__btn dsh-channels-cfg__btn--primary" onClick={onReconnect} disabled={actionBusy}>
|
|
109
|
+
{busy === 'reconnect' ? PANEL_COPY.connecting : status.phase === 'idle' ? PANEL_COPY.connect : PANEL_COPY.retry}
|
|
110
|
+
</button>
|
|
111
|
+
)
|
|
112
|
+
} else if (status.phase === 'connected') {
|
|
113
|
+
action = (
|
|
114
|
+
<button type="button" className="dsh-channels-cfg__btn" onClick={onReconnect} disabled={actionBusy}>
|
|
115
|
+
{busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}
|
|
116
|
+
</button>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
109
119
|
return (
|
|
110
|
-
<
|
|
111
|
-
<div className="dsh-channels-
|
|
112
|
-
<
|
|
113
|
-
<
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
<div className="dsh-channels-cfg__hero">
|
|
121
|
+
<div className="dsh-channels-cfg__heroTop">
|
|
122
|
+
<Pill tone={badge.tone} dot>{badge.text}</Pill>
|
|
123
|
+
<strong>{phaseSummary(status.phase)}</strong>
|
|
124
|
+
<span className="dsh-channels-cfg__spacer" />
|
|
125
|
+
{action}
|
|
116
126
|
</div>
|
|
117
127
|
{status.lastStartError === undefined
|
|
118
128
|
? null
|
|
119
129
|
: (
|
|
120
130
|
<div className="dsh-channels-cfg__notice dsh-channels-cfg__notice--error">
|
|
121
|
-
|
|
122
|
-
<span className="dsh-channels-cfg__mono">{status.lastStartError}</span>
|
|
131
|
+
{PANEL_COPY.lastError}:<span className="dsh-channels-cfg__mono">{status.lastStartError}</span>
|
|
123
132
|
</div>
|
|
124
133
|
)}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
{status.phase === 'connected'
|
|
135
|
+
? (
|
|
136
|
+
<div className="dsh-channels-cfg__stats">
|
|
137
|
+
{stats.map(stat => (
|
|
138
|
+
<div className="dsh-channels-cfg__stat" key={stat.label}>
|
|
139
|
+
<span>{stat.label}</span>
|
|
140
|
+
<strong className={stat.mono === true ? 'dsh-channels-cfg__mono' : undefined}>{stat.value}</strong>
|
|
141
|
+
</div>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
)
|
|
145
|
+
: null}
|
|
133
146
|
{status.reasons.length === 0
|
|
134
147
|
? null
|
|
135
148
|
: (
|
|
136
|
-
<span className={`dsh-channels-
|
|
149
|
+
<span className={`dsh-channels-cfg__rowSummary${status.level === 'unhealthy' ? ' dsh-channels-cfg__textBad' : ''}`}>
|
|
137
150
|
{PANEL_COPY.healthReasons}:{status.reasons.join(';')}
|
|
138
151
|
</span>
|
|
139
152
|
)}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<button
|
|
151
|
-
type="button"
|
|
152
|
-
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--primary"
|
|
153
|
-
onClick={onReconnect}
|
|
154
|
-
disabled={actionBusy}
|
|
155
|
-
>
|
|
156
|
-
{busy === 'reconnect' ? PANEL_COPY.connecting : PANEL_COPY.connect}
|
|
157
|
-
</button>
|
|
158
|
-
)
|
|
159
|
-
: null}
|
|
160
|
-
{status.phase === 'error'
|
|
161
|
-
? (
|
|
162
|
-
<button
|
|
163
|
-
type="button"
|
|
164
|
-
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--primary"
|
|
165
|
-
onClick={onReconnect}
|
|
166
|
-
disabled={actionBusy}
|
|
167
|
-
>
|
|
168
|
-
{busy === 'reconnect' ? PANEL_COPY.connecting : PANEL_COPY.retry}
|
|
169
|
-
</button>
|
|
170
|
-
)
|
|
171
|
-
: null}
|
|
172
|
-
{status.phase === 'connected'
|
|
173
|
-
? (
|
|
174
|
-
<>
|
|
175
|
-
<button
|
|
176
|
-
type="button"
|
|
177
|
-
className="dsh-channels-cfg__btn dsh-channels-cfg__btn--danger"
|
|
178
|
-
onClick={onReconnect}
|
|
179
|
-
disabled={actionBusy}
|
|
180
|
-
>
|
|
181
|
-
{busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}
|
|
182
|
-
</button>
|
|
183
|
-
<span className="dsh-channels-cfg__help">{PANEL_COPY.restartWarning}</span>
|
|
184
|
-
</>
|
|
185
|
-
)
|
|
186
|
-
: null}
|
|
187
|
-
</div>
|
|
188
|
-
</section>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Status icon in front of one check row. */
|
|
158
|
+
function CheckIcon(props: { tone: 'ok' | 'warn' | 'bad' }): JSX.Element {
|
|
159
|
+
return (
|
|
160
|
+
<span className={`dsh-channels-cfg__checkIcon dsh-channels-cfg__checkIcon--${props.tone}`} aria-hidden="true">
|
|
161
|
+
{props.tone === 'ok' ? '✓' : '!'}
|
|
162
|
+
</span>
|
|
189
163
|
)
|
|
190
164
|
}
|
|
191
165
|
|
|
192
|
-
/** Post-connect scope self-check:
|
|
193
|
-
function
|
|
166
|
+
/** Post-connect scope self-check: the gap count, expandable to each scope with its console link. */
|
|
167
|
+
function ScopeCheckRow(props: { check: ScopeCheckWire }): JSX.Element {
|
|
194
168
|
const { check } = props
|
|
169
|
+
const [open, setOpen] = useState(false)
|
|
170
|
+
if (check.state === 'ok') {
|
|
171
|
+
return <Row leading={<CheckIcon tone="ok" />} label={PANEL_COPY.scopeCheckOk} />
|
|
172
|
+
}
|
|
173
|
+
if (check.state === 'error') {
|
|
174
|
+
return (
|
|
175
|
+
<Row
|
|
176
|
+
leading={<CheckIcon tone="bad" />}
|
|
177
|
+
label={PANEL_COPY.scopeCheckError}
|
|
178
|
+
summary={<span className="dsh-channels-cfg__mono">{check.error}</span>}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
const required = check.missing.filter(item => item.level === 'required').length
|
|
195
183
|
return (
|
|
196
|
-
|
|
197
|
-
<
|
|
198
|
-
<
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
184
|
+
<>
|
|
185
|
+
<Row
|
|
186
|
+
leading={<CheckIcon tone={required > 0 ? 'bad' : 'warn'} />}
|
|
187
|
+
label={required > 0
|
|
188
|
+
? fillCopy(PANEL_COPY.scopeCheckMissingRequired, { n: required })
|
|
189
|
+
: fillCopy(PANEL_COPY.scopeCheckMissing, { n: check.missing.length })}
|
|
190
|
+
summary={required > 0 ? PANEL_COPY.scopeCheckPublish : PANEL_COPY.scopeCheckMissingOptional}
|
|
191
|
+
control={(
|
|
192
|
+
<div className="dsh-channels-cfg__inputRow">
|
|
193
|
+
<button type="button" className="dsh-channels-cfg__more" aria-expanded={open} onClick={() => setOpen(!open)}>
|
|
194
|
+
{open ? PANEL_COPY.collapse : PANEL_COPY.expand}
|
|
195
|
+
</button>
|
|
196
|
+
<a className="dsh-channels-cfg__btn" href={check.consoleUrl} target="_blank" rel="noreferrer noopener">
|
|
197
|
+
{PANEL_COPY.scopeCheckOpenAll}
|
|
198
|
+
</a>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
/>
|
|
202
|
+
{open
|
|
212
203
|
? (
|
|
213
|
-
<
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
<
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
<a href={check.consoleUrl} target="_blank" rel="noreferrer noopener">{PANEL_COPY.scopeCheckOpenAll}</a>
|
|
226
|
-
</div>
|
|
204
|
+
<ul className="dsh-channels-cfg__scopeList">
|
|
205
|
+
{check.missing.map(item => (
|
|
206
|
+
<li key={item.scopes.join(',')}>
|
|
207
|
+
<code className="dsh-channels-cfg__mono">{item.scopes.join(' / ')}</code>
|
|
208
|
+
{item.level === 'required' ? <Pill tone="bad">{PANEL_COPY.scopeCheckRequired}</Pill> : null}
|
|
209
|
+
<span>{item.purpose}</span>
|
|
210
|
+
<a className="dsh-channels-cfg__link" href={item.consoleUrl} target="_blank" rel="noreferrer noopener">
|
|
211
|
+
{PANEL_COPY.scopeCheckOpenOne}
|
|
212
|
+
</a>
|
|
213
|
+
</li>
|
|
214
|
+
))}
|
|
215
|
+
</ul>
|
|
227
216
|
)
|
|
228
217
|
: null}
|
|
229
|
-
|
|
218
|
+
</>
|
|
230
219
|
)
|
|
231
220
|
}
|
|
232
221
|
|
|
233
|
-
/** Post-connect native slash-command panel sync:
|
|
234
|
-
function
|
|
222
|
+
/** Post-connect native slash-command panel sync: the command count, or the Feishu reason. */
|
|
223
|
+
function SlashCommandSyncRow(props: { sync: SlashCommandSyncWire }): JSX.Element {
|
|
235
224
|
const { sync } = props
|
|
236
|
-
return
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<span className="dsh-channels-cfg__mono">{sync.error}</span>
|
|
252
|
-
</div>
|
|
253
|
-
)}
|
|
254
|
-
</section>
|
|
255
|
-
)
|
|
225
|
+
return sync.state === 'ok'
|
|
226
|
+
? (
|
|
227
|
+
<Row
|
|
228
|
+
leading={<CheckIcon tone="ok" />}
|
|
229
|
+
label={PANEL_COPY.slashCommandSyncTitle}
|
|
230
|
+
summary={fillCopy(PANEL_COPY.slashCommandSyncOk, { total: sync.total })}
|
|
231
|
+
/>
|
|
232
|
+
)
|
|
233
|
+
: (
|
|
234
|
+
<Row
|
|
235
|
+
leading={<CheckIcon tone="warn" />}
|
|
236
|
+
label={PANEL_COPY.slashCommandSyncTitle}
|
|
237
|
+
summary={<>{PANEL_COPY.slashCommandSyncError}:<span className="dsh-channels-cfg__mono">{sync.error}</span></>}
|
|
238
|
+
/>
|
|
239
|
+
)
|
|
256
240
|
}
|
|
257
241
|
|
|
258
242
|
/** Config saved but only applied on the next connect: offer the one-click restart. */
|
|
@@ -268,16 +252,15 @@ function RestartBanner(props: {
|
|
|
268
252
|
.join('、')
|
|
269
253
|
return (
|
|
270
254
|
<div className="dsh-channels-cfg__banner">
|
|
271
|
-
<span>{PANEL_COPY.restartBanner}{keys}</span>
|
|
255
|
+
<span className="dsh-channels-cfg__bannerText">{PANEL_COPY.restartBanner}{keys}</span>
|
|
272
256
|
<button
|
|
273
257
|
type="button"
|
|
274
|
-
className="dsh-channels-cfg__btn
|
|
258
|
+
className="dsh-channels-cfg__btn"
|
|
275
259
|
onClick={onReconnect}
|
|
276
260
|
disabled={busy !== null || locked}
|
|
277
261
|
>
|
|
278
262
|
{busy === 'reconnect' ? PANEL_COPY.reconnecting : PANEL_COPY.reconnect}
|
|
279
263
|
</button>
|
|
280
|
-
<span className="dsh-channels-cfg__help">{PANEL_COPY.restartWarning}</span>
|
|
281
264
|
</div>
|
|
282
265
|
)
|
|
283
266
|
}
|