@mindline/sync 1.0.33 → 1.0.35
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/.vs/VSWorkspaceState.json +1 -1
- package/.vs/slnx.sqlite +0 -0
- package/.vs/sync/FileContentIndex/1fe38d21-0fca-4d45-9e42-8c354c1a37f7.vsidx +0 -0
- package/.vs/sync/v17/.wsuo +0 -0
- package/README.md +11 -6
- package/hybridspa.ts +17 -12
- package/index.d.ts +7 -5
- package/index.ts +1035 -934
- package/package.json +1 -1
- package/.vs/sync/FileContentIndex/0ce1eeba-1f09-47e1-a427-aa0c39a8194a.vsidx +0 -0
package/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//index.ts - published interface - AAD implementations, facade to Mindline Config API
|
|
2
|
+
import * as signalR from "@microsoft/signalr"
|
|
2
3
|
import { IPublicClientApplication, AuthenticationResult } from "@azure/msal-browser"
|
|
3
4
|
import { deserializeArray } from 'class-transformer';
|
|
4
|
-
import { adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet} from './hybridspa';
|
|
5
|
+
import { adminDelete, adminPost, adminsGet, configDelete, configsGet, configPost, configPut, graphConfig, initPost, readerPost, tenantPut, tenantPost, tenantDelete, tenantsGet, workspacesGet } from './hybridspa';
|
|
5
6
|
import { version } from './package.json';
|
|
6
7
|
import users from "./users.json";
|
|
7
8
|
import tenants from "./tenants.json";
|
|
@@ -11,1066 +12,1166 @@ import tasksData from "./tasks";
|
|
|
11
12
|
const FILTER_FIELD = "workspaceIDs";
|
|
12
13
|
// called by unit tests
|
|
13
14
|
export function sum(a: number, b: number): number {
|
|
14
|
-
|
|
15
|
+
return a + b;
|
|
15
16
|
}
|
|
16
|
-
export function helloNpm()
|
|
17
|
-
|
|
17
|
+
export function helloNpm(): string {
|
|
18
|
+
return "hello NPM";
|
|
18
19
|
}
|
|
19
20
|
export class Group {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
id: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
description: string;
|
|
23
24
|
}
|
|
24
25
|
export class User {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
26
|
+
oid: string;
|
|
27
|
+
name: string;
|
|
28
|
+
mail: string;
|
|
29
|
+
authority: string;
|
|
30
|
+
tid: string;
|
|
31
|
+
companyName: string;
|
|
32
|
+
companyDomain: string;
|
|
33
|
+
associatedWorkspaces: string[];
|
|
34
|
+
workspaceIDs: string;
|
|
35
|
+
session: string; // button text
|
|
36
|
+
spacode: string; // to get front end access token
|
|
37
|
+
accessToken: string; // front end access token
|
|
38
|
+
loginHint: string; // to help sign out without prompt
|
|
39
|
+
scopes: string[]; // to detect if incremental consent has happened
|
|
40
|
+
authTS: Date; // timestamp user was authenticated
|
|
41
|
+
constructor() {
|
|
42
|
+
this.oid = "";
|
|
43
|
+
this.name = "";
|
|
44
|
+
this.mail = "";
|
|
45
|
+
this.authority = "https://login.microsoftonline.com/organizations/v2.0";
|
|
46
|
+
this.tid = "";
|
|
47
|
+
this.companyName = "";
|
|
48
|
+
this.companyDomain = "";
|
|
49
|
+
this.associatedWorkspaces = new Array();
|
|
50
|
+
this.workspaceIDs = "";
|
|
51
|
+
this.session = "Sign In";
|
|
52
|
+
this.spacode = "";
|
|
53
|
+
this.accessToken = "";
|
|
54
|
+
this.loginHint = "";
|
|
55
|
+
this.scopes = new Array();
|
|
56
|
+
this.authTS = new Date(0);
|
|
57
|
+
}
|
|
57
58
|
}
|
|
58
59
|
export enum TenantType {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
invalid = 0,
|
|
61
|
+
aad = 1,
|
|
62
|
+
ad = 2,
|
|
63
|
+
googleworkspace = 3
|
|
63
64
|
}
|
|
64
65
|
type TenantTypeStrings = keyof typeof TenantType;
|
|
65
66
|
export enum TenantPermissionType {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
read = 1,
|
|
68
|
+
write = 2,
|
|
69
|
+
notassigned = 3
|
|
69
70
|
}
|
|
70
71
|
export type TenantPermissionTypeStrings = keyof typeof TenantPermissionType;
|
|
71
72
|
export class Tenant {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
73
|
+
tid: string;
|
|
74
|
+
name: string;
|
|
75
|
+
domain: string;
|
|
76
|
+
tenantType: TenantTypeStrings;
|
|
77
|
+
permissionType: TenantPermissionTypeStrings;
|
|
78
|
+
onboarded: string;
|
|
79
|
+
authority: string;
|
|
80
|
+
readServicePrincipal: string;
|
|
81
|
+
writeServicePrincipal: string;
|
|
82
|
+
workspaceIDs: string;
|
|
83
|
+
constructor() {
|
|
84
|
+
this.tid = "";
|
|
85
|
+
this.name = "";
|
|
86
|
+
this.domain = "";
|
|
87
|
+
this.tenantType = "aad";
|
|
88
|
+
this.permissionType = "notassigned";
|
|
89
|
+
this.onboarded = "false";
|
|
90
|
+
this.authority = "https://login.microsoftonline.com/organizations/v2.0";
|
|
91
|
+
this.readServicePrincipal = "";
|
|
92
|
+
this.writeServicePrincipal = "";
|
|
93
|
+
this.workspaceIDs = "";
|
|
94
|
+
}
|
|
94
95
|
}
|
|
95
96
|
export enum TenantConfigType {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
source = 1,
|
|
98
|
+
target = 2,
|
|
99
|
+
sourcetarget = 3
|
|
99
100
|
}
|
|
100
101
|
export type TenantConfigTypeStrings = keyof typeof TenantConfigType;
|
|
101
102
|
export class TenantConfigInfo {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
103
|
+
tid: string;
|
|
104
|
+
sourceGroupId: string;
|
|
105
|
+
sourceGroupName: string;
|
|
106
|
+
configurationTenantType: TenantConfigTypeStrings;
|
|
107
|
+
deltaToken: string;
|
|
108
|
+
filesWritten: number;
|
|
109
|
+
constructor() {
|
|
110
|
+
this.tid = "";
|
|
111
|
+
this.sourceGroupId = "";
|
|
112
|
+
this.sourceGroupName = "";
|
|
113
|
+
this.configurationTenantType = "source";
|
|
114
|
+
this.deltaToken = "";
|
|
115
|
+
this.filesWritten = 0;
|
|
116
|
+
}
|
|
112
117
|
}
|
|
113
118
|
export class Config {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
119
|
+
id: string;
|
|
120
|
+
workspaceId: string;
|
|
121
|
+
name: string;
|
|
122
|
+
description: string;
|
|
123
|
+
tenants: TenantConfigInfo[];
|
|
124
|
+
isEnabled: boolean;
|
|
125
|
+
workspaceIDs: string;
|
|
126
|
+
constructor() {
|
|
127
|
+
this.id = "";
|
|
128
|
+
this.name = "";
|
|
129
|
+
this.description = "";
|
|
130
|
+
this.tenants = new Array();
|
|
131
|
+
this.isEnabled = false;
|
|
132
|
+
this.workspaceIDs = "";
|
|
133
|
+
}
|
|
129
134
|
}
|
|
130
135
|
export class Workspace {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
id: string;
|
|
137
|
+
name: string;
|
|
138
|
+
associatedUsers: string[];
|
|
139
|
+
associatedTenants: string[];
|
|
140
|
+
associatedConfigs: string[];
|
|
141
|
+
constructor() {
|
|
142
|
+
this.id = "";
|
|
143
|
+
this.name = "";
|
|
144
|
+
this.associatedUsers = new Array();
|
|
145
|
+
this.associatedTenants = new Array();
|
|
146
|
+
this.associatedConfigs = new Array();
|
|
147
|
+
}
|
|
143
148
|
}
|
|
144
149
|
// check for localStorage availability
|
|
145
150
|
function storageAvailable(type) {
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
storage = window[type];
|
|
149
|
-
const x = "__storage_test__";
|
|
150
|
-
storage.setItem(x, x);
|
|
151
|
-
storage.removeItem(x);
|
|
152
|
-
return true;
|
|
153
|
-
} catch (e) {
|
|
154
|
-
return (
|
|
155
|
-
e instanceof DOMException &&
|
|
156
|
-
// everything except Firefox
|
|
157
|
-
(e.code === 22 ||
|
|
158
|
-
// Firefox
|
|
159
|
-
e.code === 1014 ||
|
|
160
|
-
// test name field too, because code might not be present
|
|
161
|
-
// everything except Firefox
|
|
162
|
-
e.name === "QuotaExceededError" ||
|
|
163
|
-
// Firefox
|
|
164
|
-
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
|
|
165
|
-
// acknowledge QuotaExceededError only if there's something already stored
|
|
166
|
-
storage &&
|
|
167
|
-
storage.length !== 0
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
export class InitInfo {
|
|
172
|
-
us: User[];
|
|
173
|
-
ts: Tenant[];
|
|
174
|
-
cs: Config[];
|
|
175
|
-
ws: Workspace[];
|
|
176
|
-
constructor(bClearLocalStorage: boolean) {
|
|
177
|
-
this.init(bClearLocalStorage);
|
|
178
|
-
}
|
|
179
|
-
// get initial data from localStorage or file
|
|
180
|
-
init(bClearLocalStorage: boolean): void {
|
|
181
|
-
console.log(`Calling InitInfo::init(bClearLocalStorage: ${bClearLocalStorage?"true":"false"})`);
|
|
182
|
-
// if we have a non-zero value stored, read it from localStorage
|
|
183
|
-
if (storageAvailable("localStorage")) {
|
|
184
|
-
let result = localStorage.getItem("InitInfo");
|
|
185
|
-
if (result != null && typeof result === "string" && result !== "") {
|
|
186
|
-
let initInfoString: string = result;
|
|
187
|
-
let iiReadFromLocalStorage: InitInfo = JSON.parse(initInfoString);
|
|
188
|
-
if (iiReadFromLocalStorage.us.length !== 0) {
|
|
189
|
-
if(bClearLocalStorage) { localStorage.removeItem("InitInfo"); }
|
|
190
|
-
else{
|
|
191
|
-
this.#initFromObjects(iiReadFromLocalStorage);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
// if storage unavailable or we were just asked to clear, read from default files to enable usable UI
|
|
198
|
-
var usersString = JSON.stringify(users);
|
|
199
|
-
var tenantsString = JSON.stringify(tenants);
|
|
200
|
-
var configsString = JSON.stringify(configs);
|
|
201
|
-
var workspacesString = JSON.stringify(workspaces);
|
|
151
|
+
let storage;
|
|
202
152
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
153
|
+
storage = window[type];
|
|
154
|
+
const x = "__storage_test__";
|
|
155
|
+
storage.setItem(x, x);
|
|
156
|
+
storage.removeItem(x);
|
|
157
|
+
return true;
|
|
208
158
|
} catch (e) {
|
|
209
|
-
|
|
159
|
+
return (
|
|
160
|
+
e instanceof DOMException &&
|
|
161
|
+
// everything except Firefox
|
|
162
|
+
(e.code === 22 ||
|
|
163
|
+
// Firefox
|
|
164
|
+
e.code === 1014 ||
|
|
165
|
+
// test name field too, because code might not be present
|
|
166
|
+
// everything except Firefox
|
|
167
|
+
e.name === "QuotaExceededError" ||
|
|
168
|
+
// Firefox
|
|
169
|
+
e.name === "NS_ERROR_DOM_QUOTA_REACHED") &&
|
|
170
|
+
// acknowledge QuotaExceededError only if there's something already stored
|
|
171
|
+
storage &&
|
|
172
|
+
storage.length !== 0
|
|
173
|
+
);
|
|
210
174
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
175
|
+
}
|
|
176
|
+
export class InitInfo {
|
|
177
|
+
us: User[];
|
|
178
|
+
ts: Tenant[];
|
|
179
|
+
cs: Config[];
|
|
180
|
+
ws: Workspace[];
|
|
181
|
+
constructor(bClearLocalStorage: boolean) {
|
|
182
|
+
this.init(bClearLocalStorage);
|
|
183
|
+
}
|
|
184
|
+
// get initial data from localStorage or file
|
|
185
|
+
init(bClearLocalStorage: boolean): void {
|
|
186
|
+
console.log(`Calling InitInfo::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
|
|
187
|
+
// if we have a non-zero value stored, read it from localStorage
|
|
188
|
+
if (storageAvailable("localStorage")) {
|
|
189
|
+
let result = localStorage.getItem("InitInfo");
|
|
190
|
+
if (result != null && typeof result === "string" && result !== "") {
|
|
191
|
+
let initInfoString: string = result;
|
|
192
|
+
let iiReadFromLocalStorage: InitInfo = JSON.parse(initInfoString);
|
|
193
|
+
if (iiReadFromLocalStorage.us.length !== 0) {
|
|
194
|
+
if (bClearLocalStorage) { localStorage.removeItem("InitInfo"); }
|
|
195
|
+
else {
|
|
196
|
+
this.#initFromObjects(iiReadFromLocalStorage);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
234
201
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
return false;
|
|
202
|
+
// if storage unavailable or we were just asked to clear, read from default files to enable usable UI
|
|
203
|
+
var usersString = JSON.stringify(users);
|
|
204
|
+
var tenantsString = JSON.stringify(tenants);
|
|
205
|
+
var configsString = JSON.stringify(configs);
|
|
206
|
+
var workspacesString = JSON.stringify(workspaces);
|
|
207
|
+
try {
|
|
208
|
+
this.us = deserializeArray(User, usersString);
|
|
209
|
+
this.ts = deserializeArray(Tenant, tenantsString);
|
|
210
|
+
this.cs = deserializeArray(Config, configsString);
|
|
211
|
+
this.ws = deserializeArray(Workspace, workspacesString);
|
|
212
|
+
this.tagWithWorkspaces();
|
|
213
|
+
} catch (e) {
|
|
214
|
+
debugger;
|
|
249
215
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
216
|
+
}
|
|
217
|
+
save(): void {
|
|
218
|
+
let initInfoString: string = JSON.stringify(this);
|
|
219
|
+
localStorage.setItem("InitInfo", initInfoString);
|
|
220
|
+
}
|
|
221
|
+
tagWithWorkspaces(): boolean {
|
|
222
|
+
// first clear everyone's workspaceIDs
|
|
223
|
+
this.us.map((item) => item.workspaceIDs = "");
|
|
224
|
+
this.ts.map((item) => item.workspaceIDs = "");
|
|
225
|
+
this.cs.map((item) => item.workspaceIDs = "");
|
|
226
|
+
// for each workspace tag WorkspaceIDs of associated Users, Tenants, Configs
|
|
227
|
+
for (let workspace of this.ws) {
|
|
228
|
+
// find matching Users to tag with this workspace
|
|
229
|
+
for (let userID of workspace.associatedUsers) {
|
|
230
|
+
let user = this.us.find((currentUser) => currentUser.oid === userID);
|
|
231
|
+
if (user !== undefined) {
|
|
232
|
+
// we found the user
|
|
233
|
+
user[FILTER_FIELD] += workspace.id;
|
|
234
|
+
user[FILTER_FIELD] += " ";
|
|
235
|
+
} else {
|
|
236
|
+
// we should not have InitInfo missing Workspace components
|
|
237
|
+
debugger;
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// find matching Tenants to tag with this workspace
|
|
242
|
+
for (let tenantID of workspace.associatedTenants) {
|
|
243
|
+
let tenant = this.ts.find(
|
|
244
|
+
(currentTenant) => currentTenant.tid === tenantID
|
|
245
|
+
);
|
|
246
|
+
if (tenant !== undefined) {
|
|
247
|
+
// we found the tenant
|
|
248
|
+
tenant[FILTER_FIELD] += workspace.id;
|
|
249
|
+
tenant[FILTER_FIELD] += " ";
|
|
250
|
+
} else {
|
|
251
|
+
// we should not have InitInfo missing Workspace components
|
|
252
|
+
debugger;
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// find matching Configs to tag with this workspace
|
|
257
|
+
for (let configID of workspace.associatedConfigs) {
|
|
258
|
+
let config = this.cs.find(
|
|
259
|
+
(currentConfig) => currentConfig.id === configID
|
|
260
|
+
);
|
|
261
|
+
if (config !== undefined) {
|
|
262
|
+
// we found the config
|
|
263
|
+
config[FILTER_FIELD] += workspace.id;
|
|
264
|
+
config[FILTER_FIELD] += " ";
|
|
265
|
+
} else {
|
|
266
|
+
// we should not have InitInfo missing Workspace components
|
|
267
|
+
debugger;
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
264
271
|
}
|
|
265
|
-
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
#initFromObjects(ii: InitInfo): void {
|
|
275
|
+
// user array is the only one that has a Date that must be re-created on every read
|
|
276
|
+
if (typeof ii.us === "undefined") this.us = new Array();
|
|
277
|
+
else this.us = ii.us.map((user: User) => { user.authTS = new Date(user.authTS); return user });
|
|
278
|
+
if (typeof ii.ts === "undefined") this.ts = new Array();
|
|
279
|
+
else this.ts = ii.ts;
|
|
280
|
+
if (typeof ii.cs === "undefined") this.cs = new Array();
|
|
281
|
+
else this.cs = ii.cs;
|
|
282
|
+
if (typeof ii.ws === "undefined") this.ws = new Array();
|
|
283
|
+
else this.ws = ii.ws;
|
|
266
284
|
}
|
|
267
|
-
return true;
|
|
268
|
-
}
|
|
269
|
-
#initFromObjects(ii: InitInfo) : void {
|
|
270
|
-
// user array is the only one that has a Date that must be re-created on every read
|
|
271
|
-
if(typeof ii.us === "undefined") this.us = new Array();
|
|
272
|
-
else this.us = ii.us.map((user: User) => { user.authTS = new Date(user.authTS); return user } );
|
|
273
|
-
if(typeof ii.ts === "undefined") this.ts = new Array();
|
|
274
|
-
else this.ts = ii.ts;
|
|
275
|
-
if(typeof ii.cs === "undefined") this.cs = new Array();
|
|
276
|
-
else this.cs = ii.cs;
|
|
277
|
-
if(typeof ii.ws === "undefined") this.ws = new Array();
|
|
278
|
-
else this.ws = ii.ws;
|
|
279
|
-
}
|
|
280
285
|
}
|
|
281
|
-
export type TaskType = "initialization" |
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
286
|
+
export type TaskType = "initialization" |
|
|
287
|
+
"authenticate user" |
|
|
288
|
+
"reload React" |
|
|
289
|
+
"GET tenant details" |
|
|
290
|
+
"POST config init" |
|
|
291
|
+
"GET workspaces" |
|
|
292
|
+
"onboard tenant" |
|
|
293
|
+
"create 2nd tenant" |
|
|
294
|
+
"invite 2nd admin" |
|
|
295
|
+
"onboard 2nd tenant" |
|
|
296
|
+
"create config";
|
|
292
297
|
export class TaskArray {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
// get initial data from localStorage or file
|
|
299
|
-
init(bClearLocalStorage: boolean): void {
|
|
300
|
-
console.log(`Calling TaskArray::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
|
|
301
|
-
// first clear task array
|
|
302
|
-
this.tasks.length = 0;
|
|
303
|
-
// then clear localStorage if we have been asked to
|
|
304
|
-
if (bClearLocalStorage) {
|
|
305
|
-
if (storageAvailable("localStorage")) localStorage.removeItem("Tasks");
|
|
298
|
+
tasks: Task[];
|
|
299
|
+
constructor(bClearLocalStorage: boolean) {
|
|
300
|
+
this.tasks = [new Task()];
|
|
301
|
+
this.init(bClearLocalStorage);
|
|
306
302
|
}
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
303
|
+
// get initial data from localStorage or file
|
|
304
|
+
init(bClearLocalStorage: boolean): void {
|
|
305
|
+
console.log(`Calling TaskArray::init(bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`);
|
|
306
|
+
// first clear task array
|
|
307
|
+
this.tasks.length = 0;
|
|
308
|
+
// then clear localStorage if we have been asked to
|
|
309
|
+
if (bClearLocalStorage) {
|
|
310
|
+
if (storageAvailable("localStorage")) localStorage.removeItem("Tasks");
|
|
311
|
+
}
|
|
312
|
+
// then try localStorage
|
|
313
|
+
if (storageAvailable("localStorage")) {
|
|
314
|
+
let result = localStorage.getItem("Tasks");
|
|
315
|
+
if (result != null && typeof result === "string" && result !== "") {
|
|
316
|
+
// properly create Tasks and Dates from retrieved string
|
|
317
|
+
let tasksString: string = result;
|
|
318
|
+
let taskArray: TaskArray = JSON.parse(tasksString);
|
|
319
|
+
this.tasks = this.#initTasksFromObjects(taskArray.tasks);
|
|
320
|
+
let l = this.tasks.length;
|
|
321
|
+
if (l !== 0) return;
|
|
322
|
+
}
|
|
317
323
|
}
|
|
324
|
+
// if here, there was nothing in localStorage, use initialization file
|
|
325
|
+
this.tasks = this.#initTasksFromObjects(tasksData);
|
|
318
326
|
}
|
|
319
|
-
//
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
327
|
+
// set start time for a task
|
|
328
|
+
setTaskStart(taskType: TaskType, startDate: Date): void {
|
|
329
|
+
let task: Task | undefined = this.#findTask(taskType);
|
|
330
|
+
if (task != undefined && task != null) {
|
|
331
|
+
task.setStart(startDate);
|
|
332
|
+
task.status = "in progress";
|
|
333
|
+
this.#save();
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
debugger;
|
|
337
|
+
}
|
|
329
338
|
}
|
|
330
|
-
|
|
331
|
-
|
|
339
|
+
// set end time for a task
|
|
340
|
+
setTaskEnd(taskType: TaskType, endDate: Date, status: string): void {
|
|
341
|
+
let task: Task | undefined = this.#findTask(taskType);
|
|
342
|
+
if (task != undefined && task != null) {
|
|
343
|
+
task.setEnd(endDate);
|
|
344
|
+
task.status = status;
|
|
345
|
+
this.#save();
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
debugger;
|
|
349
|
+
}
|
|
332
350
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
351
|
+
//
|
|
352
|
+
// private
|
|
353
|
+
//
|
|
354
|
+
#findTask(taskType: TaskType): Task | undefined {
|
|
355
|
+
let task: Task | undefined = this.tasks.find(t => t.task == taskType);
|
|
356
|
+
if (task == undefined || task == null) {
|
|
357
|
+
for (task of this.tasks) {
|
|
358
|
+
if (task.subtasks != undefined && task.subtasks != null) {
|
|
359
|
+
task = task.subtasks.find(t => t.task == taskType);
|
|
360
|
+
if (task != undefined && task != null) break;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return task;
|
|
341
365
|
}
|
|
342
|
-
|
|
343
|
-
|
|
366
|
+
#initTasksFromObjects(tasks: Task[]): Task[] {
|
|
367
|
+
return tasks.map((t: Task) => {
|
|
368
|
+
let newTask: Task = new Task();
|
|
369
|
+
newTask.id = t.id;
|
|
370
|
+
newTask.task = t.task;
|
|
371
|
+
newTask.setStart(new Date(t.start));
|
|
372
|
+
newTask.setEnd(new Date(t.end));
|
|
373
|
+
newTask.expected = t.expected;
|
|
374
|
+
newTask.status = t.status;
|
|
375
|
+
newTask.expanded = t.expanded;
|
|
376
|
+
if (typeof t.subtasks !== "undefined" && t.subtasks != null) {
|
|
377
|
+
newTask.subtasks = t.subtasks.map((st: Task) => {
|
|
378
|
+
let newSubtask: Task = new Task();
|
|
379
|
+
newSubtask.id = st.id;
|
|
380
|
+
newSubtask.task = st.task;
|
|
381
|
+
newSubtask.setStart(new Date(st.start))
|
|
382
|
+
newSubtask.setEnd(new Date(st.end));
|
|
383
|
+
newSubtask.expected = st.expected;
|
|
384
|
+
newSubtask.status = st.status;
|
|
385
|
+
newSubtask.expanded = st.expanded;
|
|
386
|
+
return newSubtask;
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
return newTask;
|
|
390
|
+
});
|
|
344
391
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
#findTask(taskType: TaskType): Task | undefined {
|
|
350
|
-
let task: Task | undefined = this.tasks.find(t => t.task == taskType);
|
|
351
|
-
if (task == undefined || task == null) {
|
|
352
|
-
for(task of this.tasks){
|
|
353
|
-
if(task.subtasks != undefined && task.subtasks != null){
|
|
354
|
-
task = task.subtasks.find(t => t.task == taskType);
|
|
355
|
-
if(task != undefined && task != null) break;
|
|
392
|
+
#save(): void {
|
|
393
|
+
let taskArrayString: string = JSON.stringify(this);
|
|
394
|
+
if (storageAvailable("localStorage")) {
|
|
395
|
+
localStorage.setItem("Tasks", taskArrayString);
|
|
356
396
|
}
|
|
357
|
-
}
|
|
358
397
|
}
|
|
359
|
-
return task;
|
|
360
|
-
}
|
|
361
|
-
#initTasksFromObjects(tasks: Task[]): Task[]{
|
|
362
|
-
return tasks.map((t: Task) => {
|
|
363
|
-
let newTask: Task = new Task();
|
|
364
|
-
newTask.id = t.id;
|
|
365
|
-
newTask.task = t.task;
|
|
366
|
-
newTask.setStart(new Date(t.start));
|
|
367
|
-
newTask.setEnd(new Date(t.end));
|
|
368
|
-
newTask.expected = t.expected;
|
|
369
|
-
newTask.status = t.status;
|
|
370
|
-
newTask.expanded = t.expanded;
|
|
371
|
-
if(typeof t.subtasks !== "undefined" && t.subtasks != null){
|
|
372
|
-
newTask.subtasks = t.subtasks.map((st: Task) => {
|
|
373
|
-
let newSubtask: Task = new Task();
|
|
374
|
-
newSubtask.id = st.id;
|
|
375
|
-
newSubtask.task = st.task;
|
|
376
|
-
newSubtask.setStart(new Date(st.start))
|
|
377
|
-
newSubtask.setEnd(new Date(st.end));
|
|
378
|
-
newSubtask.expected = st.expected;
|
|
379
|
-
newSubtask.status = st.status;
|
|
380
|
-
newSubtask.expanded = st.expanded;
|
|
381
|
-
return newSubtask;
|
|
382
|
-
} )
|
|
383
|
-
}
|
|
384
|
-
return newTask;
|
|
385
|
-
} );
|
|
386
|
-
}
|
|
387
|
-
#save(): void{
|
|
388
|
-
let taskArrayString: string = JSON.stringify(this);
|
|
389
|
-
if (storageAvailable("localStorage")) {
|
|
390
|
-
localStorage.setItem("Tasks", taskArrayString);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
398
|
}
|
|
394
399
|
export class Task {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
400
|
+
id: number;
|
|
401
|
+
task: string;
|
|
402
|
+
start: Date;
|
|
403
|
+
startDisplay: string;
|
|
404
|
+
end: Date;
|
|
405
|
+
endDisplay: string;
|
|
406
|
+
elapsedDisplay: string;
|
|
407
|
+
expected: number;
|
|
408
|
+
status: string;
|
|
409
|
+
expanded: boolean;
|
|
410
|
+
subtasks: Task[];
|
|
411
|
+
setEnd(endDate: Date): void {
|
|
412
|
+
this.end = endDate;
|
|
413
|
+
this.endDisplay = `${this.end.getMinutes().toString().padStart(2, "0")}:${this.end.getSeconds().toString().padStart(2, "0")}`;
|
|
414
|
+
let minuteAdjustment: number = 0;
|
|
415
|
+
let elapsedSeconds: number = this.end.getSeconds() - this.start.getSeconds();
|
|
416
|
+
if (elapsedSeconds < 0) { elapsedSeconds += 60; minuteAdjustment = -1; }
|
|
417
|
+
let elapsedMinutes: number = this.end.getMinutes() - this.start.getMinutes() + minuteAdjustment;
|
|
418
|
+
if (elapsedMinutes < 0) elapsedMinutes += 60;
|
|
419
|
+
this.elapsedDisplay = `${elapsedMinutes.toString().padStart(2, "0")}:${elapsedSeconds.toString().padStart(2, "0")}`;
|
|
420
|
+
};
|
|
421
|
+
setStart(startDate: Date): void {
|
|
422
|
+
this.start = startDate;
|
|
423
|
+
this.startDisplay = `${this.start.getMinutes().toString().padStart(2, "0")}:${this.start.getSeconds().toString().padStart(2, "0")}`;
|
|
424
|
+
};
|
|
420
425
|
}
|
|
421
426
|
// class corresponding to an execution of a Config - a *TenantNode* for each source tenant, each with a *TenantNode* array of target tenants
|
|
422
427
|
export class BatchArray {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
) {
|
|
429
|
-
this.tenantNodes = new Array<TenantNode>();
|
|
430
|
-
this.init(config, syncPortalGlobalState, bClearLocalStorage);
|
|
431
|
-
}
|
|
432
|
-
// populate tenantNodes based on config tenants
|
|
433
|
-
init(
|
|
434
|
-
config: Config | null,
|
|
435
|
-
syncPortalGlobalState: InitInfo | null,
|
|
436
|
-
bClearLocalStorage: boolean
|
|
437
|
-
) : void {
|
|
438
|
-
console.log(
|
|
439
|
-
`Calling BatchArray::init(config: "${
|
|
440
|
-
config ? config.name : "null"
|
|
441
|
-
}", bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`
|
|
442
|
-
);
|
|
443
|
-
// first clear batch array
|
|
444
|
-
this.tenantNodes.length = 0;
|
|
445
|
-
// then clear localStorage if we have been asked to
|
|
446
|
-
if (bClearLocalStorage) {
|
|
447
|
-
if (storageAvailable("localStorage"))
|
|
448
|
-
localStorage.removeItem(config.name);
|
|
449
|
-
}
|
|
450
|
-
// create BatchArray if passed Config and InitInfo
|
|
451
|
-
if (
|
|
452
|
-
config != null &&
|
|
453
|
-
config.tenants != null &&
|
|
454
|
-
syncPortalGlobalState != null
|
|
428
|
+
tenantNodes: TenantNode[];
|
|
429
|
+
constructor(
|
|
430
|
+
config: Config | null,
|
|
431
|
+
syncPortalGlobalState: InitInfo | null,
|
|
432
|
+
bClearLocalStorage: boolean
|
|
455
433
|
) {
|
|
456
|
-
|
|
457
|
-
|
|
434
|
+
this.tenantNodes = new Array<TenantNode>();
|
|
435
|
+
this.init(config, syncPortalGlobalState, bClearLocalStorage);
|
|
436
|
+
}
|
|
437
|
+
// populate tenantNodes based on config tenants
|
|
438
|
+
init(
|
|
439
|
+
config: Config | null,
|
|
440
|
+
syncPortalGlobalState: InitInfo | null,
|
|
441
|
+
bClearLocalStorage: boolean
|
|
442
|
+
): void {
|
|
443
|
+
console.log(
|
|
444
|
+
`Calling BatchArray::init(config: "${config ? config.name : "null"
|
|
445
|
+
}", bClearLocalStorage: ${bClearLocalStorage ? "true" : "false"})`
|
|
446
|
+
);
|
|
447
|
+
// first clear batch array
|
|
448
|
+
this.tenantNodes.length = 0;
|
|
449
|
+
// then clear localStorage if we have been asked to
|
|
450
|
+
if (bClearLocalStorage) {
|
|
451
|
+
if (storageAvailable("localStorage"))
|
|
452
|
+
localStorage.removeItem(config.name);
|
|
453
|
+
}
|
|
454
|
+
// create BatchArray if passed Config and InitInfo
|
|
458
455
|
if (
|
|
459
|
-
|
|
460
|
-
|
|
456
|
+
config != null &&
|
|
457
|
+
config.tenants != null &&
|
|
458
|
+
syncPortalGlobalState != null
|
|
461
459
|
) {
|
|
462
|
-
|
|
463
|
-
(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
460
|
+
// create a sourceTenantNode for each Source and SourceTarget
|
|
461
|
+
config.tenants.map((tciPotentialSource: TenantConfigInfo) => {
|
|
462
|
+
if (
|
|
463
|
+
tciPotentialSource.configurationTenantType === "source" ||
|
|
464
|
+
tciPotentialSource.configurationTenantType === "sourcetarget"
|
|
465
|
+
) {
|
|
466
|
+
let sourceTenant = syncPortalGlobalState.ts.find(
|
|
467
|
+
(t) => t.tid === tciPotentialSource.tid
|
|
468
|
+
);
|
|
469
|
+
if (sourceTenant != null) {
|
|
470
|
+
let sourceTenantNode: TenantNode = new TenantNode(
|
|
471
|
+
tciPotentialSource.tid,
|
|
472
|
+
sourceTenant.name
|
|
473
|
+
);
|
|
474
|
+
this.tenantNodes.push(sourceTenantNode);
|
|
475
|
+
} else {
|
|
476
|
+
console.log(
|
|
477
|
+
`Error: no tenant found for config source tenant ${config.name}`
|
|
478
|
+
);
|
|
479
|
+
debugger;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
// create targetTenantNodes for each non-matching Target and SourceTarget
|
|
485
|
+
this.tenantNodes.map((sourceTenantNode: TenantNode) => {
|
|
486
|
+
config.tenants.map((tciPotentialTarget: TenantConfigInfo) => {
|
|
487
|
+
// is this a valid target?
|
|
488
|
+
if (
|
|
489
|
+
tciPotentialTarget.configurationTenantType === "target" ||
|
|
490
|
+
tciPotentialTarget.configurationTenantType === "sourcetarget"
|
|
491
|
+
) {
|
|
492
|
+
// is this a valid target that does not match this source?
|
|
493
|
+
if (tciPotentialTarget.tid !== sourceTenantNode.tid) {
|
|
494
|
+
let targetTenant = syncPortalGlobalState.ts.find(
|
|
495
|
+
(t) => t.tid === tciPotentialTarget.tid
|
|
496
|
+
);
|
|
497
|
+
if (targetTenant != null) {
|
|
498
|
+
let targetTenantNode: TenantNode = new TenantNode(
|
|
499
|
+
tciPotentialTarget.tid,
|
|
500
|
+
targetTenant.name
|
|
501
|
+
);
|
|
502
|
+
sourceTenantNode.targets.push(targetTenantNode);
|
|
503
|
+
sourceTenantNode.expanded = true;
|
|
504
|
+
} else {
|
|
505
|
+
console.log(
|
|
506
|
+
`Error: no tenant found for config target tenant ${config.name}`
|
|
507
|
+
);
|
|
508
|
+
debugger;
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
});
|
|
515
|
+
// then try localStorage to find any matching source tenant metrics
|
|
516
|
+
if (storageAvailable("localStorage")) {
|
|
517
|
+
let result = localStorage.getItem(config.name);
|
|
518
|
+
if (result != null && typeof result === "string" && result !== "") {
|
|
519
|
+
// TODO: retrieve any relevant stored statistics from localStorage
|
|
520
|
+
// let batchArrayString: string = result;
|
|
521
|
+
// let batchArray: BatchArray = JSON.parse(batchArrayString);
|
|
522
|
+
// batchArray.batches.map((batch: Batch) => {
|
|
523
|
+
// config.tenants.map((tciTarget: TenantConfigInfo) => {
|
|
524
|
+
// if(tciTarget.tid !== batch.tid) {
|
|
525
|
+
// let target: Target = new Target(tciTarget.tid);
|
|
526
|
+
// batch.targets.push(target);
|
|
527
|
+
// }
|
|
528
|
+
// });
|
|
529
|
+
// });
|
|
530
|
+
}
|
|
531
|
+
}
|
|
478
532
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
)
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
533
|
+
}
|
|
534
|
+
/*
|
|
535
|
+
monitorSyncProgress(): void {
|
|
536
|
+
const connection = new signalR.HubConnectionBuilder()
|
|
537
|
+
.withUrl("https://dev-signalrdispatcher-westus.azurewebsites.net/?statsId=df9c2e0a-f6fe-43bb-a155-d51f66dffe0e")
|
|
538
|
+
.configureLogging(signalR.LogLevel.Information)
|
|
539
|
+
.build();
|
|
540
|
+
|
|
541
|
+
// when you get a message
|
|
542
|
+
connection.on("newMessage", function (message) {
|
|
543
|
+
console.log(message); // log the message
|
|
544
|
+
const item = JSON.parse(message); //parse it into an object
|
|
545
|
+
const statsarray = item.Stats; // get the array of statistics
|
|
546
|
+
const statskeys = Object.keys(statsarray); // get the keys of the array
|
|
547
|
+
const statsvalues = Object.values(statsarray); // get the values of the array
|
|
548
|
+
let statistics = ""; // initialize statistics
|
|
549
|
+
let total = 1;
|
|
550
|
+
let currentR = 0;
|
|
551
|
+
let currentW = 0;
|
|
552
|
+
let currentD = 0;
|
|
553
|
+
for (let j = 0; j < statskeys.length; j++) { // store the TotalCount and store as total
|
|
554
|
+
if (statskeys[j].endsWith("TotalCount")) {
|
|
555
|
+
total = statsvalues[j];
|
|
556
|
+
}
|
|
557
|
+
if (statskeys[j].endsWith("CurrentCount")) { // store the Reader value as currentR
|
|
558
|
+
if (statskeys[j].startsWith("Reader")) {
|
|
559
|
+
currentR = statsvalues[j];
|
|
560
|
+
}
|
|
561
|
+
if (statskeys[j].startsWith("Writer")) { // store the Writer value as currentW
|
|
562
|
+
currentW = statsvalues[j];
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (statskeys[j].endsWith("DeferredCount")) { // store the deferred count
|
|
566
|
+
currentD = statsvalues[j];
|
|
567
|
+
}
|
|
568
|
+
statistics = statistics + statskeys[j] + "=" + statsvalues[j] + "<br />" //
|
|
507
569
|
}
|
|
508
|
-
|
|
570
|
+
updateProgress(total, currentR, currentW, currentD);
|
|
571
|
+
myFunction(item.TargetID, statistics);
|
|
509
572
|
});
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
573
|
+
|
|
574
|
+
connection.start().catch(console.error);
|
|
575
|
+
|
|
576
|
+
function myFunction(targetid, str) {
|
|
577
|
+
const table = document.getElementById("the-table");
|
|
578
|
+
let row = document.getElementById(targetid);
|
|
579
|
+
if (row) {
|
|
580
|
+
row.cells[1].innerHTML = "<code>" + str + "</ code>";
|
|
581
|
+
} else {
|
|
582
|
+
row = table.insertRow(1);
|
|
583
|
+
row.id = targetid;
|
|
584
|
+
let cell1 = row.insertCell(0);
|
|
585
|
+
let cell2 = row.insertCell(1);
|
|
586
|
+
let caption = (targetid === "00000000-0000-0000-0000-000000000000") ? "<B>Status of ServiceBus QUEUES</B>" : targetid;
|
|
587
|
+
cell1.innerHTML = "<code>" + caption + "</ code>";
|
|
588
|
+
cell2.innerHTML = "<code>" + str + "</ code>";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function updateProgress(total, currentR, currentW, currentD) {
|
|
593
|
+
updateRead(total, currentR);
|
|
594
|
+
updateWrite(total, currentW, currentD);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function updateRead(total, current) {
|
|
598
|
+
const element = document.getElementById("readBar");
|
|
599
|
+
const width = Math.round(100 * current / total);
|
|
600
|
+
element.style.width = width + '%';
|
|
601
|
+
element.innerHTML = "R=" + width + '%';
|
|
526
602
|
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
// cycle through test state machine
|
|
531
|
-
test(instance: IPublicClientApplication, authorizedUser: User|undefined, config: Config|null|undefined) : void {
|
|
532
|
-
if (this.tenantNodes == null || this.tenantNodes.length == 0) {
|
|
533
|
-
// we should not have an empty batch array for a test
|
|
534
|
-
debugger;
|
|
535
|
-
}
|
|
536
|
-
// execute post to reader endpoint
|
|
537
|
-
debugger;
|
|
538
|
-
readerPost(instance, authorizedUser, config);
|
|
539
603
|
|
|
540
|
-
|
|
604
|
+
function updateWrite(total, w, d) {
|
|
605
|
+
const elementW = document.getElementById("writeBar");
|
|
606
|
+
const widthW = Math.round(100 * w / total);
|
|
607
|
+
elementW.style.width = widthW + '%';
|
|
608
|
+
elementW.innerHTML = "W=" + widthW + '%';
|
|
541
609
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
610
|
+
const elementD = document.getElementById("deferBar");
|
|
611
|
+
let width = 0;
|
|
612
|
+
let widthScaled = 0;
|
|
613
|
+
if (d > 0) {
|
|
614
|
+
width = Math.round(100 * d / total) + 1;
|
|
615
|
+
if (width < 10) {
|
|
616
|
+
widthScaled = width * 10;
|
|
617
|
+
}
|
|
618
|
+
else if (width < 20) {
|
|
619
|
+
widthScaled = width * 5;
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
widthScaled = width;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (widthScaled + widthW >= 99) {
|
|
626
|
+
widthScaled = 99 - widthW;
|
|
627
|
+
}
|
|
628
|
+
elementD.style.width = widthScaled + '%';
|
|
629
|
+
elementD.innerHTML = width + '%';
|
|
630
|
+
document.getElementById("deferred").innerHTML = 'Deferred:' + width + '% (' + d + ' of ' + total + ')';
|
|
631
|
+
// cycle through desired states for sources and targets
|
|
632
|
+
if (this.tenantNodes != null) {
|
|
633
|
+
this.tenantNodes.map((sourceTenantNode: TenantNode) => {
|
|
634
|
+
if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
|
|
635
|
+
else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
|
|
636
|
+
else sourceTenantNode.update(0, 0, 0, 0);
|
|
637
|
+
if (sourceTenantNode.targets != null) {
|
|
638
|
+
sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
|
|
639
|
+
if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
|
|
640
|
+
else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
|
|
641
|
+
else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
|
|
642
|
+
else targetTenantNode.update(0, 0, 0, 0);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
}
|
|
555
647
|
}
|
|
556
|
-
});
|
|
557
648
|
}
|
|
558
|
-
|
|
649
|
+
*/
|
|
650
|
+
// cycle through test state machine
|
|
651
|
+
startSync(instance: IPublicClientApplication, authorizedUser: User | null | undefined, config: Config | null | undefined): void {
|
|
652
|
+
if (this.tenantNodes == null || this.tenantNodes.length == 0) {
|
|
653
|
+
// we should not have an empty batch array for a test
|
|
654
|
+
debugger;
|
|
655
|
+
}
|
|
656
|
+
// execute post to reader endpoint
|
|
657
|
+
readerPost(instance, authorizedUser, config);
|
|
658
|
+
|
|
659
|
+
// start SignalR connection
|
|
660
|
+
//debugger;
|
|
661
|
+
//monitorSyncProgress();
|
|
662
|
+
}
|
|
559
663
|
}
|
|
560
664
|
export class TenantNode {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
577
|
-
update(total: number, read: number, written: number, deferred: number): void {
|
|
578
|
-
this.total = total;
|
|
579
|
-
this.read = read;
|
|
580
|
-
this.written = written;
|
|
581
|
-
this.deferred = deferred;
|
|
582
|
-
if(this.read === 0 && this.written === 0) this.status = "not started";
|
|
583
|
-
if(this.read > 0) {
|
|
584
|
-
if(this.read < this.total) this.status = "in progress";
|
|
585
|
-
else if(this.read === this.total) this.status = "complete";
|
|
665
|
+
expanded: boolean;
|
|
666
|
+
status: string;
|
|
667
|
+
name: string;
|
|
668
|
+
tid: string;
|
|
669
|
+
total: number;
|
|
670
|
+
read: number;
|
|
671
|
+
written: number;
|
|
672
|
+
deferred: number;
|
|
673
|
+
targets: TenantNode[];
|
|
674
|
+
constructor(tid: string, name: string) {
|
|
675
|
+
this.expanded = false;
|
|
676
|
+
this.name = name;
|
|
677
|
+
this.tid = tid;
|
|
678
|
+
this.targets = new Array<TenantNode>();
|
|
679
|
+
this.update(0, 0, 0, 0);
|
|
586
680
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
681
|
+
update(total: number, read: number, written: number, deferred: number): void {
|
|
682
|
+
this.total = total;
|
|
683
|
+
this.read = read;
|
|
684
|
+
this.written = written;
|
|
685
|
+
this.deferred = deferred;
|
|
686
|
+
if (this.read === 0 && this.written === 0) this.status = "not started";
|
|
687
|
+
if (this.read > 0) {
|
|
688
|
+
if (this.read < this.total) this.status = "in progress";
|
|
689
|
+
else if (this.read === this.total) this.status = "complete";
|
|
690
|
+
}
|
|
691
|
+
else if (this.written > 0) {
|
|
692
|
+
if (this.written + this.deferred < this.total) this.status = "in progress";
|
|
693
|
+
else if (this.written === this.total) this.status = "complete";
|
|
694
|
+
else if (this.written + this.deferred === this.total) this.status = "failed";
|
|
695
|
+
}
|
|
591
696
|
}
|
|
592
|
-
}
|
|
593
697
|
}
|
|
594
698
|
export class APIResult {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
699
|
+
result: boolean;
|
|
700
|
+
status: number;
|
|
701
|
+
error: string;
|
|
702
|
+
array: Array<Object> | null;
|
|
703
|
+
constructor() { this.result = true; this.status = 200; this.error = ""; this.array = null; }
|
|
600
704
|
}
|
|
601
705
|
//
|
|
602
706
|
// Azure AD Graph API
|
|
603
707
|
//
|
|
604
708
|
//groupGet - GET /groups/{id}
|
|
605
|
-
export async function groupGet(tenant: Tenant, groupid: string): Promise<{group: string, error: string}> {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
709
|
+
export async function groupGet(tenant: Tenant, groupid: string): Promise<{ group: string, error: string }> {
|
|
710
|
+
// need a read or write access token to get graph users
|
|
711
|
+
let accessToken: string = "";
|
|
712
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
|
|
713
|
+
accessToken = tenant.readServicePrincipal;
|
|
714
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
|
|
715
|
+
accessToken = tenant.writeServicePrincipal;
|
|
716
|
+
if (accessToken === "") return { group: "", error: "no access token specified" };
|
|
717
|
+
// prepare Authorization headers as part of options
|
|
718
|
+
const headers = new Headers();
|
|
719
|
+
const bearer = `Bearer ${accessToken}`;
|
|
720
|
+
headers.append("Authorization", bearer);
|
|
721
|
+
let options = { method: "GET", headers: headers };
|
|
722
|
+
// make /groups endpoint call
|
|
723
|
+
try {
|
|
724
|
+
let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/${groupid}`;
|
|
725
|
+
let response = await fetch(groupsEndpoint, options);
|
|
726
|
+
let data = await response.json();
|
|
727
|
+
if (typeof data.error !== "undefined") {
|
|
728
|
+
return { group: "", error: `${data.error.code}: ${data.error.message}` };
|
|
729
|
+
}
|
|
730
|
+
return { group: data.value, error: `` };
|
|
731
|
+
}
|
|
732
|
+
catch (error: any) {
|
|
733
|
+
console.log(error);
|
|
734
|
+
return { group: "", error: `Exception: ${error}` };
|
|
625
735
|
}
|
|
626
|
-
return { group: data.value, error: `` };
|
|
627
|
-
}
|
|
628
|
-
catch(error: any) {
|
|
629
|
-
console.log(error);
|
|
630
|
-
return { group: "", error: `Exception: ${error}` };
|
|
631
|
-
}
|
|
632
736
|
}
|
|
633
737
|
//groupsGet - GET /groups
|
|
634
|
-
export async function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{groups: Group[], error: string}> {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
738
|
+
export async function groupsGet(tenant: Tenant, groupSearchString: string): Promise<{ groups: Group[], error: string }> {
|
|
739
|
+
// need a read or write access token to get graph users
|
|
740
|
+
let accessToken: string = "";
|
|
741
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
|
|
742
|
+
accessToken = tenant.readServicePrincipal;
|
|
743
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
|
|
744
|
+
accessToken = tenant.writeServicePrincipal;
|
|
745
|
+
if (accessToken === "") return { groups: [], error: "no access token specified" };
|
|
746
|
+
// prepare Authorization headers as part of options
|
|
747
|
+
const headers = new Headers();
|
|
748
|
+
const bearer = `Bearer ${accessToken}`;
|
|
749
|
+
headers.append("Authorization", bearer);
|
|
750
|
+
let options = { method: "GET", headers: headers };
|
|
751
|
+
// make /groups endpoint call
|
|
752
|
+
try {
|
|
753
|
+
let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/?$filter=startsWith(displayName, '${groupSearchString}')`;
|
|
754
|
+
let response = await fetch(groupsEndpoint, options);
|
|
755
|
+
let data = await response.json();
|
|
756
|
+
if (typeof data.error !== "undefined") {
|
|
757
|
+
return { groups: [], error: `${data.error.code}: ${data.error.message}` };
|
|
758
|
+
}
|
|
759
|
+
return { groups: data.value, error: `` };
|
|
760
|
+
}
|
|
761
|
+
catch (error: any) {
|
|
762
|
+
console.log(error);
|
|
763
|
+
return { group: "", error: `Exception: ${error}` };
|
|
654
764
|
}
|
|
655
|
-
return { groups: data.value, error: `` };
|
|
656
|
-
}
|
|
657
|
-
catch(error: any) {
|
|
658
|
-
console.log(error);
|
|
659
|
-
return { group: "", error: `Exception: ${error}` };
|
|
660
|
-
}
|
|
661
765
|
}
|
|
662
766
|
export function signIn(user: User, tasks: TaskArray): void {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
767
|
+
let tenantURL: string = window.location.href;
|
|
768
|
+
tenantURL += "MicrosoftIdentity/Account/Challenge";
|
|
769
|
+
let url: URL = new URL(tenantURL);
|
|
770
|
+
url.searchParams.append("redirectUri", window.location.origin);
|
|
771
|
+
url.searchParams.append("scope", "openid offline_access profile user.read contacts.read CrossTenantInformation.ReadBasic.All");
|
|
772
|
+
url.searchParams.append("domainHint", "organizations");
|
|
773
|
+
if (user.oid !== "1") {
|
|
774
|
+
url.searchParams.append("loginHint", user.mail);
|
|
775
|
+
}
|
|
776
|
+
tasks.setTaskStart("initialization", new Date());
|
|
777
|
+
tasks.setTaskStart("authenticate user", new Date());
|
|
778
|
+
window.location.assign(url.href);
|
|
675
779
|
}
|
|
676
780
|
export function signInIncrementally(user: User, scope: string): void {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
781
|
+
if (user.oid == "1") return;
|
|
782
|
+
let tenantURL: string = window.location.href;
|
|
783
|
+
tenantURL += "MicrosoftIdentity/Account/Challenge";
|
|
784
|
+
let url: URL = new URL(tenantURL);
|
|
785
|
+
url.searchParams.append("redirectUri", window.location.origin);
|
|
786
|
+
let scopes = scope;
|
|
787
|
+
url.searchParams.append("scope", scopes);
|
|
788
|
+
url.searchParams.append("domainHint", "organizations");
|
|
789
|
+
url.searchParams.append("loginHint", user.mail);
|
|
790
|
+
window.location.assign(url.href);
|
|
687
791
|
}
|
|
688
792
|
export function signOut(user: User): void {
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
793
|
+
if (user.oid == "1") return;
|
|
794
|
+
// these lines provide more callbacks during logout
|
|
795
|
+
//let tenantURL: string = window.location.href;
|
|
796
|
+
//tenantURL += "MicrosoftIdentity/Account/SignOut";
|
|
797
|
+
// this line takes advantage of our saved loginHint to logout right away, but requires additional cleanup logic
|
|
798
|
+
// https://aaddevsup.azurewebsites.net/2022/03/how-to-logout-of-an-oauth2-application-without-getting-prompted-to-select-a-user/
|
|
799
|
+
let tenantURL: string = "https://login.microsoftonline.com/common/oauth2/logout";
|
|
800
|
+
let url: URL = new URL(tenantURL);
|
|
801
|
+
url.searchParams.append("post_logout_redirect_uri", window.location.origin);
|
|
802
|
+
url.searchParams.append("logout_hint", user.loginHint);
|
|
803
|
+
window.location.assign(url.href);
|
|
700
804
|
}
|
|
701
805
|
//tenantRelationshipsGetByDomain - query AAD for associated company name and id
|
|
702
806
|
export async function tenantRelationshipsGetByDomain(loggedInUser: User, tenant: Tenant, instance: IPublicClientApplication, debug: boolean): Promise<boolean> {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
807
|
+
if (debug) debugger;
|
|
808
|
+
// do we already have a valid tenant name? if so, nothing to add
|
|
809
|
+
if (typeof tenant.name !== 'undefined' && tenant.name !== "") return false;
|
|
810
|
+
// if needed, retrieve and cache access token
|
|
811
|
+
if (typeof loggedInUser.accessToken === 'undefined' || loggedInUser.accessToken === "") {
|
|
812
|
+
console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
|
|
813
|
+
try {
|
|
814
|
+
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
|
|
815
|
+
loggedInUser.accessToken = response.accessToken; // cache access token on the user
|
|
816
|
+
console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0, 20));
|
|
817
|
+
}
|
|
818
|
+
catch (error: any) {
|
|
819
|
+
console.log("Front end token failure: " + error);
|
|
820
|
+
return false; // failed to get access token, no need to re-render
|
|
821
|
+
}
|
|
717
822
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
823
|
+
// prepare Authorization headers as part of options
|
|
824
|
+
const headers = new Headers();
|
|
825
|
+
const bearer = `Bearer ${loggedInUser.accessToken}`;
|
|
826
|
+
headers.append("Authorization", bearer);
|
|
827
|
+
let options = { method: "GET", headers: headers };
|
|
828
|
+
// make tenant endpoint call
|
|
829
|
+
try {
|
|
830
|
+
// create tenant info endpoint
|
|
831
|
+
var tenantEndpoint = graphConfig.graphTenantByDomainEndpoint;
|
|
832
|
+
tenantEndpoint += "(domainName='";
|
|
833
|
+
tenantEndpoint += tenant.domain;
|
|
834
|
+
tenantEndpoint += "')";
|
|
835
|
+
console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
|
|
836
|
+
let response = await fetch(tenantEndpoint, options);
|
|
837
|
+
let data = await response.json();
|
|
838
|
+
if (data) {
|
|
839
|
+
if (typeof data.error !== "undefined") {
|
|
840
|
+
console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
else if (typeof data.displayName !== undefined && data.displayName !== "") {
|
|
844
|
+
// set domain information on passed tenant
|
|
845
|
+
tenant.tid = data.tenantId;
|
|
846
|
+
tenant.name = data.displayName;
|
|
847
|
+
console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
|
|
848
|
+
return true; // success, need UX to re-render
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
else {
|
|
852
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
853
|
+
}
|
|
746
854
|
}
|
|
747
|
-
|
|
748
|
-
|
|
855
|
+
catch (error: any) {
|
|
856
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
857
|
+
return false; // failed, no need for UX to re-render
|
|
749
858
|
}
|
|
750
|
-
}
|
|
751
|
-
catch(error: any) {
|
|
752
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
753
859
|
return false; // failed, no need for UX to re-render
|
|
754
|
-
}
|
|
755
|
-
return false; // failed, no need for UX to re-render
|
|
756
860
|
}
|
|
757
861
|
//tenantRelationshipsGetById - query AAD for associated company name and domain
|
|
758
862
|
export async function tenantRelationshipsGetById(user: User, ii: InitInfo, instance: IPublicClientApplication, tasks: TaskArray, debug: boolean): Promise<boolean> {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
863
|
+
if (debug) debugger;
|
|
864
|
+
// do we already have a valid company name? if so, nothing to add, no need for UX to re-render
|
|
865
|
+
if (typeof user.companyName !== 'undefined' && user.companyName !== "") return false;
|
|
866
|
+
// if needed, retrieve and cache access token
|
|
867
|
+
if (typeof user.accessToken === 'undefined' || user.accessToken === "") {
|
|
868
|
+
try {
|
|
869
|
+
let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
|
|
870
|
+
user.accessToken = response.accessToken; // cache access token
|
|
871
|
+
console.log("Front end token acquired: " + user.accessToken.slice(0, 20));
|
|
872
|
+
}
|
|
873
|
+
catch (error: any) {
|
|
874
|
+
console.log("Front end token failure: " + error);
|
|
875
|
+
return false; // failed to get access token, no need to re-render
|
|
876
|
+
}
|
|
772
877
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
878
|
+
// prepare Authorization headers as part of options
|
|
879
|
+
const headers = new Headers();
|
|
880
|
+
const bearer = `Bearer ${user.accessToken}`;
|
|
881
|
+
headers.append("Authorization", bearer);
|
|
882
|
+
let options = { method: "GET", headers: headers };
|
|
883
|
+
// make tenant endpoint call
|
|
884
|
+
try {
|
|
885
|
+
// create tenant info endpoint
|
|
886
|
+
var tenantEndpoint = graphConfig.graphTenantByIdEndpoint;
|
|
887
|
+
tenantEndpoint += "(tenantId='";
|
|
888
|
+
tenantEndpoint += user.tid;
|
|
889
|
+
tenantEndpoint += "')";
|
|
890
|
+
// track time of tenant details query
|
|
891
|
+
tasks.setTaskStart("GET tenant details", new Date());
|
|
892
|
+
console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
|
|
893
|
+
let response = await fetch(tenantEndpoint, options);
|
|
894
|
+
let data = await response.json();
|
|
895
|
+
if (data && typeof data.displayName !== undefined && data.displayName !== "") {
|
|
896
|
+
// set domain information on user
|
|
897
|
+
user.companyName = data.displayName;
|
|
898
|
+
user.companyDomain = data.defaultDomainName;
|
|
899
|
+
// set domain information on tenant
|
|
900
|
+
let tenant: Tenant | undefined = ii.ts.find((t) => t.tid === user.tid);
|
|
901
|
+
if (tenant !== undefined) {
|
|
902
|
+
tenant.name = data.displayName;
|
|
903
|
+
tenant.domain = data.defaultDomainName;
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
|
|
907
|
+
debugger;
|
|
908
|
+
}
|
|
909
|
+
console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
|
|
910
|
+
tasks.setTaskEnd("GET tenant details", new Date(), "complete");
|
|
911
|
+
return true; // success, need UX to re-render
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
|
|
915
|
+
}
|
|
808
916
|
}
|
|
809
|
-
|
|
810
|
-
|
|
917
|
+
catch (error: any) {
|
|
918
|
+
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
919
|
+
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
920
|
+
return false; // failed, no need for UX to re-render
|
|
811
921
|
}
|
|
812
|
-
}
|
|
813
|
-
catch(error: any) {
|
|
814
|
-
console.log("Failed to GET from /findTenantInformationByTenantId: ", error);
|
|
815
922
|
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
816
923
|
return false; // failed, no need for UX to re-render
|
|
817
|
-
}
|
|
818
|
-
tasks.setTaskEnd("GET tenant details", new Date(), "failed");
|
|
819
|
-
return false; // failed, no need for UX to re-render
|
|
820
924
|
}
|
|
821
925
|
//usersGet - GET from AAD Users endpoint
|
|
822
|
-
export async function usersGet(tenant: Tenant): Promise<{users: string[], error: string}> {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
926
|
+
export async function usersGet(tenant: Tenant): Promise<{ users: string[], error: string }> {
|
|
927
|
+
// need a read or write access token to get graph users
|
|
928
|
+
let accessToken: string = "";
|
|
929
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
|
|
930
|
+
accessToken = tenant.readServicePrincipal;
|
|
931
|
+
if (tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
|
|
932
|
+
accessToken = tenant.writeServicePrincipal;
|
|
933
|
+
if (accessToken === "") return { users: [], error: "no access token specified" };
|
|
934
|
+
// prepare Authorization headers as part of options
|
|
935
|
+
const headers = new Headers();
|
|
936
|
+
const bearer = `Bearer ${accessToken}`;
|
|
937
|
+
headers.append("Authorization", bearer);
|
|
938
|
+
let options = { method: "GET", headers: headers };
|
|
939
|
+
// make /users endpoint call
|
|
940
|
+
try {
|
|
941
|
+
let response = await fetch(graphConfig.graphUsersEndpoint, options);
|
|
942
|
+
let data = await response.json();
|
|
943
|
+
if (typeof data.error !== "undefined") {
|
|
944
|
+
return { users: [], error: `${data.error.code}: ${data.error.message}` };
|
|
945
|
+
}
|
|
946
|
+
let users = new Array<User>();
|
|
947
|
+
for (let user of data.value) {
|
|
948
|
+
users.push(user.mail);
|
|
949
|
+
}
|
|
950
|
+
return { users: users, error: `` };
|
|
841
951
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
952
|
+
catch (error: any) {
|
|
953
|
+
console.log(error);
|
|
954
|
+
return { users: [], error: `Exception: ${error}` };
|
|
845
955
|
}
|
|
846
|
-
return { users: users, error: `` };
|
|
847
|
-
}
|
|
848
|
-
catch(error: any) {
|
|
849
|
-
console.log(error);
|
|
850
|
-
return { users: [], error: `Exception: ${error}` };
|
|
851
|
-
}
|
|
852
956
|
}
|
|
853
957
|
//
|
|
854
958
|
// Mindline Config API
|
|
855
959
|
//
|
|
856
960
|
export async function configEdit(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
961
|
+
let result: APIResult = new APIResult();
|
|
962
|
+
if (config.id === "1") {
|
|
963
|
+
result = await configPost(instance, authorizedUser, config, workspaceId, debug);
|
|
964
|
+
}
|
|
965
|
+
else {
|
|
966
|
+
result = await configPut(instance, authorizedUser, config, debug);
|
|
967
|
+
}
|
|
968
|
+
return result;
|
|
865
969
|
}
|
|
866
970
|
export async function configRemove(instance: IPublicClientApplication, authorizedUser: User, config: Config, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
867
|
-
|
|
971
|
+
return configDelete(instance, authorizedUser, config, workspaceId, debug);
|
|
868
972
|
}
|
|
869
973
|
// retrieve Workspace(s), User(s), Tenant(s), Config(s) given newly logged in user
|
|
870
|
-
export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult>
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
return result;
|
|
974
|
+
export async function initGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, tasks: TaskArray, debug: boolean): Promise<APIResult> {
|
|
975
|
+
let result: APIResult = new APIResult();
|
|
976
|
+
if (debug) debugger;
|
|
977
|
+
// get tenant name and domain from AAD
|
|
978
|
+
result.result = await tenantRelationshipsGetById(user, ii, instance, tasks, debug);
|
|
979
|
+
// if this is the first time, we have just gotten tenant info, then we must POST user and not-yet-onboarded tenant to back end
|
|
980
|
+
if (result.result) {
|
|
981
|
+
tasks.setTaskStart("POST config init", new Date());
|
|
982
|
+
result = await initPost(instance, authorizedUser, user, debug);
|
|
983
|
+
tasks.setTaskEnd("POST config init", new Date(), result.result ? "complete" : "failed");
|
|
984
|
+
}
|
|
985
|
+
// simlarly, if we just did our first post, then query config backend for workspace(s) associated with this user
|
|
986
|
+
if (result.result) {
|
|
987
|
+
tasks.setTaskStart("GET workspaces", new Date());
|
|
988
|
+
result = await workspaceInfoGet(instance, authorizedUser, user, ii, debug);
|
|
989
|
+
tasks.setTaskEnd("GET workspaces", new Date(), result.result ? "complete" : "failed");
|
|
990
|
+
}
|
|
991
|
+
if (result.result) result.error = version;
|
|
992
|
+
return result;
|
|
890
993
|
}
|
|
891
994
|
export async function tenantAdd(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string): Promise<APIResult> {
|
|
892
|
-
|
|
995
|
+
return tenantPost(instance, authorizedUser, tenant, workspaceId);
|
|
893
996
|
}
|
|
894
997
|
export async function tenantComplete(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, debug: boolean): Promise<APIResult> {
|
|
895
|
-
|
|
998
|
+
return tenantPut(instance, authorizedUser, tenant, debug);
|
|
896
999
|
}
|
|
897
1000
|
export async function tenantRemove(instance: IPublicClientApplication, authorizedUser: User, tenant: Tenant, workspaceId: string, debug: boolean): Promise<APIResult> {
|
|
898
|
-
|
|
1001
|
+
return tenantDelete(instance, authorizedUser, tenant, workspaceId, debug);
|
|
899
1002
|
}
|
|
900
1003
|
export async function userAdd(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): Promise<APIResult> {
|
|
901
|
-
|
|
1004
|
+
return adminPost(instance, authorizedUser, user, workspaceId);
|
|
902
1005
|
}
|
|
903
1006
|
export async function userRemove(instance: IPublicClientApplication, authorizedUser: User, user: User, workspaceId: string): Promise<APIResult> {
|
|
904
|
-
|
|
1007
|
+
return adminDelete(instance, authorizedUser, user, workspaceId);
|
|
905
1008
|
}
|
|
906
1009
|
//
|
|
907
1010
|
// Mindline Config API internal helper functions
|
|
908
1011
|
//
|
|
909
|
-
function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object>)
|
|
910
|
-
{
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
});
|
|
1012
|
+
function processReturnedAdmins(workspace: Workspace, ii: InitInfo, returnedAdmins: Array<Object>) {
|
|
1013
|
+
returnedAdmins.map((item) => {
|
|
1014
|
+
// are we already tracking this user?
|
|
1015
|
+
let user: User | null = null;
|
|
1016
|
+
let usIndex = ii.us.findIndex((u) => u.oid === item.userId);
|
|
1017
|
+
if (usIndex === -1) {
|
|
1018
|
+
// start tracking
|
|
1019
|
+
let dummyIndex = ii.us.findIndex((u) => u.oid === "1");
|
|
1020
|
+
if (dummyIndex !== -1) {
|
|
1021
|
+
// clear and overwrite dummy
|
|
1022
|
+
user = ii.us.at(dummyIndex);
|
|
1023
|
+
user.associatedWorkspaces.length = 0;
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
// create and track new user
|
|
1027
|
+
user = new User();
|
|
1028
|
+
ii.us.push(user);
|
|
1029
|
+
}
|
|
1030
|
+
} else {
|
|
1031
|
+
// already tracking this user
|
|
1032
|
+
user = ii.us.at(usIndex);
|
|
1033
|
+
}
|
|
1034
|
+
// refresh all the data available from the server
|
|
1035
|
+
user.oid = item.userId;
|
|
1036
|
+
user.name = item.firstName;
|
|
1037
|
+
user.mail = item.email;
|
|
1038
|
+
user.tid = item.tenantId;
|
|
1039
|
+
// ensure this workspace tracks this user
|
|
1040
|
+
let idx = workspace.associatedUsers.findIndex((u) => u === item.userId);
|
|
1041
|
+
if (idx == -1) workspace.associatedUsers.push(item.userId);
|
|
1042
|
+
});
|
|
941
1043
|
}
|
|
942
|
-
function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object>)
|
|
943
|
-
{
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
});
|
|
1044
|
+
function processReturnedTenants(workspace: Workspace, ii: InitInfo, returnedTenants: Array<Object>) {
|
|
1045
|
+
returnedTenants.map((item) => {
|
|
1046
|
+
// are we already tracking this tenant?
|
|
1047
|
+
let tenant: Tenant | null = null;
|
|
1048
|
+
let tsIndex = ii.ts.findIndex((t) => t.tid === item.tenantId);
|
|
1049
|
+
if (tsIndex === -1) {
|
|
1050
|
+
// start tracking
|
|
1051
|
+
let dummyIndex = ii.ts.findIndex((t) => t.tid === "1");
|
|
1052
|
+
if (dummyIndex !== -1) {
|
|
1053
|
+
// clear and overwrite dummy
|
|
1054
|
+
tenant = ii.ts.at(dummyIndex);
|
|
1055
|
+
} else {
|
|
1056
|
+
// create and track new workspace
|
|
1057
|
+
tenant = new Tenant();
|
|
1058
|
+
ii.ts.push(tenant);
|
|
1059
|
+
}
|
|
1060
|
+
} else {
|
|
1061
|
+
// already tracking this tenant
|
|
1062
|
+
tenant = ii.ts.at(tsIndex);
|
|
1063
|
+
}
|
|
1064
|
+
tenant.tid = item.tenantId;
|
|
1065
|
+
tenant.name = item.name;
|
|
1066
|
+
tenant.domain = item.domain;
|
|
1067
|
+
tenant.tenantType = item.type.toLowerCase(); // should now be strings
|
|
1068
|
+
tenant.permissionType = item.permissionType.toLowerCase(); // should now be strings
|
|
1069
|
+
tenant.onboarded = item.isOnboarded ? "true" : "false";
|
|
1070
|
+
tenant.authority = item.authority;
|
|
1071
|
+
tenant.readServicePrincipal = item.readServicePrincipal;
|
|
1072
|
+
tenant.writeServicePrincipal = item.writeServicePrincipal;
|
|
1073
|
+
// ensure this workspace tracks this tenant
|
|
1074
|
+
let idx = workspace.associatedTenants.findIndex((t) => t === item.tenantId);
|
|
1075
|
+
if (idx == -1) workspace.associatedTenants.push(item.tenantId);
|
|
1076
|
+
});
|
|
976
1077
|
}
|
|
977
|
-
function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object>)
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1078
|
+
function processReturnedConfigs(workspace: Workspace, ii: InitInfo, returnedConfigs: Array<Object>) {
|
|
1079
|
+
// process returned configs
|
|
1080
|
+
returnedConfigs.map((item) => {
|
|
1081
|
+
// are we already tracking this config?
|
|
1082
|
+
let config: Config | null = null;
|
|
1083
|
+
let csIndex = ii.cs.findIndex((c) => c.id === item.id);
|
|
1084
|
+
if (csIndex === -1) {
|
|
1085
|
+
// start tracking
|
|
1086
|
+
let dummyIndex = ii.cs.findIndex((c) => c.id === "1");
|
|
1087
|
+
if (dummyIndex !== -1) {
|
|
1088
|
+
// clear and overwrite dummy
|
|
1089
|
+
config = ii.cs.at(dummyIndex);
|
|
1090
|
+
} else {
|
|
1091
|
+
// create and track new workspace
|
|
1092
|
+
config = new Config();
|
|
1093
|
+
ii.cs.push(config);
|
|
1094
|
+
}
|
|
1095
|
+
} else {
|
|
1096
|
+
// already tracking this config
|
|
1097
|
+
config = ii.cs.at(csIndex);
|
|
1098
|
+
}
|
|
1099
|
+
config!.id = item.id;
|
|
1100
|
+
config!.name = item.name;
|
|
1101
|
+
config!.description = item.description;
|
|
1102
|
+
config!.isEnabled = item.isEnabled;
|
|
1103
|
+
// create TenantConfigInfo array
|
|
1104
|
+
config!.tenants.length = 0;
|
|
1105
|
+
item.tenants.map((tci) => {
|
|
1106
|
+
let tenantConfigInfo = new TenantConfigInfo();
|
|
1107
|
+
tenantConfigInfo.tid = tci.tenantId;
|
|
1108
|
+
tenantConfigInfo.sourceGroupId = tci.sourceGroupId;
|
|
1109
|
+
tenantConfigInfo.sourceGroupName = tci.sourceGroupName;
|
|
1110
|
+
tenantConfigInfo.configurationTenantType = tci.configurationTenantType.toLowerCase();
|
|
1111
|
+
tenantConfigInfo.deltaToken = tci.deltaToken ?? "";
|
|
1112
|
+
config!.tenants.push(tenantConfigInfo);
|
|
1113
|
+
});
|
|
1114
|
+
// ensure this workspace tracks this config
|
|
1115
|
+
let idx = workspace.associatedConfigs.findIndex((c) => c === item.id);
|
|
1116
|
+
if (idx == -1) workspace.associatedConfigs.push(item.id);
|
|
1012
1117
|
});
|
|
1013
|
-
// ensure this workspace tracks this config
|
|
1014
|
-
let idx = workspace.associatedConfigs.findIndex((c) => c === item.id);
|
|
1015
|
-
if (idx == -1) workspace.associatedConfigs.push(item.id);
|
|
1016
|
-
});
|
|
1017
1118
|
}
|
|
1018
1119
|
async function workspaceInfoGet(instance: IPublicClientApplication, authorizedUser: User, user: User, ii: InitInfo, debug: boolean): Promise<APIResult> {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1120
|
+
let result: APIResult = new APIResult();
|
|
1121
|
+
if (debug) debugger;
|
|
1122
|
+
try {
|
|
1123
|
+
result = await workspacesGet(instance, authorizedUser, user, debug);
|
|
1124
|
+
if (result.result) {
|
|
1125
|
+
for (let o of result.array!) {
|
|
1126
|
+
// are we already tracking this workspace?
|
|
1127
|
+
let workspace: Workspace = null;
|
|
1128
|
+
let wsIndex = ii.ws.findIndex((w) => w.id === o.id);
|
|
1129
|
+
if (wsIndex === -1) {
|
|
1130
|
+
// start tracking
|
|
1131
|
+
let dummyIndex = ii.ws.findIndex((w) => w.id === "1");
|
|
1132
|
+
if (dummyIndex !== -1) {
|
|
1133
|
+
// clear and overwrite dummy
|
|
1134
|
+
workspace = ii.ws.at(dummyIndex);
|
|
1135
|
+
}
|
|
1136
|
+
else {
|
|
1137
|
+
// create and track new workspace
|
|
1138
|
+
workspace = new Workspace();
|
|
1139
|
+
ii.ws.push(workspace);
|
|
1140
|
+
}
|
|
1141
|
+
} else {
|
|
1142
|
+
// already tracking this workspace
|
|
1143
|
+
workspace = ii.ws.at(wsIndex);
|
|
1144
|
+
}
|
|
1145
|
+
// clear associations as we are about to reset
|
|
1146
|
+
workspace.associatedUsers.length = 0;
|
|
1147
|
+
workspace.associatedTenants.length = 0;
|
|
1148
|
+
workspace.associatedConfigs.length = 0;
|
|
1149
|
+
workspace.id = o.id;
|
|
1150
|
+
workspace.name = o.name;
|
|
1151
|
+
// parallel GET admins, tenants, configs associated with this workspace
|
|
1152
|
+
let adminsPromise: Promise<APIResult> = adminsGet(instance, authorizedUser, workspace.id, debug);
|
|
1153
|
+
let tenantsPromise: Promise<APIResult> = tenantsGet(instance, authorizedUser, workspace.id, debug);
|
|
1154
|
+
let configsPromise: Promise<APIResult> = configsGet(instance, authorizedUser, workspace.id, debug);
|
|
1155
|
+
// wait for all to finish, return on any failure
|
|
1156
|
+
let [adminsResult, tenantsResult, configsResult] = await Promise.all([adminsPromise, tenantsPromise, configsPromise]);
|
|
1157
|
+
if (!adminsResult.result) return adminsResult;
|
|
1158
|
+
if (!tenantsResult.result) return tenantsResult;
|
|
1159
|
+
if (!configsResult.result) return configsResult;
|
|
1160
|
+
// process returned workspace components
|
|
1161
|
+
processReturnedAdmins(workspace, ii, adminsResult.array!);
|
|
1162
|
+
processReturnedTenants(workspace, ii, tenantsResult.array!);
|
|
1163
|
+
processReturnedConfigs(workspace, ii, configsResult.array!);
|
|
1164
|
+
// tag components with workspaceIDs
|
|
1165
|
+
ii.tagWithWorkspaces();
|
|
1166
|
+
}
|
|
1167
|
+
return result;
|
|
1043
1168
|
}
|
|
1044
|
-
// clear associations as we are about to reset
|
|
1045
|
-
workspace.associatedUsers.length = 0;
|
|
1046
|
-
workspace.associatedTenants.length = 0;
|
|
1047
|
-
workspace.associatedConfigs.length = 0;
|
|
1048
|
-
workspace.id = o.id;
|
|
1049
|
-
workspace.name = o.name;
|
|
1050
|
-
// parallel GET admins, tenants, configs associated with this workspace
|
|
1051
|
-
let adminsPromise: Promise<APIResult> = adminsGet(instance, authorizedUser, workspace.id, debug);
|
|
1052
|
-
let tenantsPromise: Promise<APIResult> = tenantsGet(instance, authorizedUser, workspace.id, debug);
|
|
1053
|
-
let configsPromise: Promise<APIResult> = configsGet(instance, authorizedUser, workspace.id, debug);
|
|
1054
|
-
// wait for all to finish, return on any failure
|
|
1055
|
-
let [adminsResult, tenantsResult, configsResult] = await Promise.all([adminsPromise, tenantsPromise, configsPromise]);
|
|
1056
|
-
if(!adminsResult.result) return adminsResult;
|
|
1057
|
-
if(!tenantsResult.result) return tenantsResult;
|
|
1058
|
-
if(!configsResult.result) return configsResult;
|
|
1059
|
-
// process returned workspace components
|
|
1060
|
-
processReturnedAdmins(workspace, ii, adminsResult.array!);
|
|
1061
|
-
processReturnedTenants(workspace, ii, tenantsResult.array!);
|
|
1062
|
-
processReturnedConfigs(workspace, ii, configsResult.array!);
|
|
1063
|
-
// tag components with workspaceIDs
|
|
1064
|
-
ii.tagWithWorkspaces();
|
|
1065
|
-
}
|
|
1066
|
-
return result;
|
|
1067
1169
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
return result;
|
|
1170
|
+
catch (error: any) {
|
|
1171
|
+
console.log(error.message);
|
|
1172
|
+
result.error = error.message;
|
|
1173
|
+
}
|
|
1174
|
+
result.result = false;
|
|
1175
|
+
result.status = 500;
|
|
1176
|
+
return result;
|
|
1076
1177
|
}
|