@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/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
- return a + b;
15
+ return a + b;
15
16
  }
16
- export function helloNpm() : string {
17
- return "hello NPM";
17
+ export function helloNpm(): string {
18
+ return "hello NPM";
18
19
  }
19
20
  export class Group {
20
- id: string;
21
- displayName: string;
22
- description: string;
21
+ id: string;
22
+ displayName: string;
23
+ description: string;
23
24
  }
24
25
  export class User {
25
- oid: string;
26
- name: string;
27
- mail: string;
28
- authority: string;
29
- tid: string;
30
- companyName: string;
31
- companyDomain: string;
32
- associatedWorkspaces: string[];
33
- workspaceIDs: string;
34
- session: string; // button text
35
- spacode: string; // to get front end access token
36
- accessToken: string; // front end access token
37
- loginHint: string; // to help sign out without prompt
38
- scopes: string[]; // to detect if incremental consent has happened
39
- authTS: Date; // timestamp user was authenticated
40
- constructor() {
41
- this.oid = "";
42
- this.name = "";
43
- this.mail = "";
44
- this.authority = "https://login.microsoftonline.com/organizations/v2.0";
45
- this.tid = "";
46
- this.companyName = "";
47
- this.companyDomain = "";
48
- this.associatedWorkspaces = new Array();
49
- this.workspaceIDs = "";
50
- this.session = "Sign In";
51
- this.spacode = "";
52
- this.accessToken = "";
53
- this.loginHint = "";
54
- this.scopes = new Array();
55
- this.authTS = new Date(0);
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
- invalid = 0,
60
- aad = 1,
61
- ad = 2,
62
- googleworkspace = 3
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
- read = 1,
67
- write = 2,
68
- notassigned = 3
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
- tid: string;
73
- name: string;
74
- domain: string;
75
- tenantType: TenantTypeStrings;
76
- permissionType: TenantPermissionTypeStrings;
77
- onboarded: string;
78
- authority: string;
79
- readServicePrincipal: string;
80
- writeServicePrincipal: string;
81
- workspaceIDs: string;
82
- constructor(){
83
- this.tid = "";
84
- this.name = "";
85
- this.domain = "";
86
- this.tenantType = "aad";
87
- this.permissionType = "notassigned";
88
- this.onboarded = "false";
89
- this.authority = "https://login.microsoftonline.com/organizations/v2.0";
90
- this.readServicePrincipal = "";
91
- this.writeServicePrincipal = "";
92
- this.workspaceIDs = "";
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
- source = 1,
97
- target = 2,
98
- sourcetarget = 3
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
- tid: string;
103
- sourceGroupId: string;
104
- sourceGroupName: string;
105
- configurationTenantType: TenantConfigTypeStrings;
106
- constructor(){
107
- this.tid = "";
108
- this.sourceGroupId = "";
109
- this.sourceGroupName = "";
110
- this.configurationTenantType = "source";
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
- id: string;
115
- workspaceId: string;
116
- name: string;
117
- description: string;
118
- tenants: TenantConfigInfo[];
119
- isEnabled: boolean;
120
- workspaceIDs: string;
121
- constructor(){
122
- this.id = "";
123
- this.name = "";
124
- this.description = "";
125
- this.tenants = new Array();
126
- this.isEnabled = false;
127
- this.workspaceIDs = "";
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
- id: string;
132
- name: string;
133
- associatedUsers: string[];
134
- associatedTenants: string[];
135
- associatedConfigs: string[];
136
- constructor(){
137
- this.id = "";
138
- this.name = "";
139
- this.associatedUsers = new Array();
140
- this.associatedTenants = new Array();
141
- this.associatedConfigs = new Array();
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
- let storage;
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
- this.us = deserializeArray(User, usersString);
204
- this.ts = deserializeArray(Tenant, tenantsString);
205
- this.cs = deserializeArray(Config, configsString);
206
- this.ws = deserializeArray(Workspace, workspacesString);
207
- this.tagWithWorkspaces();
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
- debugger;
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
- save(): void{
213
- let initInfoString: string = JSON.stringify(this);
214
- localStorage.setItem("InitInfo", initInfoString);
215
- }
216
- tagWithWorkspaces(): boolean {
217
- // first clear everyone's workspaceIDs
218
- this.us.map((item) => item.workspaceIDs = "");
219
- this.ts.map((item) => item.workspaceIDs = "");
220
- this.cs.map((item) => item.workspaceIDs = "");
221
- // for each workspace tag WorkspaceIDs of associated Users, Tenants, Configs
222
- for (let workspace of this.ws) {
223
- // find matching Users to tag with this workspace
224
- for (let userID of workspace.associatedUsers) {
225
- let user = this.us.find((currentUser) => currentUser.oid === userID);
226
- if (user !== undefined) {
227
- // we found the user
228
- user[FILTER_FIELD] += workspace.id;
229
- user[FILTER_FIELD] += " ";
230
- } else {
231
- // we should not have InitInfo missing Workspace components
232
- debugger;
233
- return false;
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
- // find matching Tenants to tag with this workspace
237
- for (let tenantID of workspace.associatedTenants) {
238
- let tenant = this.ts.find(
239
- (currentTenant) => currentTenant.tid === tenantID
240
- );
241
- if (tenant !== undefined) {
242
- // we found the tenant
243
- tenant[FILTER_FIELD] += workspace.id;
244
- tenant[FILTER_FIELD] += " ";
245
- } else {
246
- // we should not have InitInfo missing Workspace components
247
- debugger;
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
- // find matching Configs to tag with this workspace
252
- for (let configID of workspace.associatedConfigs) {
253
- let config = this.cs.find(
254
- (currentConfig) => currentConfig.id === configID
255
- );
256
- if (config !== undefined) {
257
- // we found the config
258
- config[FILTER_FIELD] += workspace.id;
259
- config[FILTER_FIELD] += " ";
260
- } else {
261
- // we should not have InitInfo missing Workspace components
262
- debugger;
263
- return false;
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
- "authenticate user" |
283
- "reload React" |
284
- "GET tenant details" |
285
- "POST config init" |
286
- "GET workspaces" |
287
- "onboard tenant" |
288
- "create 2nd tenant" |
289
- "invite 2nd admin" |
290
- "onboard 2nd tenant" |
291
- "create config";
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
- tasks: Task[];
294
- constructor(bClearLocalStorage: boolean) {
295
- this.tasks = [ new Task() ];
296
- this.init(bClearLocalStorage);
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
- // then try localStorage
308
- if (storageAvailable("localStorage")) {
309
- let result = localStorage.getItem("Tasks");
310
- if (result != null && typeof result === "string" && result !== "") {
311
- // properly create Tasks and Dates from retrieved string
312
- let tasksString: string = result;
313
- let taskArray: TaskArray = JSON.parse(tasksString);
314
- this.tasks = this.#initTasksFromObjects(taskArray.tasks);
315
- let l = this.tasks.length;
316
- if (l !== 0) return;
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
- // if here, there was nothing in localStorage, use initialization file
320
- this.tasks = this.#initTasksFromObjects(tasksData);
321
- }
322
- // set start time for a task
323
- setTaskStart(taskType: TaskType, startDate: Date): void {
324
- let task: Task | undefined = this.#findTask(taskType);
325
- if (task != undefined && task != null) {
326
- task.setStart(startDate);
327
- task.status = "in progress";
328
- this.#save();
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
- else {
331
- debugger;
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
- // set end time for a task
335
- setTaskEnd(taskType: TaskType, endDate: Date, status: string): void {
336
- let task: Task | undefined = this.#findTask(taskType);
337
- if (task != undefined && task != null) {
338
- task.setEnd(endDate);
339
- task.status = status;
340
- this.#save();
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
- else {
343
- debugger;
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
- // private
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
- id: number;
396
- task: string;
397
- start: Date;
398
- startDisplay: string;
399
- end: Date;
400
- endDisplay: string;
401
- elapsedDisplay: string;
402
- expected: number;
403
- status: string;
404
- expanded: boolean;
405
- subtasks: Task[];
406
- setEnd(endDate: Date): void{
407
- this.end = endDate;
408
- this.endDisplay = `${this.end.getMinutes().toString().padStart(2, "0")}:${this.end.getSeconds().toString().padStart(2, "0")}`;
409
- let minuteAdjustment: number = 0;
410
- let elapsedSeconds: number = this.end.getSeconds() - this.start.getSeconds();
411
- if (elapsedSeconds < 0) { elapsedSeconds += 60; minuteAdjustment = -1; }
412
- let elapsedMinutes: number = this.end.getMinutes() - this.start.getMinutes() + minuteAdjustment;
413
- if (elapsedMinutes < 0) elapsedMinutes += 60;
414
- this.elapsedDisplay = `${elapsedMinutes.toString().padStart(2, "0")}:${elapsedSeconds.toString().padStart(2, "0")}`;
415
- };
416
- setStart(startDate: Date): void{
417
- this.start = startDate;
418
- this.startDisplay = `${this.start.getMinutes().toString().padStart(2, "0")}:${this.start.getSeconds().toString().padStart(2, "0")}`;
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
- tenantNodes: TenantNode[];
424
- constructor(
425
- config: Config | null,
426
- syncPortalGlobalState: InitInfo | null,
427
- bClearLocalStorage: boolean
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
- // create a sourceTenantNode for each Source and SourceTarget
457
- config.tenants.map((tciPotentialSource: TenantConfigInfo) => {
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
- tciPotentialSource.configurationTenantType === "source" ||
460
- tciPotentialSource.configurationTenantType === "sourcetarget"
452
+ config != null &&
453
+ config.tenants != null &&
454
+ syncPortalGlobalState != null
461
455
  ) {
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
- }
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
- // 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
- }
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
- // 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
- // });
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
- // start SignalR connection
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
- // cycle through desired states for sources and targets
543
- if (this.tenantNodes != null) {
544
- this.tenantNodes.map((sourceTenantNode: TenantNode) => {
545
- if (sourceTenantNode.read == 0) sourceTenantNode.update(100, 50, 0, 0);
546
- else if (sourceTenantNode.read == 50) sourceTenantNode.update(100, 100, 0, 0);
547
- else sourceTenantNode.update(0, 0, 0, 0);
548
- if (sourceTenantNode.targets != null) {
549
- sourceTenantNode.targets.map((targetTenantNode: TenantNode) => {
550
- if (targetTenantNode.written == 0) targetTenantNode.update(100, 0, 50, 0);
551
- else if (targetTenantNode.written == 50) targetTenantNode.update(100, 0, 100, 0);
552
- else if (targetTenantNode.written == 100) targetTenantNode.update(100, 0, 99, 1);
553
- else targetTenantNode.update(0, 0, 0, 0);
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
- expanded: boolean;
562
- status: string;
563
- name: string;
564
- tid: string;
565
- total: number;
566
- read: number;
567
- written: number;
568
- deferred: number;
569
- targets: TenantNode[];
570
- constructor(tid: string, name: string) {
571
- this.expanded = false;
572
- this.name = name;
573
- this.tid = tid;
574
- this.targets = new Array<TenantNode>();
575
- this.update(0, 0, 0, 0);
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
- else if(this.written > 0) {
588
- if(this.written + this.deferred < this.total) this.status = "in progress";
589
- else if(this.written === this.total) this.status = "complete";
590
- else if(this.written + this.deferred === this.total) this.status = "failed";
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
- result: boolean;
596
- status: number;
597
- error: string;
598
- array: Array<Object> | null;
599
- constructor() { this.result = true; this.status = 200; this.error = ""; this.array = null; }
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
- // need a read or write access token to get graph users
607
- let accessToken: string = "";
608
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
609
- accessToken = tenant.readServicePrincipal;
610
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
611
- accessToken = tenant.writeServicePrincipal;
612
- if(accessToken === "") return { group: "", error: "no access token specified" };
613
- // prepare Authorization headers as part of options
614
- const headers = new Headers();
615
- const bearer = `Bearer ${accessToken}`;
616
- headers.append("Authorization", bearer);
617
- let options = { method: "GET", headers: headers };
618
- // make /groups endpoint call
619
- try {
620
- let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/${groupid}`;
621
- let response = await fetch(groupsEndpoint, options);
622
- let data = await response.json();
623
- if(typeof data.error !== "undefined"){
624
- return { group: "", error: `${data.error.code}: ${data.error.message}` };
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
- // need a read or write access token to get graph users
636
- let accessToken: string = "";
637
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
638
- accessToken = tenant.readServicePrincipal;
639
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
640
- accessToken = tenant.writeServicePrincipal;
641
- if(accessToken === "") return { groups: [], error: "no access token specified" };
642
- // prepare Authorization headers as part of options
643
- const headers = new Headers();
644
- const bearer = `Bearer ${accessToken}`;
645
- headers.append("Authorization", bearer);
646
- let options = { method: "GET", headers: headers };
647
- // make /groups endpoint call
648
- try {
649
- let groupsEndpoint = `${graphConfig.graphGroupsEndpoint}/?$filter=startsWith(displayName, '${groupSearchString}')`;
650
- let response = await fetch(groupsEndpoint, options);
651
- let data = await response.json();
652
- if(typeof data.error !== "undefined"){
653
- return { groups: [], error: `${data.error.code}: ${data.error.message}` };
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
- let tenantURL: string = window.location.href;
664
- tenantURL += "MicrosoftIdentity/Account/Challenge";
665
- let url: URL = new URL(tenantURL);
666
- url.searchParams.append("redirectUri", window.location.origin);
667
- url.searchParams.append("scope", "openid offline_access profile user.read contacts.read CrossTenantInformation.ReadBasic.All");
668
- url.searchParams.append("domainHint", "organizations");
669
- if (user.oid !== "1"){
670
- url.searchParams.append("loginHint", user.mail);
671
- }
672
- tasks.setTaskStart("initialization", new Date());
673
- tasks.setTaskStart("authenticate user", new Date());
674
- window.location.assign(url.href);
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
- if (user.oid == "1") return;
678
- let tenantURL: string = window.location.href;
679
- tenantURL += "MicrosoftIdentity/Account/Challenge";
680
- let url: URL = new URL(tenantURL);
681
- url.searchParams.append("redirectUri", window.location.origin);
682
- let scopes = scope;
683
- url.searchParams.append("scope", scopes);
684
- url.searchParams.append("domainHint", "organizations");
685
- url.searchParams.append("loginHint", user.mail);
686
- window.location.assign(url.href);
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
- if (user.oid == "1") return;
690
- // these lines provide more callbacks during logout
691
- //let tenantURL: string = window.location.href;
692
- //tenantURL += "MicrosoftIdentity/Account/SignOut";
693
- // this line takes advantage of our saved loginHint to logout right away, but requires additional cleanup logic
694
- // https://aaddevsup.azurewebsites.net/2022/03/how-to-logout-of-an-oauth2-application-without-getting-prompted-to-select-a-user/
695
- let tenantURL: string = "https://login.microsoftonline.com/common/oauth2/logout";
696
- let url: URL = new URL(tenantURL);
697
- url.searchParams.append("post_logout_redirect_uri", window.location.origin);
698
- url.searchParams.append("logout_hint", user.loginHint);
699
- window.location.assign(url.href);
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
- if (debug) debugger;
704
- // do we already have a valid tenant name? if so, nothing to add
705
- if (typeof tenant.name !== 'undefined' && tenant.name !== "") return false;
706
- // if needed, retrieve and cache access token
707
- if (typeof loggedInUser.accessToken === 'undefined' || loggedInUser.accessToken === "") {
708
- console.log(`tenantRelationshipsGetByDomain called with invalid logged in user: ${loggedInUser.name}`);
709
- try {
710
- let response: AuthenticationResult = await instance.acquireTokenByCode({ code: loggedInUser.spacode });
711
- loggedInUser.accessToken = response.accessToken; // cache access token on the user
712
- console.log("Front end token acquired: " + loggedInUser.accessToken.slice(0,20));
713
- }
714
- catch(error: any) {
715
- console.log("Front end token failure: " + error);
716
- return false; // failed to get access token, no need to re-render
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
- // prepare Authorization headers as part of options
720
- const headers = new Headers();
721
- const bearer = `Bearer ${loggedInUser.accessToken}`;
722
- headers.append("Authorization", bearer);
723
- let options = { method: "GET", headers: headers };
724
- // make tenant endpoint call
725
- try {
726
- // create tenant info endpoint
727
- var tenantEndpoint = graphConfig.graphTenantByDomainEndpoint;
728
- tenantEndpoint += "(domainName='";
729
- tenantEndpoint += tenant.domain;
730
- tenantEndpoint += "')";
731
- console.log("Attempting GET from /findTenantInformationByDomainName:", tenantEndpoint);
732
- let response = await fetch(tenantEndpoint, options);
733
- let data = await response.json();
734
- if(data) {
735
- if(typeof data.error !== "undefined") {
736
- console.log("Failed GET from /findTenantInformationByDomainName: ", data.error.message);
737
- return false;
738
- }
739
- else if (typeof data.displayName !== undefined && data.displayName !== "") {
740
- // set domain information on passed tenant
741
- tenant.tid = data.tenantId;
742
- tenant.name = data.displayName;
743
- console.log("Successful GET from /findTenantInformationByDomainName: ", data.displayName);
744
- return true; // success, need UX to re-render
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
- else{
748
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
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
- if (debug) debugger;
760
- // do we already have a valid company name? if so, nothing to add, no need for UX to re-render
761
- if (typeof user.companyName !== 'undefined' && user.companyName !== "") return false;
762
- // if needed, retrieve and cache access token
763
- if (typeof user.accessToken === 'undefined' || user.accessToken === "") {
764
- try {
765
- let response: AuthenticationResult = await instance.acquireTokenByCode({ code: user.spacode });
766
- user.accessToken = response.accessToken; // cache access token
767
- console.log("Front end token acquired: " + user.accessToken.slice(0,20));
768
- }
769
- catch(error: any) {
770
- console.log("Front end token failure: " + error);
771
- return false; // failed to get access token, no need to re-render
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
- // prepare Authorization headers as part of options
775
- const headers = new Headers();
776
- const bearer = `Bearer ${user.accessToken}`;
777
- headers.append("Authorization", bearer);
778
- let options = { method: "GET", headers: headers };
779
- // make tenant endpoint call
780
- try {
781
- // create tenant info endpoint
782
- var tenantEndpoint = graphConfig.graphTenantByIdEndpoint;
783
- tenantEndpoint += "(tenantId='";
784
- tenantEndpoint += user.tid;
785
- tenantEndpoint += "')";
786
- // track time of tenant details query
787
- tasks.setTaskStart("GET tenant details", new Date());
788
- console.log("Attempting GET from /findTenantInformationByTenantId:", tenantEndpoint);
789
- let response = await fetch(tenantEndpoint, options);
790
- let data = await response.json();
791
- if(data && typeof data.displayName !== undefined && data.displayName !== "") {
792
- // set domain information on user
793
- user.companyName = data.displayName;
794
- user.companyDomain = data.defaultDomainName;
795
- // set domain information on tenant
796
- let tenant: Tenant | undefined = ii.ts.find((t) => t.tid === user.tid);
797
- if(tenant !== undefined){
798
- tenant.name = data.displayName;
799
- tenant.domain = data.defaultDomainName;
800
- }
801
- else{
802
- console.log("tenantRelationshipsGetById: missing associated tenant for logged in user.");
803
- debugger;
804
- }
805
- console.log("Successful GET from /findTenantInformationByTenantId: ", data.displayName);
806
- tasks.setTaskEnd("GET tenant details", new Date(), "complete");
807
- return true; // success, need UX to re-render
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
- else{
810
- console.log("Failed to GET from /findTenantInformationByTenantId: ", tenantEndpoint);
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
- // need a read or write access token to get graph users
824
- let accessToken: string = "";
825
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.read])
826
- accessToken = tenant.readServicePrincipal;
827
- if(tenant.permissionType === TenantPermissionType[TenantPermissionType.write])
828
- accessToken = tenant.writeServicePrincipal;
829
- if(accessToken === "") return { users: [], error: "no access token specified" };
830
- // prepare Authorization headers as part of options
831
- const headers = new Headers();
832
- const bearer = `Bearer ${accessToken}`;
833
- headers.append("Authorization", bearer);
834
- let options = { method: "GET", headers: headers };
835
- // make /users endpoint call
836
- try {
837
- let response = await fetch(graphConfig.graphUsersEndpoint, options);
838
- let data = await response.json();
839
- if(typeof data.error !== "undefined"){
840
- return { users: [], error: `${data.error.code}: ${data.error.message}` };
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
- let users = new Array<User>();
843
- for (let user of data.value) {
844
- users.push(user.mail);
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
- let result: APIResult = new APIResult();
858
- if (config.id === "1") {
859
- result = await configPost(instance, authorizedUser, config, workspaceId, debug);
860
- }
861
- else {
862
- result = await configPut(instance, authorizedUser, config, debug);
863
- }
864
- return result;
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
- return configDelete(instance, authorizedUser, config, workspaceId, debug);
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
- let result: APIResult = new APIResult();
873
- if (debug) debugger;
874
- // get tenant name and domain from AAD
875
- result.result = await tenantRelationshipsGetById(user, ii, instance, tasks, debug);
876
- // 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
877
- if (result.result) {
878
- tasks.setTaskStart("POST config init", new Date());
879
- result = await initPost(instance, authorizedUser, user, debug);
880
- tasks.setTaskEnd("POST config init", new Date(), result.result ? "complete" : "failed");
881
- }
882
- // simlarly, if we just did our first post, then query config backend for workspace(s) associated with this user
883
- if (result.result) {
884
- tasks.setTaskStart("GET workspaces", new Date());
885
- result = await workspaceInfoGet(instance, authorizedUser, user, ii, debug);
886
- tasks.setTaskEnd("GET workspaces", new Date(), result ? "complete" : "failed");
887
- }
888
- if(result.result) result.error = version;
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
- return tenantPost(instance, authorizedUser, tenant, workspaceId);
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
- return tenantPut(instance, authorizedUser, tenant, debug);
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
- return tenantDelete(instance, authorizedUser, tenant, workspaceId, debug);
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
- return adminPost(instance, authorizedUser, user, workspaceId);
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
- return adminDelete(instance, authorizedUser, user, workspaceId);
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
- returnedAdmins.map((item) => {
912
- // are we already tracking this user?
913
- let user: User|null = null;
914
- let usIndex = ii.us.findIndex((u) => u.oid === item.userId);
915
- if(usIndex===-1) {
916
- // start tracking
917
- let dummyIndex = ii.us.findIndex((u) => u.oid === "1");
918
- if(dummyIndex!==-1) {
919
- // clear and overwrite dummy
920
- user = ii.us.at(dummyIndex);
921
- user.associatedWorkspaces.length = 0;
922
- }
923
- else {
924
- // create and track new user
925
- user = new User();
926
- ii.us.push(user);
927
- }
928
- } else {
929
- // already tracking this user
930
- user = ii.us.at(usIndex);
931
- }
932
- // refresh all the data available from the server
933
- user.oid = item.userId;
934
- user.name = item.firstName;
935
- user.mail = item.email;
936
- user.tid = item.tenantId;
937
- // ensure this workspace tracks this user
938
- let idx = workspace.associatedUsers.findIndex((u) => u === item.userId);
939
- if(idx == -1) workspace.associatedUsers.push(item.userId);
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
- returnedTenants.map((item) => {
945
- // are we already tracking this tenant?
946
- let tenant: Tenant|null = null;
947
- let tsIndex = ii.ts.findIndex((t) => t.tid === item.tenantId);
948
- if (tsIndex === -1) {
949
- // start tracking
950
- let dummyIndex = ii.ts.findIndex((t) => t.tid === "1");
951
- if (dummyIndex !== -1) {
952
- // clear and overwrite dummy
953
- tenant = ii.ts.at(dummyIndex);
954
- } else {
955
- // create and track new workspace
956
- tenant = new Tenant();
957
- ii.ts.push(tenant);
958
- }
959
- } else {
960
- // already tracking this tenant
961
- tenant = ii.ts.at(tsIndex);
962
- }
963
- tenant.tid = item.tenantId;
964
- tenant.name = item.name;
965
- tenant.domain = item.domain;
966
- tenant.tenantType = item.type.toLowerCase(); // should now be strings
967
- tenant.permissionType = item.permissionType.toLowerCase(); // should now be strings
968
- tenant.onboarded = item.isOnboarded ? "true" : "false";
969
- tenant.authority = item.authority;
970
- tenant.readServicePrincipal = item.readServicePrincipal;
971
- tenant.writeServicePrincipal = item.writeServicePrincipal;
972
- // ensure this workspace tracks this tenant
973
- let idx = workspace.associatedTenants.findIndex((t) => t === item.tenantId);
974
- if (idx == -1) workspace.associatedTenants.push(item.tenantId);
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
- // process returned configs
980
- returnedConfigs.map((item) => {
981
- // are we already tracking this config?
982
- let config: Config | null = null;
983
- let csIndex = ii.cs.findIndex((c) => c.id === item.id);
984
- if (csIndex === -1) {
985
- // start tracking
986
- let dummyIndex = ii.cs.findIndex((c) => c.id === "1");
987
- if (dummyIndex !== -1) {
988
- // clear and overwrite dummy
989
- config = ii.cs.at(dummyIndex);
990
- } else {
991
- // create and track new workspace
992
- config = new Config();
993
- ii.cs.push(config);
994
- }
995
- } else {
996
- // already tracking this config
997
- config = ii.cs.at(csIndex);
998
- }
999
- config!.id = item.id;
1000
- config!.name = item.name;
1001
- config!.description = item.description;
1002
- config!.isEnabled = item.isEnabled;
1003
- // create TenantConfigInfo array
1004
- config!.tenants.length = 0;
1005
- item.tenants.map((tci) => {
1006
- let tenantConfigInfo = new TenantConfigInfo();
1007
- tenantConfigInfo.tid = tci.tenantId;
1008
- tenantConfigInfo.sourceGroupId = tci.sourceGroupId;
1009
- tenantConfigInfo.sourceGroupName = tci.sourceGroupName;
1010
- tenantConfigInfo.configurationTenantType = tci.configurationTenantType.toLowerCase();
1011
- config!.tenants.push(tenantConfigInfo);
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
- let result: APIResult = new APIResult();
1020
- if (debug) debugger;
1021
- try {
1022
- result = await workspacesGet(instance, authorizedUser, user, debug);
1023
- if (result.result) {
1024
- for (let o of result.array!) {
1025
- // are we already tracking this workspace?
1026
- let workspace: Workspace = null;
1027
- let wsIndex = ii.ws.findIndex((w) => w.id === o.id);
1028
- if (wsIndex === -1) {
1029
- // start tracking
1030
- let dummyIndex = ii.ws.findIndex((w) => w.id === "1");
1031
- if(dummyIndex !== -1) {
1032
- // clear and overwrite dummy
1033
- workspace = ii.ws.at(dummyIndex);
1034
- }
1035
- else {
1036
- // create and track new workspace
1037
- workspace = new Workspace();
1038
- ii.ws.push(workspace);
1039
- }
1040
- } else {
1041
- // already tracking this workspace
1042
- workspace = ii.ws.at(wsIndex);
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
- catch (error: any) {
1070
- console.log(error.message);
1071
- result.error = error.message;
1072
- }
1073
- result.result = false;
1074
- result.status = 500;
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
  }