@dsh-plus/notify-email 0.1.2 → 0.1.4
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/lib/client.js +73 -4
- package/lib/index.d.ts +8 -0
- package/lib/index.js +4 -0
- package/package.json +2 -2
- package/src/client/card.tsx +1 -1
- package/src/client/client.ts +74 -11
- package/src/client/scope.ts +9 -5
- package/src/service.ts +5 -0
package/lib/client.js
CHANGED
|
@@ -176,7 +176,7 @@ function NotifyEmailCard(props) {
|
|
|
176
176
|
}, [value, draft]);
|
|
177
177
|
(0, react.useEffect)(() => {
|
|
178
178
|
let alive = true;
|
|
179
|
-
api.describe().then((response) => {
|
|
179
|
+
api.describe({}).then((response) => {
|
|
180
180
|
if (!alive || !response.result.ok) return;
|
|
181
181
|
const view = response.result.value?.namespaces.find((ns) => ns.ns === SETTINGS_NS);
|
|
182
182
|
setPassConfigured(view?.secrets.some((secret) => secret.path.join(".") === "smtp.pass" && secret.set) ?? false);
|
|
@@ -610,9 +610,78 @@ const name = "dsh-plus-notify-email";
|
|
|
610
610
|
const inject = [
|
|
611
611
|
"slots",
|
|
612
612
|
"locale",
|
|
613
|
-
"
|
|
614
|
-
"
|
|
613
|
+
"connection",
|
|
614
|
+
"remote"
|
|
615
615
|
];
|
|
616
|
+
/**
|
|
617
|
+
* api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
618
|
+
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
619
|
+
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
620
|
+
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
621
|
+
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
622
|
+
*/
|
|
623
|
+
function createApiScope(api, ns, c) {
|
|
624
|
+
let state = {
|
|
625
|
+
status: "loading",
|
|
626
|
+
value: void 0,
|
|
627
|
+
revision: void 0,
|
|
628
|
+
writable: false
|
|
629
|
+
};
|
|
630
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
631
|
+
let generation = 0;
|
|
632
|
+
const publish = (next) => {
|
|
633
|
+
state = next;
|
|
634
|
+
for (const listener of [...listeners]) listener();
|
|
635
|
+
};
|
|
636
|
+
const load = async () => {
|
|
637
|
+
const gen = ++generation;
|
|
638
|
+
let response;
|
|
639
|
+
try {
|
|
640
|
+
response = await api.describe({});
|
|
641
|
+
} catch {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (gen !== generation || !response.result.ok) return;
|
|
645
|
+
const writable = response.result.value?.writable ?? false;
|
|
646
|
+
const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns);
|
|
647
|
+
if (view === void 0) {
|
|
648
|
+
publish({
|
|
649
|
+
status: "unavailable",
|
|
650
|
+
value: void 0,
|
|
651
|
+
revision: void 0,
|
|
652
|
+
writable
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
publish({
|
|
657
|
+
status: "ready",
|
|
658
|
+
value: view.value,
|
|
659
|
+
revision: view.revision,
|
|
660
|
+
writable
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
const refresh = (namespace) => {
|
|
664
|
+
if (namespace !== void 0 && namespace !== ns) return;
|
|
665
|
+
load();
|
|
666
|
+
};
|
|
667
|
+
const disposers = [c.get("remote").$on("settings/document-updated", refresh), c.on("connection/reset", () => {
|
|
668
|
+
load();
|
|
669
|
+
})];
|
|
670
|
+
c.effect(() => () => {
|
|
671
|
+
for (const dispose of disposers) dispose();
|
|
672
|
+
}, "notify-email: settings scope");
|
|
673
|
+
load();
|
|
674
|
+
return {
|
|
675
|
+
getSnapshot: () => state,
|
|
676
|
+
subscribe: (listener) => {
|
|
677
|
+
listeners.add(listener);
|
|
678
|
+
return () => {
|
|
679
|
+
listeners.delete(listener);
|
|
680
|
+
};
|
|
681
|
+
},
|
|
682
|
+
load
|
|
683
|
+
};
|
|
684
|
+
}
|
|
616
685
|
function apply(ctx) {
|
|
617
686
|
const c = ctx;
|
|
618
687
|
const tag = injectStyle();
|
|
@@ -623,8 +692,8 @@ function apply(ctx) {
|
|
|
623
692
|
zh,
|
|
624
693
|
en
|
|
625
694
|
}), "notify-email: locale");
|
|
626
|
-
const scope = c.settingsScope.bind({ namespace: SETTINGS_NS });
|
|
627
695
|
const api = c.get("connection").api.settings;
|
|
696
|
+
const scope = createApiScope(api, SETTINGS_NS, c);
|
|
628
697
|
c.slots.inject("settings.plugin.item", () => c.slots.register({
|
|
629
698
|
name: "settings.plugin.item",
|
|
630
699
|
key: SETTINGS_NS,
|
package/lib/index.d.ts
CHANGED
|
@@ -138,6 +138,14 @@ declare class NotifyEmailService extends Service {
|
|
|
138
138
|
ok: boolean;
|
|
139
139
|
detail: string;
|
|
140
140
|
}>;
|
|
141
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
|
|
142
|
+
sendNotice(notice: {
|
|
143
|
+
subject: string;
|
|
144
|
+
text: string;
|
|
145
|
+
}): Promise<{
|
|
146
|
+
ok: boolean;
|
|
147
|
+
detail: string;
|
|
148
|
+
}>;
|
|
141
149
|
private dispatch;
|
|
142
150
|
}
|
|
143
151
|
//#endregion
|
package/lib/index.js
CHANGED
|
@@ -452,6 +452,10 @@ var NotifyEmailService = class extends Service {
|
|
|
452
452
|
text: "这是一封来自 dsh notify-email 插件的测试邮件。"
|
|
453
453
|
}, true);
|
|
454
454
|
}
|
|
455
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
|
|
456
|
+
async sendNotice(notice) {
|
|
457
|
+
return this.mailer.send(notice);
|
|
458
|
+
}
|
|
455
459
|
async dispatch(kind, payload, produce) {
|
|
456
460
|
for (const trigger of [...this.triggers]) {
|
|
457
461
|
let notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/notify-email",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 任务结束/等待决策/出错时向指定邮箱发送邮件通知",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"@deepseek-ai/dsh-client-runtime",
|
|
27
27
|
"@deepseek-ai/dsh-client-locale",
|
|
28
28
|
"@deepseek-ai/dsh-client-connection",
|
|
29
|
-
"@deepseek-ai/dsh-
|
|
29
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
30
30
|
],
|
|
31
31
|
"platform": "web"
|
|
32
32
|
}
|
package/src/client/card.tsx
CHANGED
|
@@ -130,7 +130,7 @@ export function NotifyEmailCard(props: CardProps): ReactElement | null {
|
|
|
130
130
|
// passConfigured 探测:scope 快照不含 secrets,经 describe 的 secrets 列表判断。
|
|
131
131
|
useEffect(() => {
|
|
132
132
|
let alive = true
|
|
133
|
-
api.describe()
|
|
133
|
+
api.describe({})
|
|
134
134
|
.then((response) => {
|
|
135
135
|
if (!alive || !response.result.ok) return
|
|
136
136
|
const view = response.result.value?.namespaces.find((ns) => ns.ns === SETTINGS_NS)
|
package/src/client/client.ts
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
3
3
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
4
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
5
|
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/
|
|
6
|
+
* 类型说明:浏览器半只用到 slots/locale/connection/remote 的很窄一面,
|
|
7
7
|
* 此处以最小本地接口声明(见 ./scope.ts),避免为构建期类型引入整条官方
|
|
8
8
|
* client 依赖树;运行时契约以官方 dsh-client-ui-settings-plugins 的
|
|
9
|
-
* settings.plugin.item 插槽与 dsh-client-
|
|
9
|
+
* settings.plugin.item 插槽与 dsh-client-connection 的 settings RPC 面为准。
|
|
10
10
|
* rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
11
11
|
* (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
|
|
12
12
|
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
13
|
-
* 配置读写经官方
|
|
14
|
-
*
|
|
13
|
+
* 配置读写经官方 settings RPC 直连(rc7 起第三方命名空间全量开放;
|
|
14
|
+
* 不复用 settingsScope 服务——非 loopback 页面下它无数据,见 scope.ts);
|
|
15
|
+
* 自定义端点仅剩「发送测试邮件」(api.ts)。
|
|
15
16
|
* @module @dsh-plus/notify-email/client
|
|
16
17
|
*/
|
|
17
18
|
import type { Context } from '@deepseek-ai/cordis'
|
|
@@ -19,13 +20,13 @@ import type { Context } from '@deepseek-ai/cordis'
|
|
|
19
20
|
import { SETTINGS_NS } from '../ns.ts'
|
|
20
21
|
import { NotifyEmailCard } from './card.tsx'
|
|
21
22
|
import { en, NS, zh } from './i18n.ts'
|
|
22
|
-
import type { Scope, SettingsApi } from './scope.ts'
|
|
23
|
+
import type { Scope, ScopeSnapshot, SettingsApi } from './scope.ts'
|
|
23
24
|
import { injectStyle } from './styles.ts'
|
|
24
25
|
|
|
25
26
|
export const name = 'dsh-plus-notify-email'
|
|
26
27
|
|
|
27
28
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
28
|
-
export const inject = ['slots', 'locale', '
|
|
29
|
+
export const inject = ['slots', 'locale', 'connection', 'remote'] as const
|
|
29
30
|
|
|
30
31
|
interface SlotsLike {
|
|
31
32
|
inject(key: string, callback: () => unknown): unknown
|
|
@@ -37,22 +38,84 @@ interface LocaleLike {
|
|
|
37
38
|
bind(ns: string): (key: string) => string
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
interface
|
|
41
|
-
|
|
41
|
+
interface RemoteLike {
|
|
42
|
+
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
interface ConnectionLike {
|
|
45
|
-
settings: SettingsApi
|
|
46
|
+
api: { settings: SettingsApi }
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
interface ClientContext {
|
|
49
50
|
slots: SlotsLike
|
|
50
51
|
locale: LocaleLike
|
|
51
|
-
settingsScope: SettingsScopeServiceLike
|
|
52
52
|
get(key: 'connection'): ConnectionLike
|
|
53
|
+
get(key: 'remote'): RemoteLike
|
|
54
|
+
on(event: string, listener: () => void): () => void
|
|
53
55
|
effect(execute: () => () => void, label?: string): unknown
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
/**
|
|
59
|
+
* api 直连实现的命名空间 scope(官方配置页 tab 同款模式)。
|
|
60
|
+
* 读:describe 取命名空间视图(脱敏 value/revision + 顶层 writable);
|
|
61
|
+
* 刷新:remote `settings/document-updated`(按命名空间过滤)与
|
|
62
|
+
* `connection/reset`;generation 防旧读覆盖新发布。任何页面 origin
|
|
63
|
+
* (loopback 或 tailnet 信任域名)下行为一致。
|
|
64
|
+
*/
|
|
65
|
+
function createApiScope(api: SettingsApi, ns: string, c: ClientContext): Scope {
|
|
66
|
+
let state: ScopeSnapshot = { status: 'loading', value: undefined, revision: undefined, writable: false }
|
|
67
|
+
const listeners = new Set<() => void>()
|
|
68
|
+
let generation = 0
|
|
69
|
+
const publish = (next: ScopeSnapshot): void => {
|
|
70
|
+
state = next
|
|
71
|
+
for (const listener of [...listeners]) listener()
|
|
72
|
+
}
|
|
73
|
+
const load = async (): Promise<void> => {
|
|
74
|
+
const gen = ++generation
|
|
75
|
+
let response
|
|
76
|
+
try {
|
|
77
|
+
response = await api.describe({})
|
|
78
|
+
} catch {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
if (gen !== generation || !response.result.ok) return
|
|
82
|
+
const writable = response.result.value?.writable ?? false
|
|
83
|
+
const view = response.result.value?.namespaces.find((candidate) => candidate.ns === ns)
|
|
84
|
+
if (view === undefined) {
|
|
85
|
+
publish({ status: 'unavailable', value: undefined, revision: undefined, writable })
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
publish({ status: 'ready', value: view.value, revision: view.revision, writable })
|
|
89
|
+
}
|
|
90
|
+
const refresh = (namespace?: unknown): void => {
|
|
91
|
+
if (namespace !== undefined && namespace !== ns) return
|
|
92
|
+
void load()
|
|
93
|
+
}
|
|
94
|
+
const disposers = [
|
|
95
|
+
c.get('remote').$on('settings/document-updated', refresh),
|
|
96
|
+
c.on('connection/reset', () => {
|
|
97
|
+
void load()
|
|
98
|
+
}),
|
|
99
|
+
]
|
|
100
|
+
c.effect(
|
|
101
|
+
() => () => {
|
|
102
|
+
for (const dispose of disposers) dispose()
|
|
103
|
+
},
|
|
104
|
+
'notify-email: settings scope',
|
|
105
|
+
)
|
|
106
|
+
void load()
|
|
107
|
+
return {
|
|
108
|
+
getSnapshot: () => state,
|
|
109
|
+
subscribe: (listener: () => void) => {
|
|
110
|
+
listeners.add(listener)
|
|
111
|
+
return () => {
|
|
112
|
+
listeners.delete(listener)
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
load,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
56
119
|
export function apply(ctx: Context): void {
|
|
57
120
|
const c = ctx as unknown as ClientContext
|
|
58
121
|
const tag = injectStyle()
|
|
@@ -66,8 +129,8 @@ export function apply(ctx: Context): void {
|
|
|
66
129
|
() => c.locale.register(NS, { zh, en }),
|
|
67
130
|
'notify-email: locale',
|
|
68
131
|
)
|
|
69
|
-
const scope = c.settingsScope.bind({ namespace: SETTINGS_NS })
|
|
70
132
|
const api = c.get('connection').api.settings
|
|
133
|
+
const scope = createApiScope(api, SETTINGS_NS, c)
|
|
71
134
|
c.slots.inject('settings.plugin.item', () =>
|
|
72
135
|
c.slots.register(
|
|
73
136
|
{
|
package/src/client/scope.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 浏览器半 settingsScope/connection 的最小本地接口声明。
|
|
3
3
|
* 只声明本插件用到的窄面,避免为构建期类型引入整条官方 client 依赖树;
|
|
4
|
-
* 运行时契约以官方 dsh-client-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* 运行时契约以官方 dsh-client-connection 的 settings RPC 面为准
|
|
5
|
+
* (rc7:白名单已移除,任何已注册命名空间均可 describe/update/replace)。
|
|
6
|
+
*
|
|
7
|
+
* 实现说明:本插件不复用 settingsScope 服务的 bind()——它在非 loopback
|
|
8
|
+
* 页面(如经 tailnet 域名访问)下走 memory 模式、永远无数据;这里按官方
|
|
9
|
+
* 配置页 tab 的同款模式用 `api.settings` 直连实现 Scope(describe 读 +
|
|
10
|
+
* remote `settings/document-updated` / `connection/reset` 刷新,见 client.ts)。
|
|
7
11
|
* @module notify-email/client/scope
|
|
8
12
|
*/
|
|
9
13
|
|
|
@@ -18,7 +22,7 @@ export interface ScopeSnapshot {
|
|
|
18
22
|
writable: boolean
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
/** settingsScope.bind({namespace})
|
|
25
|
+
/** settingsScope.bind({namespace}) 返回的控制器面(本插件由 api 直连实现)。 */
|
|
22
26
|
export interface Scope {
|
|
23
27
|
getSnapshot(): ScopeSnapshot
|
|
24
28
|
subscribe(listener: () => void): () => void
|
|
@@ -27,7 +31,7 @@ export interface Scope {
|
|
|
27
31
|
|
|
28
32
|
/** connection api.settings 的 RPC 面(本插件用到的三个方法)。 */
|
|
29
33
|
export interface SettingsApi {
|
|
30
|
-
describe(): Promise<{ result: { ok: boolean; value?: { namespaces: Array<{ ns: string; secrets: Array<{ path: string[]; set: boolean }> }> }; error?: { message?: string } } }>
|
|
34
|
+
describe(payload?: Record<string, never>): Promise<{ result: { ok: boolean; value?: { writable: boolean; namespaces: Array<{ ns: string; value: unknown; revision: number; secrets: Array<{ path: string[]; set: boolean }> }> }; error?: { message?: string } } }>
|
|
31
35
|
update(request: { ns: string; patch: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
32
36
|
replace(request: { ns: string; section: Record<string, unknown>; expectedRevision?: number }): Promise<{ result: { ok: boolean; error?: { message?: string } } }>
|
|
33
37
|
}
|
package/src/service.ts
CHANGED
|
@@ -94,6 +94,11 @@ export class NotifyEmailService extends Service {
|
|
|
94
94
|
)
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** 通用直发接口(如 lifeboat 故障告警):走正常 enabled/完整性门禁。 */
|
|
98
|
+
async sendNotice(notice: { subject: string; text: string }): Promise<{ ok: boolean; detail: string }> {
|
|
99
|
+
return this.mailer.send(notice)
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
private async dispatch<T>(
|
|
98
103
|
kind: string,
|
|
99
104
|
payload: T,
|