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