@modesto_ma/dsh-channels 0.5.0-beta.4 → 0.5.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +49 -28
  2. package/docs/attachment-pipeline-v2.md +1 -1
  3. package/docs/feishu-onboarding.md +6 -5
  4. package/lib/client.js +1874 -1510
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +736 -353
  7. package/lib/index.js.map +1 -1
  8. package/package.json +2 -2
  9. package/src/client/api.ts +45 -2
  10. package/src/client/avatar.tsx +36 -0
  11. package/src/client/icon.tsx +25 -0
  12. package/src/client/index.ts +78 -23
  13. package/src/client/number-field.ts +97 -0
  14. package/src/client/origin-cache.ts +45 -0
  15. package/src/client/origin.tsx +201 -0
  16. package/src/client/panel/access-tab.tsx +90 -21
  17. package/src/client/panel/advanced-tab.tsx +59 -68
  18. package/src/client/panel/connection-tab.tsx +147 -164
  19. package/src/client/panel/credentials-card.tsx +124 -87
  20. package/src/client/panel/fields-tab.tsx +27 -0
  21. package/src/client/panel/index.tsx +77 -54
  22. package/src/client/panel/profiles.ts +73 -0
  23. package/src/client/panel/shared.tsx +358 -253
  24. package/src/client/panel/whitelist.tsx +203 -0
  25. package/src/client/panel/wiki-tab.tsx +33 -39
  26. package/src/client/styles.ts +132 -92
  27. package/src/domain.ts +42 -0
  28. package/src/feishu/scopes.ts +2 -1
  29. package/src/feishu-channel.ts +87 -0
  30. package/src/index.ts +1 -1
  31. package/src/routing/inbound.ts +13 -5
  32. package/src/service.ts +146 -3
  33. package/src/session/routes.ts +45 -4
  34. package/src/settings/contract.ts +265 -152
  35. package/src/settings/routes.ts +47 -0
  36. package/src/settings/surface.ts +201 -197
  37. package/src/client/panel/access-card.tsx +0 -329
  38. package/src/client/panel/analyze-token-card.tsx +0 -70
  39. package/src/client/panel/model-tab.tsx +0 -23
  40. package/src/client/panel/sessions-tab.tsx +0 -24
  41. package/src/client/panel/workspace-tab.tsx +0 -24
@@ -1,18 +1,25 @@
1
1
  /**
2
- * 「访问授权」tab:准入与授权字段表单(管理员 user_id / 准入模式 /
3
- * 申请有效期)+ 白名单与待处理申请卡。表单保存走全局 draft,白名单
4
- * 增删是独立即时 mutation。
2
+ * 「访问授权」tab:待处理申请(有才显示,置顶)、准入字段(调试模式 /
3
+ * 管理员 / 申请有效期)与群、用户白名单。字段保存走全局 draft,白名单增删
4
+ * 与申请处理是独立即时 mutation。
5
+ *
6
+ * `access.loaded === false`(飞书未连接、插件 storage 域未打开)时白名单与
7
+ * 申请列表是「未知」而不是「空」,因此只渲染连接提示并禁用全部写操作。
5
8
  *
6
9
  * @module dsh-channels/client/panel/access-tab
7
10
  */
8
11
 
9
- import { PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
10
- import { AccessCard } from './access-card.tsx'
11
- import { FieldsCard, SaveBar, TabHelp } from './shared.tsx'
12
+ import { fillCopy, PANEL_COPY, TAB_FIELD_GROUPS } from '../../settings/contract.ts'
13
+ import type { AccessWire } from '../../settings/contract.ts'
14
+ import { Avatar } from '../avatar.tsx'
15
+ import type { ProfileView } from './profiles.ts'
16
+ import { FieldSections, NoticeBar, Row } from './shared.tsx'
12
17
  import type { FormView } from './shared.tsx'
18
+ import { RequestList, Whitelist } from './whitelist.tsx'
13
19
 
14
20
  export interface AccessTabProps {
15
21
  form: FormView
22
+ profiles: ProfileView
16
23
  accessNotice: string | null
17
24
  accessError: string | null
18
25
  onAddGroup: (chatId: string, name: string) => Promise<boolean>
@@ -23,24 +30,86 @@ export interface AccessTabProps {
23
30
  }
24
31
 
25
32
  export function AccessTab(props: AccessTabProps): JSX.Element {
26
- const { form, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props
33
+ const { form, profiles, accessNotice, accessError, onAddGroup, onRemoveGroup, onAddUser, onRemoveUser, onResolveRequest } = props
34
+ const { access } = form.panel
35
+ const working = form.busy === 'access'
36
+ const disabled = form.busy !== null || form.locked || !access.loaded
27
37
  return (
28
38
  <div className="dsh-channels-cfg__tabBody">
29
- <TabHelp text={PANEL_COPY.accessTabHelp} />
30
- <FieldsCard form={form} groups={TAB_FIELD_GROUPS.access} />
31
- <SaveBar form={form} tab="access" />
32
- <AccessCard
33
- panel={form.panel}
34
- busy={form.busy}
35
- locked={form.locked}
36
- notice={accessNotice}
37
- error={accessError}
38
- onAddGroup={onAddGroup}
39
- onRemoveGroup={onRemoveGroup}
40
- onAddUser={onAddUser}
41
- onRemoveUser={onRemoveUser}
42
- onResolveRequest={onResolveRequest}
39
+ {accessNotice === null ? null : <NoticeBar kind="ok" text={accessNotice} />}
40
+ {accessError === null ? null : <NoticeBar kind="error" text={accessError} />}
41
+ {access.loaded && access.mode === 'guarded' && !access.admin.configured
42
+ ? (
43
+ <NoticeBar
44
+ kind="error"
45
+ text={access.admin.ownerLookupError === undefined
46
+ ? PANEL_COPY.accessAdminMissingWarning
47
+ : `${PANEL_COPY.accessAdminMissingWarning}(${access.admin.ownerLookupError})`}
48
+ />
49
+ )
50
+ : null}
51
+ {profiles.warning === undefined ? null : <p className="dsh-channels-cfg__secFoot">{fillCopy(PANEL_COPY.profilesWarning, { reason: profiles.warning })}</p>}
52
+ {access.loaded && access.requests.length > 0
53
+ ? <RequestList requests={access.requests} profiles={profiles} disabled={disabled} working={working} onResolve={onResolveRequest} />
54
+ : null}
55
+ <FieldSections
56
+ form={form}
57
+ groups={TAB_FIELD_GROUPS.access}
58
+ extra={{ access: <AdminRow admin={access.admin} profiles={profiles} /> }}
59
+ />
60
+ <Whitelist
61
+ title={PANEL_COPY.accessGroupsTitle}
62
+ entries={access.groups.map(group => ({
63
+ id: group.chatId,
64
+ name: profiles.chat(group.chatId)?.name ?? group.name,
65
+ avatarUrl: profiles.chat(group.chatId)?.avatarUrl,
66
+ source: group.source,
67
+ }))}
68
+ avatar="群"
69
+ empty={PANEL_COPY.accessGroupsEmpty}
70
+ loaded={access.loaded}
71
+ addLabel={PANEL_COPY.accessAddGroup}
72
+ idPlaceholder={PANEL_COPY.accessGroupChatIdPlaceholder}
73
+ namePlaceholder={PANEL_COPY.accessGroupNamePlaceholder}
74
+ disabled={disabled}
75
+ working={working}
76
+ onAdd={onAddGroup}
77
+ onRemove={onRemoveGroup}
78
+ />
79
+ <Whitelist
80
+ title={PANEL_COPY.accessUsersTitle}
81
+ entries={access.users.map(user => ({
82
+ id: user.userId,
83
+ name: profiles.user(user.userId)?.name ?? user.name,
84
+ avatarUrl: profiles.user(user.userId)?.avatarUrl,
85
+ source: user.source,
86
+ }))}
87
+ empty={PANEL_COPY.accessUsersEmpty}
88
+ loaded={access.loaded}
89
+ addLabel={PANEL_COPY.accessAddUser}
90
+ idPlaceholder={PANEL_COPY.accessUserIdPlaceholder}
91
+ namePlaceholder={PANEL_COPY.accessUserNamePlaceholder}
92
+ disabled={disabled}
93
+ working={working}
94
+ onAdd={onAddUser}
95
+ onRemove={onRemoveUser}
43
96
  />
44
97
  </div>
45
98
  )
46
99
  }
100
+
101
+ /** The effective administrator with their Feishu name and avatar; nothing while there is none. */
102
+ function AdminRow(props: { admin: AccessWire['admin']; profiles: ProfileView }): JSX.Element | null {
103
+ const { admin, profiles } = props
104
+ if (admin.userId === undefined) return null
105
+ const profile = profiles.user(admin.userId)
106
+ const name = profile?.name ?? admin.userId
107
+ const source = admin.source === 'owner' ? PANEL_COPY.accessAdminSourceOwner : PANEL_COPY.accessAdminSourceConfig
108
+ return (
109
+ <Row
110
+ leading={<Avatar text={name} url={profile?.avatarUrl} />}
111
+ label={name}
112
+ summary={<>{PANEL_COPY.accessAdminCurrent} · <span className="dsh-channels-cfg__mono">{admin.userId}</span> · {source}</>}
113
+ />
114
+ )
115
+ }
@@ -1,80 +1,71 @@
1
1
  /**
2
- * 「高级」tab:流式刷新等低层字段 + 只读部署信息(overlayPath / version /
3
- * 部署级参数)+ 「清除 Web 覆盖」。清除覆盖是整体动作,不走 draft。
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 type { PanelWire } from '../../settings/contract.ts'
10
- import { FieldsCard, SaveBar, TabHelp } from './shared.tsx'
11
- import type { BusyAction, FormView } from './shared.tsx'
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: { form: FormView; onClear: () => void }): JSX.Element {
14
- const { form, onClear } = props
15
- return (
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 { panel, busy, locked, onClear } = props
33
- const { readOnly } = panel.config
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
- <section className="dsh-channels-cfg__card">
37
- <div className="dsh-channels-cfg__cardHead">
38
- <span className="dsh-channels-cfg__cardTitle">{PANEL_COPY.advancedCard}</span>
39
- <span className="dsh-channels-cfg__cardMeta">
40
- {overridden === 0 ? PANEL_COPY.inherited : `${PANEL_COPY.overridden} ${overridden} 项`}
41
- </span>
42
- </div>
43
- <span className="dsh-channels-cfg__help">{PANEL_COPY.advancedHelp}</span>
44
- <div className="dsh-channels-cfg__labelRow">
45
- <span className="dsh-channels-cfg__cardTitle">{PANEL_COPY.readOnlyCard}</span>
46
- </div>
47
- <div className="dsh-channels-cfg__readOnly">
48
- {readOnly.map(field => (
49
- <div className="dsh-channels-cfg__kv" key={field.key}>
50
- <span className="dsh-channels-cfg__kvKey">{field.label}</span>
51
- <span className="dsh-channels-cfg__kvValue">{field.value}</span>
52
- {field.help === undefined ? null : <span className="dsh-channels-cfg__help">{field.help}</span>}
53
- </div>
54
- ))}
55
- <div className="dsh-channels-cfg__kv">
56
- <span className="dsh-channels-cfg__kvKey">{PANEL_COPY.overlayPath}</span>
57
- <span className="dsh-channels-cfg__kvValue dsh-channels-cfg__mono">{panel.status.overlayPath}</span>
58
- <span className="dsh-channels-cfg__help">{PANEL_COPY.overlayPathHelp}</span>
59
- </div>
60
- <div className="dsh-channels-cfg__kv">
61
- <span className="dsh-channels-cfg__kvKey">{PANEL_COPY.version}</span>
62
- <span className="dsh-channels-cfg__kvValue dsh-channels-cfg__mono">{panel.status.version}</span>
63
- </div>
64
- </div>
65
- <div className="dsh-channels-cfg__actions">
66
- <button
67
- type="button"
68
- className="dsh-channels-cfg__btn"
69
- onClick={onClear}
70
- disabled={overridden === 0 || busy !== null || locked}
71
- >
72
- {busy === 'clear' ? PANEL_COPY.clearing : PANEL_COPY.clear}
73
- </button>
74
- <span className="dsh-channels-cfg__help">
75
- {overridden === 0 ? PANEL_COPY.noOverrides : `${PANEL_COPY.overridden} ${overridden} 项`}
76
- </span>
77
- </div>
78
- </section>
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
  }