nucleus-core-ts 0.9.864 → 0.9.866
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/dist/fe/components/IntegrationsPage/components/ConnectionForm.js +21 -1
- package/dist/fe/components/IntegrationsPage/components/MappingsTab.js +15 -0
- package/dist/fe/components/IntegrationsPage/components/RunHistoryRow.js +5 -0
- package/dist/fe/components/IntegrationsPage/components/RunPanel.js +15 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.d.ts +15 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.js +41 -0
- package/dist/fe/components/IntegrationsPage/components/connectionDraft.test.js +67 -2
- package/dist/index.js +6 -4
- package/dist/src/ElysiaPlugin/routes/monitoring/index.d.ts +9 -1
- package/dist/src/ElysiaPlugin/routes/monitoring/index.test.d.ts +1 -0
- package/dist/src/Services/Monitoring/collectors/SystemCollector.d.ts +2 -0
- package/dist/src/Services/Monitoring/collectors/memory.d.ts +70 -0
- package/dist/src/Services/Monitoring/collectors/memory.test.d.ts +1 -0
- package/dist/src/Services/Monitoring/types.d.ts +8 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ import { useLabels } from '../text/context';
|
|
|
6
6
|
import { integrationsPageTheme } from '../theme';
|
|
7
7
|
import { ConnectionAuthFields } from './ConnectionAuthFields';
|
|
8
8
|
import { ConnectionBasicFields } from './ConnectionBasicFields';
|
|
9
|
-
import { draftFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
|
|
9
|
+
import { draftFromSource, draftPatchFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
|
|
10
10
|
import { errorText } from './formSupport';
|
|
11
11
|
import { Badge } from './Primitives';
|
|
12
12
|
import { CardSkeleton } from './Skeletons';
|
|
@@ -58,6 +58,26 @@ export function ConnectionForm({ actions, source, loading = false, onSaved }) {
|
|
|
58
58
|
// it into the list, and selecting an id the list does not hold is the
|
|
59
59
|
// state every card below renders a skeleton for.
|
|
60
60
|
onSaved?.(saved);
|
|
61
|
+
/**
|
|
62
|
+
* The boxes are re-derived from what was actually STORED.
|
|
63
|
+
*
|
|
64
|
+
* The effect above resets them only when the id changes, so a create
|
|
65
|
+
* showed the truth and an edit never did. Two ways that was visible:
|
|
66
|
+
* emptying "Timeout" and saving left the box empty while the connection
|
|
67
|
+
* went on timing out at the stored value — the draft omits a blank
|
|
68
|
+
* timeout, so the patch never mentions it and the server rightly keeps
|
|
69
|
+
* what it had; and a trailing space in the name was trimmed on the way
|
|
70
|
+
* out, so the switcher above showed "Acme" while the box below still
|
|
71
|
+
* read "Acme ". One screen, disagreeing with itself, until F5.
|
|
72
|
+
*
|
|
73
|
+
* MERGED rather than replaced: a field-scoped patch claim can strip
|
|
74
|
+
* columns out of the reply, and re-deriving wholesale from a partial
|
|
75
|
+
* record would blank the boxes those columns fill. Nothing can have been
|
|
76
|
+
* typed in the meantime — every field is disabled while `saving`.
|
|
77
|
+
*/ if (source) setDraft((current)=>({
|
|
78
|
+
...current,
|
|
79
|
+
...draftPatchFromSource(saved)
|
|
80
|
+
}));
|
|
61
81
|
// Selecting resets everything hanging off a connection, so it is only
|
|
62
82
|
// right for a NEW one — doing it on an edit would empty the endpoint,
|
|
63
83
|
// mapping and run lists the operator is working in.
|
|
@@ -174,6 +174,21 @@ export function MappingsTab({ actions }) {
|
|
|
174
174
|
// second earlier landed on "choose a mapping".
|
|
175
175
|
store.upsertMapping(saved);
|
|
176
176
|
store.selectMapping(saved.id);
|
|
177
|
+
/**
|
|
178
|
+
* The plan described the mapping as it WAS.
|
|
179
|
+
*
|
|
180
|
+
* A preview is an answer about one particular mapping, and editing that
|
|
181
|
+
* mapping makes the answer wrong — silently, because the import step
|
|
182
|
+
* prints its own heading from the saved record and the counts from the
|
|
183
|
+
* plan. Switching the write mode to `replace` after previewing gave a
|
|
184
|
+
* screen that said `replace` above numbers computed for `upsert`, and
|
|
185
|
+
* the destructive confirmation then combined the new mode with the old
|
|
186
|
+
* count: "1240 rows will be deleted", from a plan that never considered
|
|
187
|
+
* deleting anything.
|
|
188
|
+
*
|
|
189
|
+
* `selectMapping` cannot do this — it returns early when the id has not
|
|
190
|
+
* changed, and on a save it never does.
|
|
191
|
+
*/ store.setPlan(null, null);
|
|
177
192
|
setReloadToken((token)=>token + 1);
|
|
178
193
|
then?.(saved);
|
|
179
194
|
}).catch((error)=>{
|
|
@@ -42,6 +42,11 @@ export function RunHistoryRow({ run, actions, onReverted }) {
|
|
|
42
42
|
setReverting(false);
|
|
43
43
|
if (result.ok) {
|
|
44
44
|
setConflicts(null);
|
|
45
|
+
// An undo moves the rows a plan was counted against, so the plan on
|
|
46
|
+
// the import step above is now describing a database that no longer
|
|
47
|
+
// exists. Leaving it up invited a re-import against numbers that were
|
|
48
|
+
// true two states ago.
|
|
49
|
+
store.setPlan(null, null);
|
|
45
50
|
onReverted();
|
|
46
51
|
return;
|
|
47
52
|
}
|
|
@@ -111,11 +111,23 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
111
111
|
};
|
|
112
112
|
// Progress for SOMEONE ELSE'S run is dropped: the topic is shared, and a
|
|
113
113
|
// second operator's import must not drive this panel's bar.
|
|
114
|
+
/**
|
|
115
|
+
* A plan describes a database that no longer exists once it has been run.
|
|
116
|
+
*
|
|
117
|
+
* The plan is what the buttons act on and what the destructive confirmation
|
|
118
|
+
* quotes. Left standing after the import, it went on saying "880 to create"
|
|
119
|
+
* next to a history entry reporting 880 created — and the buttons came back
|
|
120
|
+
* enabled, so a second press started a second full import against numbers
|
|
121
|
+
* that were no longer true, with the confirmation showing those same stale
|
|
122
|
+
* numbers. Cleared here, the operator has to preview again, which is the only
|
|
123
|
+
* honest answer to "what would this do now".
|
|
124
|
+
*/ const forgetPlan = ()=>store.setPlan(null, null);
|
|
114
125
|
const applyProgress = useEffectEvent((progress)=>{
|
|
115
126
|
if (store.activeRunId !== progress.runId) return;
|
|
116
127
|
store.setProgress(progress);
|
|
117
128
|
if (progress.phase === 'done') {
|
|
118
129
|
store.setActiveRun(null);
|
|
130
|
+
forgetPlan();
|
|
119
131
|
onRunSettled?.();
|
|
120
132
|
}
|
|
121
133
|
});
|
|
@@ -155,6 +167,9 @@ export function RunPanel({ mapping, actions, onSubscribeProgress, onRunSettled }
|
|
|
155
167
|
if (!run || run.status === 'running' || run.status === 'queued') return;
|
|
156
168
|
store.setActiveRun(null);
|
|
157
169
|
store.setProgress(null);
|
|
170
|
+
// The other place a run ends. Both have to forget the plan, or the
|
|
171
|
+
// realtime path and the polling path disagree about what is on screen.
|
|
172
|
+
forgetPlan();
|
|
158
173
|
if (run.status === 'failed') {
|
|
159
174
|
store.setError(run.errors?.[0] ?? 'The import failed. Its history entry has the detail.');
|
|
160
175
|
}
|
|
@@ -41,6 +41,21 @@ export type ConnectionFieldsProps = {
|
|
|
41
41
|
};
|
|
42
42
|
export declare const emptyConnectionDraft: ConnectionDraft;
|
|
43
43
|
export declare function draftFromSource(source: IntegrationSource): ConnectionDraft;
|
|
44
|
+
/**
|
|
45
|
+
* The boxes a SAVED record should correct, and only those.
|
|
46
|
+
*
|
|
47
|
+
* After an edit the form has to show what was actually stored — the server
|
|
48
|
+
* trims, defaults and ignores things, and the boxes went on showing what was
|
|
49
|
+
* typed. But re-deriving the whole draft from the reply is not safe: a
|
|
50
|
+
* field-scoped patch claim strips columns out of the response, and
|
|
51
|
+
* {@link draftFromSource} substitutes a default for every column it does not
|
|
52
|
+
* find. A reply that legitimately omits `name` would therefore blank the name
|
|
53
|
+
* box — trading a stale screen for a destroyed one.
|
|
54
|
+
*
|
|
55
|
+
* So this reads the reply key by key and says nothing about a key that is not
|
|
56
|
+
* there. What it returns is a patch, applied over the draft the operator has.
|
|
57
|
+
*/
|
|
58
|
+
export declare function draftPatchFromSource(source: Partial<IntegrationSource>): Partial<ConnectionDraft>;
|
|
44
59
|
export declare function usesHeaderName(authType: IntegrationAuthType): boolean;
|
|
45
60
|
export declare function usesQueryParam(authType: IntegrationAuthType): boolean;
|
|
46
61
|
export declare function usesOauth(authType: IntegrationAuthType): boolean;
|
|
@@ -45,6 +45,47 @@ export function draftFromSource(source) {
|
|
|
45
45
|
timeoutMs: source.timeoutMs == null ? '' : String(source.timeoutMs)
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The boxes a SAVED record should correct, and only those.
|
|
50
|
+
*
|
|
51
|
+
* After an edit the form has to show what was actually stored — the server
|
|
52
|
+
* trims, defaults and ignores things, and the boxes went on showing what was
|
|
53
|
+
* typed. But re-deriving the whole draft from the reply is not safe: a
|
|
54
|
+
* field-scoped patch claim strips columns out of the response, and
|
|
55
|
+
* {@link draftFromSource} substitutes a default for every column it does not
|
|
56
|
+
* find. A reply that legitimately omits `name` would therefore blank the name
|
|
57
|
+
* box — trading a stale screen for a destroyed one.
|
|
58
|
+
*
|
|
59
|
+
* So this reads the reply key by key and says nothing about a key that is not
|
|
60
|
+
* there. What it returns is a patch, applied over the draft the operator has.
|
|
61
|
+
*/ export function draftPatchFromSource(source) {
|
|
62
|
+
const full = draftFromSource(source);
|
|
63
|
+
const patch = {};
|
|
64
|
+
const carry = (key, column)=>{
|
|
65
|
+
if (column in source && source[column] !== undefined) patch[key] = full[key];
|
|
66
|
+
};
|
|
67
|
+
carry('name', 'name');
|
|
68
|
+
carry('slug', 'slug');
|
|
69
|
+
carry('baseUrl', 'baseUrl');
|
|
70
|
+
carry('description', 'description');
|
|
71
|
+
carry('enabled', 'enabled');
|
|
72
|
+
carry('authType', 'authType');
|
|
73
|
+
carry('authHeaderName', 'authHeaderName');
|
|
74
|
+
carry('authQueryParam', 'authQueryParam');
|
|
75
|
+
carry('tokenUrl', 'tokenUrl');
|
|
76
|
+
carry('oauthGrantType', 'oauthGrantType');
|
|
77
|
+
carry('oauthClientId', 'oauthClientId');
|
|
78
|
+
carry('oauthUsername', 'oauthUsername');
|
|
79
|
+
carry('oauthScope', 'oauthScope');
|
|
80
|
+
carry('oauthClientAuth', 'oauthClientAuth');
|
|
81
|
+
carry('tlsVerify', 'tlsVerify');
|
|
82
|
+
// `timeoutMs` is the reason this exists at all: emptying the box omits it
|
|
83
|
+
// from the patch, the server keeps its stored value, and the box has to be
|
|
84
|
+
// told. `null` is a real answer here — it means "no timeout set" — so the
|
|
85
|
+
// presence of the KEY is what is checked, not its truthiness.
|
|
86
|
+
if ('timeoutMs' in source) patch.timeoutMs = full.timeoutMs;
|
|
87
|
+
return patch;
|
|
88
|
+
}
|
|
48
89
|
// ── which extras an auth type actually uses ─────────────────────────────────
|
|
49
90
|
export function usesHeaderName(authType) {
|
|
50
91
|
return authType === 'bearer' || authType === 'api_key';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { describe, expect, test } from 'bun:test';
|
|
2
|
-
import { draftFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
|
|
1
|
+
import { describe, expect, it, test } from 'bun:test';
|
|
2
|
+
import { draftFromSource, draftPatchFromSource, draftToSource, emptyConnectionDraft, validateConnectionDraft } from './connectionDraft';
|
|
3
3
|
/**
|
|
4
4
|
* Found in production, creating the first connection: the form said "Empty
|
|
5
5
|
* means the server default" under the timeout, and saving with it empty
|
|
@@ -131,3 +131,68 @@ describe('draftFromSource', ()=>{
|
|
|
131
131
|
expect(draftToSource(draftFromSource(source), 'a').timeoutMs).toBe(30000);
|
|
132
132
|
});
|
|
133
133
|
});
|
|
134
|
+
/**
|
|
135
|
+
* What a save is allowed to correct in the boxes.
|
|
136
|
+
*
|
|
137
|
+
* After an edit the form showed what was TYPED, not what was stored: emptying
|
|
138
|
+
* the timeout left the box empty while the connection kept timing out at the
|
|
139
|
+
* stored value, and a trailing space in the name was trimmed on the way out so
|
|
140
|
+
* the switcher above and the box below disagreed until F5. The reply has to be
|
|
141
|
+
* read back — but only the parts of it that are actually there, because a
|
|
142
|
+
* field-scoped patch claim strips columns out, and `draftFromSource` fills a
|
|
143
|
+
* default for every column it does not find. Blanking a required box is worse
|
|
144
|
+
* than leaving it stale.
|
|
145
|
+
*/ describe('the boxes a saved record corrects', ()=>{
|
|
146
|
+
const stored = {
|
|
147
|
+
id: 's1',
|
|
148
|
+
name: 'Acme',
|
|
149
|
+
slug: 'acme',
|
|
150
|
+
baseUrl: 'https://acme.test',
|
|
151
|
+
timeoutMs: 30000
|
|
152
|
+
};
|
|
153
|
+
it('takes the server’s value for a field it answered with', ()=>{
|
|
154
|
+
// Typed "Acme " with a trailing space; stored trimmed.
|
|
155
|
+
expect(draftPatchFromSource(stored).name).toBe('Acme');
|
|
156
|
+
});
|
|
157
|
+
/** The case this exists for: the box says empty, the server says 30000. */ it('corrects a timeout the server kept when the box was emptied', ()=>{
|
|
158
|
+
expect(draftPatchFromSource(stored).timeoutMs).toBe('30000');
|
|
159
|
+
});
|
|
160
|
+
it('says nothing at all about a field the reply left out', ()=>{
|
|
161
|
+
const patch = draftPatchFromSource({
|
|
162
|
+
id: 's1',
|
|
163
|
+
name: 'Acme'
|
|
164
|
+
});
|
|
165
|
+
expect(patch).toEqual({
|
|
166
|
+
name: 'Acme'
|
|
167
|
+
});
|
|
168
|
+
expect('slug' in patch).toBe(false);
|
|
169
|
+
expect('timeoutMs' in patch).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
/**
|
|
172
|
+
* The failure this shape prevents: applied over a real draft, a partial reply
|
|
173
|
+
* must correct what it mentions and leave everything else alone.
|
|
174
|
+
*/ it('leaves the rest of the draft untouched when applied over it', ()=>{
|
|
175
|
+
const draft = {
|
|
176
|
+
...emptyConnectionDraft,
|
|
177
|
+
name: 'Acme ',
|
|
178
|
+
slug: 'acme',
|
|
179
|
+
baseUrl: 'https://a.test'
|
|
180
|
+
};
|
|
181
|
+
const next = {
|
|
182
|
+
...draft,
|
|
183
|
+
...draftPatchFromSource({
|
|
184
|
+
id: 's1',
|
|
185
|
+
name: 'Acme'
|
|
186
|
+
})
|
|
187
|
+
};
|
|
188
|
+
expect(next.name).toBe('Acme');
|
|
189
|
+
expect(next.slug).toBe('acme');
|
|
190
|
+
expect(next.baseUrl).toBe('https://a.test');
|
|
191
|
+
});
|
|
192
|
+
/** null is an answer — "no timeout" — not an absence. */ it('treats a null the server returned as a value, not as silence', ()=>{
|
|
193
|
+
expect(draftPatchFromSource({
|
|
194
|
+
id: 's1',
|
|
195
|
+
timeoutMs: null
|
|
196
|
+
}).timeoutMs).toBe('');
|
|
197
|
+
});
|
|
198
|
+
});
|