ultravisor 1.0.25 → 1.0.26
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/docs/_sidebar.md +1 -0
- package/docs/features/persistence-via-databeacon.md +1211 -0
- package/package.json +6 -6
- package/source/cli/Ultravisor-CLIProgram.cjs +62 -0
- package/source/config/Ultravisor-Default-Command-Configuration.cjs +9 -1
- package/source/persistence/UltravisorPersistenceSchema.json +240 -0
- package/source/services/Ultravisor-AuthBeaconBridge.cjs +271 -0
- package/source/services/Ultravisor-Beacon-Coordinator.cjs +242 -149
- package/source/services/Ultravisor-Beacon-Scheduler.cjs +65 -29
- package/source/services/Ultravisor-ExecutionManifest.cjs +99 -4
- package/source/services/Ultravisor-FleetManager.cjs +19 -1
- package/source/services/Ultravisor-ManifestStoreBridge.cjs +1134 -0
- package/source/services/Ultravisor-QueuePersistenceBridge.cjs +1336 -0
- package/source/web_server/Ultravisor-API-Server.cjs +951 -90
- package/webinterface/package.json +1 -0
- package/webinterface/source/Pict-Application-Ultravisor.js +57 -2
- package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +8 -0
- package/webinterface/source/views/PictView-Ultravisor-Login.js +74 -0
- package/webinterface/source/views/PictView-Ultravisor-TopBar.js +25 -0
- package/webinterface/source/views/PictView-Ultravisor-UserManagement.js +159 -0
|
@@ -31,7 +31,10 @@ const libViewBeaconList = require('./views/PictView-Ultravisor-BeaconList.js');
|
|
|
31
31
|
const libViewReachabilityMap = require('./views/PictView-Ultravisor-ReachabilityMap.js');
|
|
32
32
|
const libViewOperationDescriptionEditor = require('./views/PictView-Ultravisor-OperationDescriptionEditor.js');
|
|
33
33
|
const libViewFleet = require('./views/PictView-Ultravisor-Fleet.js');
|
|
34
|
+
const libViewLogin = require('./views/PictView-Ultravisor-Login.js');
|
|
35
|
+
const libViewUserManagement = require('./views/PictView-Ultravisor-UserManagement.js');
|
|
34
36
|
const libPictSectionModal = require('pict-section-modal');
|
|
37
|
+
const libPictSectionUserManagement = require('pict-section-usermanagement');
|
|
35
38
|
|
|
36
39
|
class UltravisorApplication extends libPictApplication
|
|
37
40
|
{
|
|
@@ -68,9 +71,36 @@ class UltravisorApplication extends libPictApplication
|
|
|
68
71
|
this.pict.addView('Ultravisor-ReachabilityMap', libViewReachabilityMap.default_configuration, libViewReachabilityMap);
|
|
69
72
|
this.pict.addView('Ultravisor-OperationDescriptionEditor', libViewOperationDescriptionEditor.default_configuration, libViewOperationDescriptionEditor);
|
|
70
73
|
this.pict.addView('Ultravisor-Fleet', libViewFleet.default_configuration, libViewFleet);
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
this.pict.addView('Ultravisor-Login', libViewLogin.default_configuration, libViewLogin);
|
|
75
|
+
this.pict.addView('Ultravisor-UserManagement', libViewUserManagement.default_configuration, libViewUserManagement);
|
|
76
|
+
|
|
77
|
+
// Modal/toast notification system (replaces browser alert/confirm).
|
|
78
|
+
// Two registrations: 'Modal' is the legacy hash existing call sites
|
|
79
|
+
// use; 'Pict-Section-Modal' is what pict-section-usermanagement's
|
|
80
|
+
// confirm/toast hooks look up. Both addView calls produce
|
|
81
|
+
// independent instances of the same section, which is fine because
|
|
82
|
+
// the modal section is stateless across constructions.
|
|
73
83
|
this.pict.addView('Modal', {}, libPictSectionModal);
|
|
84
|
+
this.pict.addView('Pict-Section-Modal', {}, libPictSectionModal);
|
|
85
|
+
|
|
86
|
+
// pict-section-usermanagement — install() registers the provider
|
|
87
|
+
// + 5 views (Login, CurrentUser, UserList, UserEdit,
|
|
88
|
+
// PasswordChange). Default browser fetch() is the transport,
|
|
89
|
+
// hitting orator-authentication's /1.0/Authenticate + the
|
|
90
|
+
// auth-beacon /Users routes mounted by ultravisor's API server.
|
|
91
|
+
// OnLogin / OnLogout hooks let us re-render the topbar and
|
|
92
|
+
// reroute on session-state flips without each view subscribing
|
|
93
|
+
// to AppData.
|
|
94
|
+
let tmpSelf = this;
|
|
95
|
+
libPictSectionUserManagement.install(this.pict,
|
|
96
|
+
{
|
|
97
|
+
ProviderOptions: { BaseURL: '/1.0/' },
|
|
98
|
+
ViewOptions:
|
|
99
|
+
{
|
|
100
|
+
Login: { OnLogin: () => tmpSelf._afterLogin() },
|
|
101
|
+
CurrentUser: { OnLogout: () => tmpSelf._afterLogout() }
|
|
102
|
+
}
|
|
103
|
+
});
|
|
74
104
|
|
|
75
105
|
// Register pict-section-form service types so Form panels can use them
|
|
76
106
|
this.pict.addServiceType('PictFormMetacontroller', libPictSectionForm.PictFormMetacontroller);
|
|
@@ -139,6 +169,31 @@ class UltravisorApplication extends libPictApplication
|
|
|
139
169
|
this.pict.providers.PictRouter.navigate(pRoute);
|
|
140
170
|
}
|
|
141
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Hook fired by pict-section-usermanagement's Login view after a
|
|
174
|
+
* successful sign-in. Re-render the TopBar so the CurrentUser badge
|
|
175
|
+
* appears, then bounce to the dashboard so the user lands on a
|
|
176
|
+
* useful page rather than the login form's success message.
|
|
177
|
+
*/
|
|
178
|
+
_afterLogin()
|
|
179
|
+
{
|
|
180
|
+
let tmpTopBar = this.pict.views['Ultravisor-TopBar'];
|
|
181
|
+
if (tmpTopBar) tmpTopBar.render();
|
|
182
|
+
this.navigateTo('/Home');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Hook fired by pict-section-usermanagement's CurrentUser view
|
|
187
|
+
* after a logout. Drop back to /Login so it's obvious the session
|
|
188
|
+
* is gone (vs. seeing a stale dashboard with no badge).
|
|
189
|
+
*/
|
|
190
|
+
_afterLogout()
|
|
191
|
+
{
|
|
192
|
+
let tmpTopBar = this.pict.views['Ultravisor-TopBar'];
|
|
193
|
+
if (tmpTopBar) tmpTopBar.render();
|
|
194
|
+
this.navigateTo('/Login');
|
|
195
|
+
}
|
|
196
|
+
|
|
142
197
|
showView(pViewIdentifier)
|
|
143
198
|
{
|
|
144
199
|
if (pViewIdentifier in this.pict.views)
|
|
@@ -61,6 +61,14 @@
|
|
|
61
61
|
{
|
|
62
62
|
"path": "/Docs",
|
|
63
63
|
"template": "{~LV:Pict.PictApplication.showView(`Ultravisor-Documentation`)~}"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"path": "/Login",
|
|
67
|
+
"template": "{~LV:Pict.PictApplication.showView(`Ultravisor-Login`)~}"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"path": "/Users",
|
|
71
|
+
"template": "{~LV:Pict.PictApplication.showView(`Ultravisor-UserManagement`)~}"
|
|
64
72
|
}
|
|
65
73
|
]
|
|
66
74
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PictView-Ultravisor-Login
|
|
3
|
+
*
|
|
4
|
+
* Thin host wrapper around `pict-section-usermanagement`'s Login
|
|
5
|
+
* view. The section's Login renders into `#PictUM-Login`; this view
|
|
6
|
+
* just provides that mount point inside ultravisor's main content
|
|
7
|
+
* panel and delegates to the section view's render.
|
|
8
|
+
*
|
|
9
|
+
* The section's Login carries its own theme-neutral CSS, so this
|
|
10
|
+
* view doesn't need to restyle the form — but does add a small
|
|
11
|
+
* "Ultravisor — sign in" page heading so the page doesn't feel
|
|
12
|
+
* floating.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const libPictView = require('pict-view');
|
|
16
|
+
|
|
17
|
+
const _ViewConfiguration =
|
|
18
|
+
{
|
|
19
|
+
ViewIdentifier: 'Ultravisor-Login',
|
|
20
|
+
AutoInitialize: true,
|
|
21
|
+
AutoRender: false,
|
|
22
|
+
|
|
23
|
+
DefaultRenderable: 'Ultravisor-Login-Content',
|
|
24
|
+
DefaultDestinationAddress: '#Ultravisor-Content-Container',
|
|
25
|
+
|
|
26
|
+
Templates:
|
|
27
|
+
[
|
|
28
|
+
{
|
|
29
|
+
Hash: 'Ultravisor-Login-Template',
|
|
30
|
+
Template: /*html*/`
|
|
31
|
+
<div class="ultravisor-login-page">
|
|
32
|
+
<div id="PictUM-Login"></div>
|
|
33
|
+
</div>`
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
|
|
37
|
+
Renderables:
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
RenderableHash: 'Ultravisor-Login-Content',
|
|
41
|
+
TemplateHash: 'Ultravisor-Login-Template',
|
|
42
|
+
DestinationAddress: '#Ultravisor-Content-Container',
|
|
43
|
+
RenderMethod: 'replace'
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
|
|
47
|
+
CSS: /*css*/`
|
|
48
|
+
.ultravisor-login-page {
|
|
49
|
+
min-height: calc(100vh - 56px);
|
|
50
|
+
display: flex; align-items: center; justify-content: center;
|
|
51
|
+
padding: 32px 16px;
|
|
52
|
+
}
|
|
53
|
+
`
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
class UltravisorLoginView extends libPictView
|
|
57
|
+
{
|
|
58
|
+
onAfterRender(pRenderable, pAddress, pRecord, pContent)
|
|
59
|
+
{
|
|
60
|
+
// Render the section's Login view into the mount point we just
|
|
61
|
+
// painted. The section view tracks its own destination via
|
|
62
|
+
// DefaultDestinationAddress: '#PictUM-Login', so a plain render()
|
|
63
|
+
// call is enough.
|
|
64
|
+
let tmpInner = this.pict && this.pict.views && this.pict.views['PictUM-Login'];
|
|
65
|
+
if (tmpInner) tmpInner.render();
|
|
66
|
+
this.pict.CSSMap.injectCSS();
|
|
67
|
+
return super.onAfterRender
|
|
68
|
+
? super.onAfterRender(pRenderable, pAddress, pRecord, pContent)
|
|
69
|
+
: undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = UltravisorLoginView;
|
|
74
|
+
module.exports.default_configuration = _ViewConfiguration;
|
|
@@ -219,9 +219,15 @@ const _ViewConfiguration =
|
|
|
219
219
|
<a onclick="{~P~}.PictApplication.navigateTo('/Manifests')">Manifests</a>
|
|
220
220
|
<a onclick="{~P~}.PictApplication.navigateTo('/Beacons')">Beacons</a>
|
|
221
221
|
<a onclick="{~P~}.PictApplication.navigateTo('/Fleet')">Fleet</a>
|
|
222
|
+
<a onclick="{~P~}.PictApplication.navigateTo('/Users')">Users</a>
|
|
222
223
|
<a onclick="{~P~}.PictApplication.navigateTo('/Docs')">Docs</a>
|
|
223
224
|
</div>
|
|
224
225
|
<div class="ultravisor-topbar-right">
|
|
226
|
+
<!-- pict-section-usermanagement's CurrentUser badge renders here.
|
|
227
|
+
The view tracks #PictUM-CurrentUser as its destination;
|
|
228
|
+
onAfterRender() below calls its render() so the badge
|
|
229
|
+
appears alongside the status indicator. -->
|
|
230
|
+
<span id="PictUM-CurrentUser"></span>
|
|
225
231
|
<div class="ultravisor-topbar-status" id="Ultravisor-TopBar-StatusArea"></div>
|
|
226
232
|
<div class="ultravisor-settings-wrap">
|
|
227
233
|
<button class="ultravisor-settings-gear" onclick="{~P~}.views['Ultravisor-TopBar'].toggleThemePanel()" title="Settings">
|
|
@@ -287,6 +293,25 @@ class UltravisorTopBarView extends libPictView
|
|
|
287
293
|
// Render theme swatches
|
|
288
294
|
this._renderThemeGrid();
|
|
289
295
|
|
|
296
|
+
// Render the user-management current-user badge into our slot.
|
|
297
|
+
// On first paint there's no session yet — the section view
|
|
298
|
+
// drives a CheckSession on its own first render. After that
|
|
299
|
+
// it just paints from AppData.UserManagement.CurrentUser.
|
|
300
|
+
let tmpCurrentUserView = this.pict.views['PictUM-CurrentUser'];
|
|
301
|
+
if (tmpCurrentUserView)
|
|
302
|
+
{
|
|
303
|
+
let tmpProvider = this.pict.providers['Pict-UserManagement-Provider'];
|
|
304
|
+
if (tmpProvider && !this._didInitialSessionCheck)
|
|
305
|
+
{
|
|
306
|
+
this._didInitialSessionCheck = true;
|
|
307
|
+
tmpProvider.checkSession(() => tmpCurrentUserView.render());
|
|
308
|
+
}
|
|
309
|
+
else
|
|
310
|
+
{
|
|
311
|
+
tmpCurrentUserView.render();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
290
315
|
// Set up click-outside-to-close handler
|
|
291
316
|
if (this._boundCloseHandler)
|
|
292
317
|
{
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PictView-Ultravisor-UserManagement
|
|
3
|
+
*
|
|
4
|
+
* Hosts the user-management surface inside ultravisor's main content
|
|
5
|
+
* panel. Composes three section views:
|
|
6
|
+
*
|
|
7
|
+
* - PictUM-UserList (admin: search/list/edit/delete users)
|
|
8
|
+
* - PictUM-UserEdit (admin: create/edit user form)
|
|
9
|
+
* - PictUM-PasswordChange (any session: self change-password)
|
|
10
|
+
*
|
|
11
|
+
* The section's CurrentUser badge is rendered in the TopBar, not here
|
|
12
|
+
* — the page itself doesn't need its own session indicator.
|
|
13
|
+
*
|
|
14
|
+
* Sub-tabs (Users | Change my password) keep the admin and self
|
|
15
|
+
* surfaces separate. A non-admin landing on /Users sees only the
|
|
16
|
+
* password tab; the admin tab self-hides via the section's 401/403
|
|
17
|
+
* error path.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const libPictView = require('pict-view');
|
|
21
|
+
|
|
22
|
+
const _ViewConfiguration =
|
|
23
|
+
{
|
|
24
|
+
ViewIdentifier: 'Ultravisor-UserManagement',
|
|
25
|
+
AutoInitialize: true,
|
|
26
|
+
AutoRender: false,
|
|
27
|
+
|
|
28
|
+
DefaultRenderable: 'Ultravisor-UserManagement-Content',
|
|
29
|
+
DefaultDestinationAddress: '#Ultravisor-Content-Container',
|
|
30
|
+
|
|
31
|
+
Templates:
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
Hash: 'Ultravisor-UserManagement-Template',
|
|
35
|
+
Template: /*html*/`
|
|
36
|
+
<div class="ultravisor-um-page">
|
|
37
|
+
<div class="ultravisor-um-tabs">
|
|
38
|
+
<button type="button" class="ultravisor-um-tab" id="UV-UM-Tab-Users"
|
|
39
|
+
onclick="{~P~}.views['Ultravisor-UserManagement'].switchTab('users')">Users</button>
|
|
40
|
+
<button type="button" class="ultravisor-um-tab" id="UV-UM-Tab-Pwd"
|
|
41
|
+
onclick="{~P~}.views['Ultravisor-UserManagement'].switchTab('change-password')">Change my password</button>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="ultravisor-um-content">
|
|
44
|
+
<div id="UV-UM-Pane-Users">
|
|
45
|
+
<div id="PictUM-UserList"></div>
|
|
46
|
+
<div id="PictUM-UserEdit" style="margin-top: 16px;"></div>
|
|
47
|
+
</div>
|
|
48
|
+
<div id="UV-UM-Pane-Pwd" style="display:none;">
|
|
49
|
+
<div id="PictUM-PasswordChange"></div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>`
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
|
|
56
|
+
Renderables:
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
RenderableHash: 'Ultravisor-UserManagement-Content',
|
|
60
|
+
TemplateHash: 'Ultravisor-UserManagement-Template',
|
|
61
|
+
DestinationAddress: '#Ultravisor-Content-Container',
|
|
62
|
+
RenderMethod: 'replace'
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
|
|
66
|
+
CSS: /*css*/`
|
|
67
|
+
.ultravisor-um-page { padding: 24px; }
|
|
68
|
+
.ultravisor-um-tabs {
|
|
69
|
+
display: flex; gap: 4px; margin-bottom: 16px;
|
|
70
|
+
border-bottom: 1px solid var(--uv-border, #2a2a2a);
|
|
71
|
+
}
|
|
72
|
+
.ultravisor-um-tab {
|
|
73
|
+
background: transparent; border: 0; cursor: pointer;
|
|
74
|
+
padding: 10px 18px; color: var(--uv-text-tertiary, #888);
|
|
75
|
+
font-size: 14px; font-weight: 500;
|
|
76
|
+
border-bottom: 2px solid transparent;
|
|
77
|
+
}
|
|
78
|
+
.ultravisor-um-tab:hover { color: var(--uv-text, #ddd); }
|
|
79
|
+
.ultravisor-um-tab-active {
|
|
80
|
+
color: var(--uv-text, #ddd);
|
|
81
|
+
border-bottom-color: var(--uv-brand, #2563eb);
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
class UltravisorUserManagementView extends libPictView
|
|
87
|
+
{
|
|
88
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
89
|
+
{
|
|
90
|
+
super(pFable, pOptions, pServiceHash);
|
|
91
|
+
this._activeTab = 'users';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
switchTab(pTab)
|
|
95
|
+
{
|
|
96
|
+
this._activeTab = pTab;
|
|
97
|
+
this._refreshTabState();
|
|
98
|
+
// Render the section view that's now visible. Other panes stay
|
|
99
|
+
// in the DOM (display:none) so their form state survives toggles.
|
|
100
|
+
let tmpProvider = this._provider();
|
|
101
|
+
if (pTab === 'users' && tmpProvider) { tmpProvider.loadUsers(); }
|
|
102
|
+
this._renderChildren();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
onAfterRender(pRenderable, pAddress, pRecord, pContent)
|
|
106
|
+
{
|
|
107
|
+
this._refreshTabState();
|
|
108
|
+
// Kick a load so the list paints on first arrival; the section's
|
|
109
|
+
// UserList itself doesn't auto-fetch.
|
|
110
|
+
let tmpProvider = this._provider();
|
|
111
|
+
if (this._activeTab === 'users' && tmpProvider)
|
|
112
|
+
{
|
|
113
|
+
tmpProvider.loadUsers(() => this._renderChildren());
|
|
114
|
+
}
|
|
115
|
+
this._renderChildren();
|
|
116
|
+
this.pict.CSSMap.injectCSS();
|
|
117
|
+
return super.onAfterRender
|
|
118
|
+
? super.onAfterRender(pRenderable, pAddress, pRecord, pContent)
|
|
119
|
+
: undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_refreshTabState()
|
|
123
|
+
{
|
|
124
|
+
let tmpUsersTab = document.getElementById('UV-UM-Tab-Users');
|
|
125
|
+
let tmpPwdTab = document.getElementById('UV-UM-Tab-Pwd');
|
|
126
|
+
let tmpUsersPane = document.getElementById('UV-UM-Pane-Users');
|
|
127
|
+
let tmpPwdPane = document.getElementById('UV-UM-Pane-Pwd');
|
|
128
|
+
if (tmpUsersTab)
|
|
129
|
+
{
|
|
130
|
+
tmpUsersTab.className = 'ultravisor-um-tab' + (this._activeTab === 'users' ? ' ultravisor-um-tab-active' : '');
|
|
131
|
+
}
|
|
132
|
+
if (tmpPwdTab)
|
|
133
|
+
{
|
|
134
|
+
tmpPwdTab.className = 'ultravisor-um-tab' + (this._activeTab === 'change-password' ? ' ultravisor-um-tab-active' : '');
|
|
135
|
+
}
|
|
136
|
+
if (tmpUsersPane) tmpUsersPane.style.display = (this._activeTab === 'users') ? '' : 'none';
|
|
137
|
+
if (tmpPwdPane) tmpPwdPane.style.display = (this._activeTab === 'change-password') ? '' : 'none';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_renderChildren()
|
|
141
|
+
{
|
|
142
|
+
// Render section views into their mount points. Each view tracks
|
|
143
|
+
// its own destination — calling render() is enough.
|
|
144
|
+
let tmpList = this.pict.views['PictUM-UserList'];
|
|
145
|
+
let tmpEdit = this.pict.views['PictUM-UserEdit'];
|
|
146
|
+
let tmpPwd = this.pict.views['PictUM-PasswordChange'];
|
|
147
|
+
if (tmpList) tmpList.render();
|
|
148
|
+
if (tmpEdit) tmpEdit.render();
|
|
149
|
+
if (tmpPwd) tmpPwd.render();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
_provider()
|
|
153
|
+
{
|
|
154
|
+
return this.pict.providers && this.pict.providers['Pict-UserManagement-Provider'];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = UltravisorUserManagementView;
|
|
159
|
+
module.exports.default_configuration = _ViewConfiguration;
|