@ibgib/space-gib 0.0.3 → 0.0.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/CHANGELOG.md +4 -0
- package/IMPLEMENTATION.md +9 -13
- package/dist/client/bootstrap.mjs +1 -1
- package/dist/client/bootstrap.mjs.map +1 -1
- package/dist/client/chunk-NCXKCVYS.mjs +42 -0
- package/dist/client/chunk-NCXKCVYS.mjs.map +7 -0
- package/dist/client/chunk-ZUEU37Z5.mjs +1920 -0
- package/dist/client/chunk-ZUEU37Z5.mjs.map +7 -0
- package/dist/client/index.html +103 -5
- package/dist/client/index.mjs +1 -1
- package/dist/client/script.mjs +1 -1
- package/dist/client/style.css +466 -61
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +294 -225
- package/dist/server/server.mjs.map +2 -2
- package/package.json +6 -6
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/components/identity-header/IMPLEMENTATION.md +45 -0
- package/src/client/components/identity-header/identity-header.css +74 -0
- package/src/client/components/identity-header/identity-header.html +10 -0
- package/src/client/components/identity-header/identity-header.mts +361 -0
- package/src/client/components/identity-manager/IMPLEMENTATION.md +100 -0
- package/src/client/components/identity-manager/identity-manager.css +467 -0
- package/src/client/components/identity-manager/identity-manager.html +113 -0
- package/src/client/components/identity-manager/identity-manager.mts +767 -0
- package/src/client/components/keystone-creator/keystone-creator.css +2 -76
- package/src/client/components/keystone-creator/keystone-creator.html +41 -26
- package/src/client/components/keystone-creator/keystone-creator.mts +178 -41
- package/src/client/dev-tools/base-tools.mts +252 -0
- package/src/client/dev-tools/common.mts +217 -0
- package/src/client/dev-tools/phase-1.mts +156 -0
- package/src/client/dev-tools/phase-2.mts +143 -0
- package/src/client/dev-tools/phase-3.mts +189 -0
- package/src/client/dev-tools/phase-4-1.mts +197 -0
- package/src/client/dev-tools/phase-4-10.mts +884 -0
- package/src/client/dev-tools/phase-4-2.mts +388 -0
- package/src/client/dev-tools/phase-4-3.mts +391 -0
- package/src/client/dev-tools/phase-4-4.mts +374 -0
- package/src/client/dev-tools/phase-4-5.mts +376 -0
- package/src/client/dev-tools/phase-4-6.mts +273 -0
- package/src/client/dev-tools/phase-4-7.mts +399 -0
- package/src/client/dev-tools/phase-4-8.mts +430 -0
- package/src/client/dev-tools/phase-4-9.mts +398 -0
- package/src/client/dev-tools/phase-4.mts +1302 -0
- package/src/client/dev-tools.mts +52 -1194
- package/src/client/index.html +103 -5
- package/src/client/style.css +466 -61
- package/src/client/ui/shell/space-gib-shell-constants.mts +0 -2
- package/src/client/ui/shell/space-gib-shell-service.mts +82 -10
- package/src/common/common-constants.mts +0 -0
- package/src/common/keystone-policies.json +40 -43
- package/src/common/keystone-policies.mts +3 -5
- package/src/server/path-helper.respec.mts +99 -94
- package/src/server/serve-gib/README.md +9 -0
- package/src/server/serve-gib/handlers/api/keystone/keystone-genesis.handler.mts +1 -1
- package/src/server/serve-gib/handlers/api/keystone/keystone-get.respec.mts +1 -1
- package/src/server/serve-gib/handlers/ws/sync-upgrade-handler-base.mts +31 -3
- package/src/server/serve-gib/handlers/ws/ws-helper.mts +73 -45
- package/dist/client/chunk-2KJC5XKE.mjs +0 -31
- package/dist/client/chunk-2KJC5XKE.mjs.map +0 -7
- package/dist/client/chunk-QNIXTRFO.mjs +0 -235
- package/dist/client/chunk-QNIXTRFO.mjs.map +0 -7
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
import styleCss from "../../style.css";
|
|
2
|
+
import thisCss from "./identity-manager.css";
|
|
3
|
+
import thisHtml from "./identity-manager.html";
|
|
4
|
+
|
|
5
|
+
import { extractErrorMsg, pretty } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
|
|
6
|
+
import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
|
|
7
|
+
import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
|
|
8
|
+
import { getIbGibAddr } from "@ibgib/ts-gib/dist/helper.mjs";
|
|
9
|
+
import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
|
|
10
|
+
import { KeystoneService_V1 } from "@ibgib/core-gib/dist/keystone/keystone-service-v1.mjs";
|
|
11
|
+
import { KeystoneIbGib_V1 } from "@ibgib/core-gib/dist/keystone/keystone-types.mjs";
|
|
12
|
+
import {
|
|
13
|
+
IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
|
|
14
|
+
} from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
|
|
15
|
+
import {
|
|
16
|
+
ElementsBase, IbGibDynamicComponentInstance,
|
|
17
|
+
IbGibDynamicComponentInstanceInitOpts,
|
|
18
|
+
} from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
|
|
19
|
+
import { SettingsType } from "@ibgib/web-gib/dist/common/settings/settings-constants.mjs";
|
|
20
|
+
import { Settings_General } from "@ibgib/web-gib/dist/common/settings/settings-types.mjs";
|
|
21
|
+
import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
|
|
22
|
+
import { EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, EVENT_IBGIB_IDENTITY_CHANGED } from "@ibgib/web-gib/dist/ui/ui-constants.mjs";
|
|
23
|
+
|
|
24
|
+
import { getComponentCtorArg, getIbGibGlobalThis_SpaceGib } from "../../helpers.web.mjs";
|
|
25
|
+
import { APP_CONFIG } from "../../constants.mjs";
|
|
26
|
+
import { devLog } from "../../dev-tools.mjs";
|
|
27
|
+
|
|
28
|
+
export const IDENTITY_MANAGER_COMPONENT_NAME = 'ibgib-identity-manager';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Metadata for the Identity Manager component.
|
|
32
|
+
*/
|
|
33
|
+
export class IdentityManagerComponentMeta extends IbGibDynamicComponentMetaBase {
|
|
34
|
+
protected lc: string = `[IdentityManagerComponentMeta]`;
|
|
35
|
+
|
|
36
|
+
routeRegExp?: RegExp = new RegExp(`^${IDENTITY_MANAGER_COMPONENT_NAME}$`);
|
|
37
|
+
componentName = IDENTITY_MANAGER_COMPONENT_NAME;
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
super(getComponentCtorArg());
|
|
41
|
+
if (!customElements.get(this.componentName)) {
|
|
42
|
+
customElements.define(this.componentName, IdentityManagerComponentInstance);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async createInstance({
|
|
47
|
+
path,
|
|
48
|
+
ibGibAddr
|
|
49
|
+
}: {
|
|
50
|
+
path: string;
|
|
51
|
+
ibGibAddr: IbGibAddr;
|
|
52
|
+
}): Promise<IbGibDynamicComponentInstance> {
|
|
53
|
+
const lc = `${this.lc}[${this.createInstance.name}]`;
|
|
54
|
+
const component = document.createElement(this.componentName) as IdentityManagerComponentInstance;
|
|
55
|
+
await component.initialize({
|
|
56
|
+
ibGibAddr,
|
|
57
|
+
meta: this,
|
|
58
|
+
html: thisHtml,
|
|
59
|
+
css: [styleCss, thisCss],
|
|
60
|
+
});
|
|
61
|
+
return component;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* References to DOM elements within the component's shadow root.
|
|
67
|
+
*/
|
|
68
|
+
interface IdentityManagerElements extends ElementsBase {
|
|
69
|
+
containerEl: HTMLDivElement;
|
|
70
|
+
titleEl: HTMLHeadingElement;
|
|
71
|
+
tabsListEl: HTMLDivElement;
|
|
72
|
+
btnAddIdentityEl: HTMLButtonElement;
|
|
73
|
+
noIdentityPlaceholderEl: HTMLDivElement;
|
|
74
|
+
btnCreatePlaceholderEl: HTMLButtonElement;
|
|
75
|
+
identityDetailsViewEl: HTMLDivElement;
|
|
76
|
+
identityAddrEl: HTMLElement;
|
|
77
|
+
identityGenEl: HTMLElement;
|
|
78
|
+
identityTimestampEl: HTMLElement;
|
|
79
|
+
identityUuidEl: HTMLElement;
|
|
80
|
+
identityNameEl: HTMLElement;
|
|
81
|
+
identityDescriptionEl: HTMLElement;
|
|
82
|
+
identityFrameDetailsEl: HTMLPreElement;
|
|
83
|
+
identityAggrDetailsEl: HTMLPreElement;
|
|
84
|
+
|
|
85
|
+
// Scrubber elements
|
|
86
|
+
btnScrubFirstEl: HTMLButtonElement;
|
|
87
|
+
btnScrubPrev10El: HTMLButtonElement;
|
|
88
|
+
btnScrubPrevEl: HTMLButtonElement;
|
|
89
|
+
scrubberStatusEl: HTMLSpanElement;
|
|
90
|
+
btnScrubNextEl: HTMLButtonElement;
|
|
91
|
+
btnScrubNext10El: HTMLButtonElement;
|
|
92
|
+
btnScrubLatestEl: HTMLButtonElement;
|
|
93
|
+
|
|
94
|
+
poolsContainerEl: HTMLDivElement;
|
|
95
|
+
btnVerifyChainEl: HTMLButtonElement;
|
|
96
|
+
// btnSyncIdentityEl: HTMLButtonElement;
|
|
97
|
+
btnSetActiveEl: HTMLButtonElement;
|
|
98
|
+
verificationStatusEl: HTMLDivElement;
|
|
99
|
+
replicasListEl: HTMLDivElement;
|
|
100
|
+
// inputReplicaUrlEl: HTMLInputElement;
|
|
101
|
+
// btnAddReplicaEl: HTMLButtonElement;
|
|
102
|
+
rawAccordionHeaderEl: HTMLDivElement;
|
|
103
|
+
rawAccordionContentEl: HTMLDivElement;
|
|
104
|
+
rawKeystoneJsonEl: HTMLPreElement;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Concrete implementation of the Identity Manager component.
|
|
109
|
+
*/
|
|
110
|
+
export class IdentityManagerComponentInstance
|
|
111
|
+
extends IbGibDynamicComponentInstanceBase<IbGib_V1, IdentityManagerElements>
|
|
112
|
+
implements IbGibDynamicComponentInstance<IbGib_V1, IdentityManagerElements> {
|
|
113
|
+
|
|
114
|
+
protected lc: string = `[IdentityManagerComponentInstance]`;
|
|
115
|
+
|
|
116
|
+
private activeAddr: string | null = null;
|
|
117
|
+
private globalActiveAddr: string | null = null;
|
|
118
|
+
private _onIdentityChanged: ((e: any) => void) | null = null;
|
|
119
|
+
private history: any[] = [];
|
|
120
|
+
private currentFrameIndex: number = -1;
|
|
121
|
+
private lastLoadedHistoryAddr: string | null = null;
|
|
122
|
+
private keystoneCache: Map<string, { username: string, description: string }> = new Map();
|
|
123
|
+
|
|
124
|
+
constructor() {
|
|
125
|
+
super();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async getKeystoneDetails(addr: string): Promise<{ username: string, description: string }> {
|
|
129
|
+
const cached = this.keystoneCache.get(addr);
|
|
130
|
+
if (cached) return cached;
|
|
131
|
+
|
|
132
|
+
const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
|
|
133
|
+
if (!metaspace) return { username: '', description: '' };
|
|
134
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
135
|
+
if (!space) return { username: '', description: '' };
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const res = await metaspace.get({ addr, space });
|
|
139
|
+
if (res.success && res.ibGibs && res.ibGibs.length > 0) {
|
|
140
|
+
const keystone = res.ibGibs[0] as KeystoneIbGib_V1;
|
|
141
|
+
const keystoneSvc = new KeystoneService_V1();
|
|
142
|
+
const aggregated = await keystoneSvc.getAggregateDetails({
|
|
143
|
+
latestKeystone: keystone,
|
|
144
|
+
metaspace,
|
|
145
|
+
space
|
|
146
|
+
});
|
|
147
|
+
const username = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '';
|
|
148
|
+
const description = aggregated?.description || aggregated?.profile?.description || '';
|
|
149
|
+
const details = { username, description };
|
|
150
|
+
this.keystoneCache.set(addr, details);
|
|
151
|
+
return details;
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.warn(`Error getting details for keystone ${addr}: ${extractErrorMsg(err)}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { username: '', description: '' };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override async initialize(opts: IbGibDynamicComponentInstanceInitOpts): Promise<void> {
|
|
161
|
+
await super.initialize(opts);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override async created(): Promise<void> {
|
|
165
|
+
this.elements = {} as IdentityManagerElements;
|
|
166
|
+
|
|
167
|
+
const shadow = this.shadowRoot!;
|
|
168
|
+
this.elements.containerEl = shadow.getElementById('container') as HTMLDivElement;
|
|
169
|
+
this.elements.titleEl = shadow.getElementById('title') as HTMLHeadingElement;
|
|
170
|
+
this.elements.tabsListEl = shadow.getElementById('identity-tabs') as HTMLDivElement;
|
|
171
|
+
this.elements.btnAddIdentityEl = shadow.getElementById('btn-add-identity') as HTMLButtonElement;
|
|
172
|
+
this.elements.noIdentityPlaceholderEl = shadow.getElementById('no-identity-placeholder') as HTMLDivElement;
|
|
173
|
+
this.elements.btnCreatePlaceholderEl = shadow.getElementById('btn-create-placeholder') as HTMLButtonElement;
|
|
174
|
+
this.elements.identityDetailsViewEl = shadow.getElementById('identity-details-view') as HTMLDivElement;
|
|
175
|
+
this.elements.identityAddrEl = shadow.getElementById('identity-addr') as HTMLElement;
|
|
176
|
+
this.elements.identityGenEl = shadow.getElementById('identity-gen') as HTMLSpanElement;
|
|
177
|
+
this.elements.identityTimestampEl = shadow.getElementById('identity-timestamp') as HTMLSpanElement;
|
|
178
|
+
this.elements.identityUuidEl = shadow.getElementById('identity-uuid') as HTMLSpanElement;
|
|
179
|
+
this.elements.identityNameEl = shadow.getElementById('identity-name') as HTMLElement;
|
|
180
|
+
this.elements.identityDescriptionEl = shadow.getElementById('identity-description') as HTMLElement;
|
|
181
|
+
this.elements.identityFrameDetailsEl = shadow.getElementById('identity-frame-details') as HTMLPreElement;
|
|
182
|
+
this.elements.identityAggrDetailsEl = shadow.getElementById('identity-aggr-details') as HTMLPreElement;
|
|
183
|
+
|
|
184
|
+
// Scrubber elements
|
|
185
|
+
this.elements.btnScrubFirstEl = shadow.getElementById('btn-scrub-first') as HTMLButtonElement;
|
|
186
|
+
this.elements.btnScrubPrev10El = shadow.getElementById('btn-scrub-prev10') as HTMLButtonElement;
|
|
187
|
+
this.elements.btnScrubPrevEl = shadow.getElementById('btn-scrub-prev') as HTMLButtonElement;
|
|
188
|
+
this.elements.scrubberStatusEl = shadow.getElementById('scrubber-status') as HTMLSpanElement;
|
|
189
|
+
this.elements.btnScrubNextEl = shadow.getElementById('btn-scrub-next') as HTMLButtonElement;
|
|
190
|
+
this.elements.btnScrubNext10El = shadow.getElementById('btn-scrub-next10') as HTMLButtonElement;
|
|
191
|
+
this.elements.btnScrubLatestEl = shadow.getElementById('btn-scrub-latest') as HTMLButtonElement;
|
|
192
|
+
|
|
193
|
+
this.elements.poolsContainerEl = shadow.getElementById('pools-container') as HTMLDivElement;
|
|
194
|
+
this.elements.btnVerifyChainEl = shadow.getElementById('btn-verify-chain') as HTMLButtonElement;
|
|
195
|
+
// this.elements.btnSyncIdentityEl = shadow.getElementById('btn-sync-identity') as HTMLButtonElement;
|
|
196
|
+
this.elements.btnSetActiveEl = shadow.getElementById('btn-set-active') as HTMLButtonElement;
|
|
197
|
+
this.elements.verificationStatusEl = shadow.getElementById('verification-status') as HTMLDivElement;
|
|
198
|
+
this.elements.replicasListEl = shadow.getElementById('replicas-list') as HTMLDivElement;
|
|
199
|
+
// this.elements.inputReplicaUrlEl = shadow.getElementById('input-replica-url') as HTMLInputElement;
|
|
200
|
+
// this.elements.btnAddReplicaEl = shadow.getElementById('btn-add-replica') as HTMLButtonElement;
|
|
201
|
+
this.elements.rawAccordionHeaderEl = shadow.getElementById('raw-accordion-header') as HTMLDivElement;
|
|
202
|
+
this.elements.rawAccordionContentEl = shadow.getElementById('raw-accordion-content') as HTMLDivElement;
|
|
203
|
+
this.elements.rawKeystoneJsonEl = shadow.getElementById('raw-keystone-json') as HTMLPreElement;
|
|
204
|
+
|
|
205
|
+
// ElementBase requires contentEl (points to the main container/body area)
|
|
206
|
+
this.elements.contentEl = shadow.getElementById('identity-manager-content') as HTMLDivElement;
|
|
207
|
+
|
|
208
|
+
this.initHandlers();
|
|
209
|
+
|
|
210
|
+
// Subscribe to global active identity changes
|
|
211
|
+
this._onIdentityChanged = (e: any) => {
|
|
212
|
+
this.globalActiveAddr = e.detail?.activeIdentityAddr || null;
|
|
213
|
+
this.renderUI();
|
|
214
|
+
};
|
|
215
|
+
window.addEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
|
|
216
|
+
|
|
217
|
+
if (this.ibGibAddr === ROOT_ADDR || !this.ibGib) {
|
|
218
|
+
await this.loadKeystonesIndex();
|
|
219
|
+
} else {
|
|
220
|
+
await this.initSettings();
|
|
221
|
+
}
|
|
222
|
+
await this.renderUI();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
override async disconnected(): Promise<void> {
|
|
226
|
+
if (this._onIdentityChanged) {
|
|
227
|
+
window.removeEventListener(EVENT_IBGIB_IDENTITY_CHANGED, this._onIdentityChanged);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private async loadKeystonesIndex(): Promise<void> {
|
|
232
|
+
const lc = `${this.lc}[loadKeystonesIndex]`;
|
|
233
|
+
try {
|
|
234
|
+
const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
|
|
235
|
+
if (!metaspace) { return; }
|
|
236
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
237
|
+
if (!space) { return; }
|
|
238
|
+
const keystonesIndex = await metaspace.getSpecialIbGib({
|
|
239
|
+
type: "keystones",
|
|
240
|
+
space,
|
|
241
|
+
initialize: true,
|
|
242
|
+
});
|
|
243
|
+
if (keystonesIndex) {
|
|
244
|
+
this.ibGibAddr = getIbGibAddr({ ibGib: keystonesIndex });
|
|
245
|
+
await this.loadIbGib();
|
|
246
|
+
await this.initSettings();
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
private initHandlers() {
|
|
256
|
+
// Setup raw JSON accordion toggle
|
|
257
|
+
this.elements!.rawAccordionHeaderEl.addEventListener('click', () => this.toggleAccordion());
|
|
258
|
+
|
|
259
|
+
// Buttons
|
|
260
|
+
this.elements!.btnAddIdentityEl.addEventListener('click', () => this.handleAddNewIdentityFlow());
|
|
261
|
+
this.elements!.btnCreatePlaceholderEl.addEventListener('click', () => this.handleAddNewIdentityFlow());
|
|
262
|
+
this.elements!.btnVerifyChainEl.addEventListener('click', () => this.handleVerifyIdentityChain());
|
|
263
|
+
// this.elements!.btnSyncIdentityEl.addEventListener('click', () => this.handleSyncIdentity());
|
|
264
|
+
this.elements!.btnSetActiveEl.addEventListener('click', () => this.handleSetActiveIdentity());
|
|
265
|
+
// this.elements!.btnAddReplicaEl.addEventListener('click', () => this.handleAddReplicaSpace());
|
|
266
|
+
|
|
267
|
+
// Scrubber Buttons
|
|
268
|
+
this.elements!.btnScrubFirstEl.addEventListener('click', () => this.scrubFirst());
|
|
269
|
+
this.elements!.btnScrubPrev10El.addEventListener('click', () => this.scrubPrev10());
|
|
270
|
+
this.elements!.btnScrubPrevEl.addEventListener('click', () => this.scrubPrev());
|
|
271
|
+
this.elements!.btnScrubNextEl.addEventListener('click', () => this.scrubNext());
|
|
272
|
+
this.elements!.btnScrubNext10El.addEventListener('click', () => this.scrubNext10());
|
|
273
|
+
this.elements!.btnScrubLatestEl.addEventListener('click', () => this.scrubLatest());
|
|
274
|
+
|
|
275
|
+
// Convert vertical mouse wheel scrolling into horizontal scroll for tabs
|
|
276
|
+
this.elements!.tabsListEl.addEventListener('wheel', (e: WheelEvent) => {
|
|
277
|
+
if (e.deltaY !== 0) {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
// this.elements!.tabsListEl.scrollLeft += e.deltaY;
|
|
280
|
+
this.elements!.tabsListEl.scrollBy({ left: e.deltaY, behavior: 'smooth' });
|
|
281
|
+
}
|
|
282
|
+
}, { passive: false });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private toggleAccordion() {
|
|
286
|
+
const header = this.elements!.rawAccordionHeaderEl;
|
|
287
|
+
const content = this.elements!.rawAccordionContentEl;
|
|
288
|
+
header.classList.toggle('expanded');
|
|
289
|
+
content.classList.toggle('expanded');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
protected async handleAddNewIdentityFlow() {
|
|
293
|
+
const lc = `${this.lc}[${this.handleAddNewIdentityFlow.name}]`;
|
|
294
|
+
devLog(`${lc} Triggering identity creation/import flow...`);
|
|
295
|
+
try {
|
|
296
|
+
const componentSvc = await getComponentSvc();
|
|
297
|
+
const creator = await componentSvc.getComponentInstance({
|
|
298
|
+
path: 'ibgib-keystone-creator',
|
|
299
|
+
ibGibAddr: ROOT_ADDR,
|
|
300
|
+
useRegExpPrefilter: true,
|
|
301
|
+
});
|
|
302
|
+
if (creator) {
|
|
303
|
+
// Hide placeholder and details panel
|
|
304
|
+
this.elements!.noIdentityPlaceholderEl.classList.add('hidden');
|
|
305
|
+
this.elements!.identityDetailsViewEl.classList.add('hidden');
|
|
306
|
+
|
|
307
|
+
// Clear any previous creator
|
|
308
|
+
const existing = this.elements!.contentEl.querySelector('ibgib-keystone-creator');
|
|
309
|
+
if (existing) { existing.remove(); }
|
|
310
|
+
|
|
311
|
+
this.elements!.contentEl.appendChild(creator as any);
|
|
312
|
+
devLog(`${lc} Mounted ibgib-keystone-creator successfully.`);
|
|
313
|
+
} else {
|
|
314
|
+
throw new Error("Could not find meta for ibgib-keystone-creator");
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
318
|
+
this.setVerificationStatus(`Failed to load creator: ${extractErrorMsg(error)}`, "error");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
protected async handleVerifyIdentityChain() {
|
|
323
|
+
const lc = `${this.lc}[${this.handleVerifyIdentityChain.name}]`;
|
|
324
|
+
devLog(`${lc} Verifying identity keystone timeline...`);
|
|
325
|
+
this.setVerificationStatus("Verification request sent...", "info");
|
|
326
|
+
try {
|
|
327
|
+
// Skeleton logic placeholder
|
|
328
|
+
this.setVerificationStatus("Verification complete. Root Keystone is verified (Genesis phase 1).", "success");
|
|
329
|
+
} catch (error) {
|
|
330
|
+
this.setVerificationStatus(`Verification failed: ${extractErrorMsg(error)}`, "error");
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// leave this in
|
|
335
|
+
// protected async handleSyncIdentity() {
|
|
336
|
+
// const lc = `${this.lc}[${this.handleSyncIdentity.name}]`;
|
|
337
|
+
// devLog(`${lc} Starting Peer-to-Peer sync saga...`);
|
|
338
|
+
// this.setVerificationStatus("Starting synchronization session...", "info");
|
|
339
|
+
// }
|
|
340
|
+
|
|
341
|
+
// leave this in
|
|
342
|
+
// protected async handleAddReplicaSpace() {
|
|
343
|
+
// const lc = `${this.lc}[${this.handleAddReplicaSpace.name}]`;
|
|
344
|
+
// const url = this.elements!.inputReplicaUrlEl.value;
|
|
345
|
+
// if (!url) {
|
|
346
|
+
// this.setVerificationStatus("Please enter a valid space replication URL.", "error");
|
|
347
|
+
// return;
|
|
348
|
+
// }
|
|
349
|
+
// devLog(`${lc} Registering replica space: ${url}`);
|
|
350
|
+
|
|
351
|
+
// // Add to UI list for verification
|
|
352
|
+
// const replicaItem = document.createElement('div');
|
|
353
|
+
// replicaItem.className = 'address-code';
|
|
354
|
+
// replicaItem.style.marginTop = '0.5rem';
|
|
355
|
+
// replicaItem.textContent = url;
|
|
356
|
+
// this.elements!.replicasListEl.appendChild(replicaItem);
|
|
357
|
+
|
|
358
|
+
// this.elements!.inputReplicaUrlEl.value = '';
|
|
359
|
+
// this.setVerificationStatus(`Registered replica endpoint: ${url}`, "success");
|
|
360
|
+
// }
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* dispatches the event to set {@link activeAddr} (if truthy) as the active
|
|
364
|
+
* identity. IOW, this "logs it in".
|
|
365
|
+
*/
|
|
366
|
+
protected async handleSetActiveIdentity() {
|
|
367
|
+
if (!this.activeAddr) { return; }
|
|
368
|
+
window.dispatchEvent(new CustomEvent(EVENT_IBGIB_IDENTITY_REQUEST_CHANGE, {
|
|
369
|
+
detail: { activeIdentityAddr: this.activeAddr },
|
|
370
|
+
bubbles: true,
|
|
371
|
+
composed: true
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private setVerificationStatus(msg: string, type: 'info' | 'success' | 'error') {
|
|
376
|
+
const statusEl = this.elements!.verificationStatusEl;
|
|
377
|
+
statusEl.textContent = msg;
|
|
378
|
+
statusEl.className = `status-msg ${type}`;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private async viewIdentityDetails(addr: string): Promise<void> {
|
|
382
|
+
this.activeAddr = addr;
|
|
383
|
+
await this.renderUI();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private async resolveActiveIdentity() {
|
|
387
|
+
const lc = `${this.lc}[resolveActiveIdentity]`;
|
|
388
|
+
try {
|
|
389
|
+
if (!this.settings) {
|
|
390
|
+
this.activeAddr = null;
|
|
391
|
+
this.globalActiveAddr = null;
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const generalSettings = await this.getSettings<Settings_General>({
|
|
395
|
+
settingsType: SettingsType.general,
|
|
396
|
+
useCase: 'current',
|
|
397
|
+
});
|
|
398
|
+
const activeAddrInSettings = (generalSettings as any)?.activeIdentityAddr;
|
|
399
|
+
|
|
400
|
+
const registeredKeystones = this.ibGib?.rel8ns?.keystone ?? [];
|
|
401
|
+
if (activeAddrInSettings && registeredKeystones.includes(activeAddrInSettings)) {
|
|
402
|
+
this.globalActiveAddr = activeAddrInSettings;
|
|
403
|
+
} else if (registeredKeystones.length > 0) {
|
|
404
|
+
this.globalActiveAddr = registeredKeystones[0];
|
|
405
|
+
} else {
|
|
406
|
+
this.globalActiveAddr = null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!this.activeAddr) {
|
|
410
|
+
this.activeAddr = this.globalActiveAddr;
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
414
|
+
this.globalActiveAddr = null;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
protected override async renderUI(): Promise<void> {
|
|
419
|
+
const lc = `${this.lc}[${this.renderUI.name}]`;
|
|
420
|
+
if (!this.elements) { return; }
|
|
421
|
+
|
|
422
|
+
// always resolve the current active identity, even if we aren't
|
|
423
|
+
// necessarily rendering the details for that identity (if it isn't
|
|
424
|
+
// this.activeAddr)
|
|
425
|
+
await this.resolveActiveIdentity();
|
|
426
|
+
|
|
427
|
+
const keystones = this.ibGib?.rel8ns?.keystone ?? [];
|
|
428
|
+
|
|
429
|
+
if (keystones.length === 0) {
|
|
430
|
+
this.elements.tabsListEl.innerHTML = '';
|
|
431
|
+
this.elements.noIdentityPlaceholderEl.classList.remove('hidden');
|
|
432
|
+
this.elements.identityDetailsViewEl.classList.add('hidden');
|
|
433
|
+
|
|
434
|
+
const existing = this.elements.contentEl.querySelector('ibgib-keystone-creator');
|
|
435
|
+
if (existing && !this.elements.noIdentityPlaceholderEl.classList.contains('hidden')) {
|
|
436
|
+
existing.remove();
|
|
437
|
+
}
|
|
438
|
+
return; /* <<<< returns early */
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
this.elements.noIdentityPlaceholderEl.classList.add('hidden');
|
|
442
|
+
|
|
443
|
+
// Render Tabs
|
|
444
|
+
this.elements.tabsListEl.innerHTML = '';
|
|
445
|
+
for (const addr of keystones) {
|
|
446
|
+
const tabEl = document.createElement('button');
|
|
447
|
+
tabEl.className = `tab-btn ${addr === this.activeAddr ? 'active' : ''} ${addr === this.globalActiveAddr ? 'global-active' : ''}`;
|
|
448
|
+
|
|
449
|
+
// Initial fallback label and tooltip
|
|
450
|
+
let displayTab = addr;
|
|
451
|
+
if (displayTab.length > 12) {
|
|
452
|
+
displayTab = displayTab.substring(0, 5) + '...' + displayTab.substring(displayTab.length - 4);
|
|
453
|
+
}
|
|
454
|
+
tabEl.textContent = displayTab;
|
|
455
|
+
tabEl.title = addr;
|
|
456
|
+
|
|
457
|
+
// Fetch details and update tab text/tooltip
|
|
458
|
+
const details = await this.getKeystoneDetails(addr);
|
|
459
|
+
let name = details.username;
|
|
460
|
+
const maxTabLen = 16;
|
|
461
|
+
if (name) {
|
|
462
|
+
if (name.length > maxTabLen) {
|
|
463
|
+
name = name.substring(0, maxTabLen - 3) + '...';
|
|
464
|
+
}
|
|
465
|
+
tabEl.textContent = name;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const tooltipParts: string[] = [];
|
|
469
|
+
if (details.username) {
|
|
470
|
+
tooltipParts.push(`User: ${details.username}`);
|
|
471
|
+
}
|
|
472
|
+
if (details.description) {
|
|
473
|
+
const desc = details.description.length > 100
|
|
474
|
+
? details.description.substring(0, 97) + '...'
|
|
475
|
+
: details.description;
|
|
476
|
+
tooltipParts.push(`Desc: ${desc}`);
|
|
477
|
+
}
|
|
478
|
+
tooltipParts.push(`Addr: ${addr}`);
|
|
479
|
+
tabEl.title = tooltipParts.join('\n');
|
|
480
|
+
|
|
481
|
+
tabEl.addEventListener('click', async () => {
|
|
482
|
+
await this.viewIdentityDetails(addr);
|
|
483
|
+
});
|
|
484
|
+
this.elements.tabsListEl.appendChild(tabEl);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (this.activeAddr) {
|
|
488
|
+
const existing = this.elements.contentEl.querySelector('ibgib-keystone-creator');
|
|
489
|
+
if (existing) { existing.remove(); }
|
|
490
|
+
|
|
491
|
+
this.elements.identityDetailsViewEl.classList.remove('hidden');
|
|
492
|
+
|
|
493
|
+
// Update Set Active button state
|
|
494
|
+
if (this.activeAddr === this.globalActiveAddr) {
|
|
495
|
+
this.elements.btnSetActiveEl.disabled = true;
|
|
496
|
+
this.elements.btnSetActiveEl.textContent = "Active Identity";
|
|
497
|
+
this.elements.btnSetActiveEl.classList.add('active-primary');
|
|
498
|
+
} else {
|
|
499
|
+
this.elements.btnSetActiveEl.disabled = false;
|
|
500
|
+
this.elements.btnSetActiveEl.textContent = "Set as Active Identity";
|
|
501
|
+
this.elements.btnSetActiveEl.classList.remove('active-primary');
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (this.activeAddr !== this.lastLoadedHistoryAddr) {
|
|
505
|
+
await this.loadHistory(this.activeAddr);
|
|
506
|
+
this.lastLoadedHistoryAddr = this.activeAddr;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
await this.updateFrameDetailsView();
|
|
510
|
+
} else {
|
|
511
|
+
this.elements.identityDetailsViewEl.classList.add('hidden');
|
|
512
|
+
this.clearActiveDetails();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
private clearActiveDetails() {
|
|
517
|
+
if (!this.elements) return;
|
|
518
|
+
this.elements.identityGenEl.textContent = '-';
|
|
519
|
+
this.elements.identityTimestampEl.textContent = '-';
|
|
520
|
+
this.elements.identityUuidEl.textContent = '-';
|
|
521
|
+
this.elements.identityNameEl.textContent = '-';
|
|
522
|
+
this.elements.identityDescriptionEl.textContent = '-';
|
|
523
|
+
this.elements.identityFrameDetailsEl.textContent = '{}';
|
|
524
|
+
this.elements.identityAggrDetailsEl.textContent = '{}';
|
|
525
|
+
this.elements.scrubberStatusEl.textContent = 'Frame - / -';
|
|
526
|
+
this.elements.poolsContainerEl.innerHTML = '';
|
|
527
|
+
this.history = [];
|
|
528
|
+
this.currentFrameIndex = -1;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private async loadHistory(tipAddr: string): Promise<void> {
|
|
532
|
+
const lc = `${this.lc}[loadHistory]`;
|
|
533
|
+
this.history = [];
|
|
534
|
+
this.currentFrameIndex = -1;
|
|
535
|
+
|
|
536
|
+
const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
|
|
537
|
+
if (!metaspace) return;
|
|
538
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
539
|
+
if (!space) return;
|
|
540
|
+
|
|
541
|
+
let currentAddr: string | undefined = tipAddr;
|
|
542
|
+
const tempHistory: KeystoneIbGib_V1[] = [];
|
|
543
|
+
|
|
544
|
+
while (currentAddr) {
|
|
545
|
+
const res = await metaspace.get({ addr: currentAddr, space });
|
|
546
|
+
if (!res.success || !res.ibGibs || res.ibGibs.length === 0) {
|
|
547
|
+
console.warn(`${lc} Could not find frame at ${currentAddr}`);
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
const keystone = res.ibGibs[0] as KeystoneIbGib_V1;
|
|
551
|
+
tempHistory.push(keystone);
|
|
552
|
+
|
|
553
|
+
if (keystone.data?.n === 0) {
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const past = keystone.rel8ns?.past;
|
|
558
|
+
currentAddr = (past && past.length > 0) ? past[past.length - 1] : undefined;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
this.history = tempHistory.reverse();
|
|
562
|
+
this.currentFrameIndex = this.history.length - 1;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
private scrubTo(index: number) {
|
|
566
|
+
if (this.history.length === 0) return;
|
|
567
|
+
this.currentFrameIndex = Math.max(0, Math.min(index, this.history.length - 1));
|
|
568
|
+
this.updateFrameDetailsView();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private scrubFirst() {
|
|
572
|
+
this.scrubTo(0);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private scrubPrev10() {
|
|
576
|
+
this.scrubTo(this.currentFrameIndex - 10);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
private scrubPrev() {
|
|
580
|
+
this.scrubTo(this.currentFrameIndex - 1);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private scrubNext() {
|
|
584
|
+
this.scrubTo(this.currentFrameIndex + 1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private scrubNext10() {
|
|
588
|
+
this.scrubTo(this.currentFrameIndex + 10);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
private scrubLatest() {
|
|
592
|
+
this.scrubTo(this.history.length - 1);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
private async updateFrameDetailsView(): Promise<void> {
|
|
596
|
+
const lc = `${this.lc}[updateFrameDetailsView]`;
|
|
597
|
+
if (!this.elements) return;
|
|
598
|
+
|
|
599
|
+
if (this.history.length === 0 || this.currentFrameIndex < 0 || this.currentFrameIndex >= this.history.length) {
|
|
600
|
+
this.clearActiveDetails();
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const keystone = this.history[this.currentFrameIndex];
|
|
605
|
+
const keystoneAddr = getIbGibAddr({ ibGib: keystone });
|
|
606
|
+
|
|
607
|
+
// Update Verifiable Address
|
|
608
|
+
this.elements.identityAddrEl.textContent = keystoneAddr;
|
|
609
|
+
|
|
610
|
+
// Update Generation
|
|
611
|
+
this.elements.identityGenEl.textContent = keystone.data?.n !== undefined ? String(keystone.data.n) : '0 (Genesis)';
|
|
612
|
+
|
|
613
|
+
// Update Timestamp
|
|
614
|
+
const timestampStr = keystone.data?.timestamp;
|
|
615
|
+
let formattedTimestamp = 'N/A';
|
|
616
|
+
if (timestampStr) {
|
|
617
|
+
const parsed = parseInt(timestampStr);
|
|
618
|
+
if (!isNaN(parsed)) {
|
|
619
|
+
formattedTimestamp = new Date(parsed).toLocaleString();
|
|
620
|
+
} else {
|
|
621
|
+
formattedTimestamp = new Date(timestampStr).toLocaleString();
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
this.elements.identityTimestampEl.textContent = formattedTimestamp;
|
|
625
|
+
|
|
626
|
+
// Update UUID
|
|
627
|
+
this.elements.identityUuidEl.textContent = keystone.data?.uuid || 'N/A';
|
|
628
|
+
|
|
629
|
+
// Update Scrubber Status
|
|
630
|
+
this.elements.scrubberStatusEl.textContent = `Frame ${this.currentFrameIndex + 1} / ${this.history.length}`;
|
|
631
|
+
|
|
632
|
+
// Disable/enable scrubber buttons based on boundaries
|
|
633
|
+
this.elements.btnScrubFirstEl.disabled = this.currentFrameIndex === 0;
|
|
634
|
+
this.elements.btnScrubPrev10El.disabled = this.currentFrameIndex === 0;
|
|
635
|
+
this.elements.btnScrubPrevEl.disabled = this.currentFrameIndex === 0;
|
|
636
|
+
this.elements.btnScrubNextEl.disabled = this.currentFrameIndex === this.history.length - 1;
|
|
637
|
+
this.elements.btnScrubNext10El.disabled = this.currentFrameIndex === this.history.length - 1;
|
|
638
|
+
this.elements.btnScrubLatestEl.disabled = this.currentFrameIndex === this.history.length - 1;
|
|
639
|
+
|
|
640
|
+
// Show Frame Details
|
|
641
|
+
const frameDetails = keystone.data?.frameDetails;
|
|
642
|
+
this.elements.identityFrameDetailsEl.textContent = frameDetails ? pretty(frameDetails) : '{}';
|
|
643
|
+
|
|
644
|
+
// Get and Show Aggregated details up to the scrolled-to frame
|
|
645
|
+
const metaspace = getIbGibGlobalThis_SpaceGib(APP_CONFIG).metaspace;
|
|
646
|
+
if (metaspace) {
|
|
647
|
+
const space = await metaspace.getLocalUserSpace({ lock: false });
|
|
648
|
+
if (space) {
|
|
649
|
+
try {
|
|
650
|
+
const keystoneSvc = new KeystoneService_V1();
|
|
651
|
+
const aggregated = await keystoneSvc.getAggregateDetails({
|
|
652
|
+
latestKeystone: keystone,
|
|
653
|
+
metaspace,
|
|
654
|
+
space
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
this.elements.identityAggrDetailsEl.textContent = aggregated ? pretty(aggregated) : '{}';
|
|
658
|
+
|
|
659
|
+
// Update Username and Description fields
|
|
660
|
+
this.elements.identityNameEl.textContent = aggregated?.username || aggregated?.profile?.name || aggregated?.name || '-';
|
|
661
|
+
this.elements.identityDescriptionEl.textContent = aggregated?.description || aggregated?.profile?.description || '-';
|
|
662
|
+
} catch (err) {
|
|
663
|
+
console.warn(`${lc} Error aggregating details: ${extractErrorMsg(err)}`);
|
|
664
|
+
this.elements.identityAggrDetailsEl.textContent = '{}';
|
|
665
|
+
this.elements.identityNameEl.textContent = '-';
|
|
666
|
+
this.elements.identityDescriptionEl.textContent = '-';
|
|
667
|
+
}
|
|
668
|
+
} else {
|
|
669
|
+
this.elements.identityAggrDetailsEl.textContent = '{}';
|
|
670
|
+
this.elements.identityNameEl.textContent = '-';
|
|
671
|
+
this.elements.identityDescriptionEl.textContent = '-';
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
this.elements.identityAggrDetailsEl.textContent = '{}';
|
|
675
|
+
this.elements.identityNameEl.textContent = '-';
|
|
676
|
+
this.elements.identityDescriptionEl.textContent = '-';
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Render Raw Timeline JSON
|
|
680
|
+
this.elements.rawKeystoneJsonEl.textContent = pretty(keystone);
|
|
681
|
+
|
|
682
|
+
// Render Challenge Pools
|
|
683
|
+
this.renderChallengePools(keystone.data?.challengePools || []);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
private renderChallengePools(pools: any[]) {
|
|
687
|
+
if (!this.elements) return;
|
|
688
|
+
const container = this.elements.poolsContainerEl;
|
|
689
|
+
container.innerHTML = '';
|
|
690
|
+
|
|
691
|
+
if (!pools || pools.length === 0) {
|
|
692
|
+
container.innerHTML = `<p class="description">No challenge pools found in this keystone.</p>`;
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
for (const pool of pools) {
|
|
697
|
+
const card = document.createElement('div');
|
|
698
|
+
card.className = 'pool-card';
|
|
699
|
+
|
|
700
|
+
const config = pool.config || {};
|
|
701
|
+
const behavior = config.behavior || {};
|
|
702
|
+
const allowedVerbs = config.allowedVerbs || [];
|
|
703
|
+
const activeCount = pool.challenges ? Object.keys(pool.challenges).length : 0;
|
|
704
|
+
const isForeign = !!pool.isForeign;
|
|
705
|
+
|
|
706
|
+
// Generate HTML for the challenges list
|
|
707
|
+
const challenges = pool.challenges || {};
|
|
708
|
+
const challengeListHtml = Object.entries(challenges).map(([id, challenge]: [string, any]) => {
|
|
709
|
+
const truncatedHash = challenge.hash && challenge.hash.length > 20
|
|
710
|
+
? challenge.hash.substring(0, 10) + '...' + challenge.hash.substring(challenge.hash.length - 8)
|
|
711
|
+
: challenge.hash || 'N/A';
|
|
712
|
+
return `
|
|
713
|
+
<div class="pool-challenge-item">
|
|
714
|
+
<span class="challenge-id">${id}</span>
|
|
715
|
+
<span class="challenge-hash" title="${challenge.hash || ''}">${truncatedHash}</span>
|
|
716
|
+
</div>
|
|
717
|
+
`;
|
|
718
|
+
}).join('');
|
|
719
|
+
|
|
720
|
+
card.innerHTML = `
|
|
721
|
+
<div class="pool-header">
|
|
722
|
+
<span class="pool-title">${pool.id || 'Unnamed Pool'}</span>
|
|
723
|
+
<span class="pool-tag ${isForeign ? 'foreign' : 'native'}">${isForeign ? 'Foreign' : 'Native'}</span>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="pool-grid">
|
|
726
|
+
<div class="pool-field">
|
|
727
|
+
<label>Challenge Type</label>
|
|
728
|
+
<span>${config.type || 'N/A'}</span>
|
|
729
|
+
</div>
|
|
730
|
+
<div class="pool-field">
|
|
731
|
+
<label>Target Size</label>
|
|
732
|
+
<span>${behavior.size ?? 'N/A'}</span>
|
|
733
|
+
</div>
|
|
734
|
+
<div class="pool-field">
|
|
735
|
+
<label>Active Challenges</label>
|
|
736
|
+
<span>${activeCount}</span>
|
|
737
|
+
</div>
|
|
738
|
+
<div class="pool-field">
|
|
739
|
+
<label>Replenish Strategy</label>
|
|
740
|
+
<span>${behavior.replenish || 'N/A'}</span>
|
|
741
|
+
</div>
|
|
742
|
+
<div class="pool-field">
|
|
743
|
+
<label>Allowed Verbs</label>
|
|
744
|
+
<span>${allowedVerbs.length > 0 ? allowedVerbs.join(', ') : 'Any'}</span>
|
|
745
|
+
</div>
|
|
746
|
+
<div class="pool-field">
|
|
747
|
+
<label>FIFO / Random cost</label>
|
|
748
|
+
<span>FIFO: ${behavior.selectSequentially ?? 0}, Rand: ${behavior.selectRandomly ?? 0}</span>
|
|
749
|
+
</div>
|
|
750
|
+
</div>
|
|
751
|
+
|
|
752
|
+
<!-- Collapsible Challenges List -->
|
|
753
|
+
<details class="pool-details">
|
|
754
|
+
<summary class="pool-details-summary">Challenges (${activeCount})</summary>
|
|
755
|
+
<div class="pool-challenges-list">
|
|
756
|
+
${challengeListHtml || '<p class="placeholder-text" style="padding: 0.5rem; text-align: center;">No challenges active.</p>'}
|
|
757
|
+
</div>
|
|
758
|
+
</details>
|
|
759
|
+
`;
|
|
760
|
+
container.appendChild(card);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
override async handleContextUpdated(): Promise<void> {
|
|
765
|
+
await this.renderUI();
|
|
766
|
+
}
|
|
767
|
+
}
|