diff --git a/jslib b/jslib index b5db9edc..c3dad8fd 160000 --- a/jslib +++ b/jslib @@ -1 +1 @@ -Subproject commit b5db9edc3f930a4553f1bdab347cf8468f282a54 +Subproject commit c3dad8fd1ae862476ccc417b4d33eecd3edd61a9 diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 92f60d47..6b240e56 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -23,10 +23,12 @@ import { ModalComponent } from 'jslib/angular/components/modal.component'; import { BroadcasterService } from 'jslib/angular/services/broadcaster.service'; +import { ApiService } from 'jslib/abstractions/api.service'; import { AuthService } from 'jslib/abstractions/auth.service'; import { I18nService } from 'jslib/abstractions/i18n.service'; import { MessagingService } from 'jslib/abstractions/messaging.service'; import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service'; +import { StateService } from 'jslib/abstractions/state.service'; import { StorageService } from 'jslib/abstractions/storage.service'; import { TokenService } from 'jslib/abstractions/token.service'; import { UserService } from 'jslib/abstractions/user.service'; @@ -66,12 +68,19 @@ export class AppComponent implements OnInit { private toasterService: ToasterService, private i18nService: I18nService, private platformUtilsService: PlatformUtilsService, private ngZone: NgZone, private componentFactoryResolver: ComponentFactoryResolver, private messagingService: MessagingService, - private configurationService: ConfigurationService, private syncService: SyncService) { } + private configurationService: ConfigurationService, private syncService: SyncService, + private stateService: StateService, private apiService: ApiService) { } ngOnInit() { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { this.ngZone.run(async () => { switch (message.command) { + case 'loggedIn': + if (await this.userService.isAuthenticated()) { + const profile = await this.apiService.getProfile(); + this.stateService.save('profileOrganizations', profile.organizations); + } + break; case 'logout': this.logOut(!!message.expired); break; diff --git a/src/app/services/services.module.ts b/src/app/services/services.module.ts index 631dab9f..37a9ff73 100644 --- a/src/app/services/services.module.ts +++ b/src/app/services/services.module.ts @@ -101,6 +101,13 @@ export function initFactory(): Function { if (installAction != null) { await storageService.save(ConstantsService.installedVersionKey, currentVersion); } + + window.setTimeout(async () => { + if (await userService.isAuthenticated()) { + const profile = await apiService.getProfile(); + stateService.save('profileOrganizations', profile.organizations); + } + }, 500); }; } diff --git a/src/app/tabs/dashboard.component.ts b/src/app/tabs/dashboard.component.ts index 4a4f6663..bdf9b73d 100644 --- a/src/app/tabs/dashboard.component.ts +++ b/src/app/tabs/dashboard.component.ts @@ -1,6 +1,8 @@ import { + ChangeDetectorRef, Component, NgZone, + OnDestroy, OnInit, } from '@angular/core'; @@ -27,7 +29,7 @@ const BroadcasterSubscriptionId = 'DashboardComponent'; selector: 'app-dashboard', templateUrl: 'dashboard.component.html', }) -export class DashboardComponent implements OnInit { +export class DashboardComponent implements OnInit, OnDestroy { simGroups: GroupEntry[]; simUsers: UserEntry[]; simEnabledUsers: UserEntry[] = []; @@ -35,7 +37,7 @@ export class DashboardComponent implements OnInit { simDeletedUsers: UserEntry[] = []; simPromise: Promise; simSinceLast: boolean = false; - syncPromise: Promise; + syncPromise: Promise<[GroupEntry[], UserEntry[]]>; startPromise: Promise; lastGroupSync: Date; lastUserSync: Date; @@ -44,7 +46,7 @@ export class DashboardComponent implements OnInit { constructor(private i18nService: I18nService, private syncService: SyncService, private configurationService: ConfigurationService, private broadcasterService: BroadcasterService, private ngZone: NgZone, private messagingService: MessagingService, - private toasterService: ToasterService) { } + private toasterService: ToasterService, private changeDetectorRef: ChangeDetectorRef) { } async ngOnInit() { this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { @@ -54,13 +56,20 @@ export class DashboardComponent implements OnInit { this.updateLastSync(); break; default: + break; } + + this.changeDetectorRef.detectChanges(); }); }); this.updateLastSync(); } + async ngOnDestroy() { + this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); + } + async start() { this.startPromise = this.syncService.sync(false, false); await this.startPromise; @@ -77,7 +86,10 @@ export class DashboardComponent implements OnInit { async sync() { this.syncPromise = this.syncService.sync(false, false); - await this.syncPromise; + const result = await this.syncPromise; + const groupCount = result[0] != null ? result[0].length : 0; + const userCount = result[1] != null ? result[1].length : 0; + this.toasterService.popAsync('success', null, 'Synced ' + groupCount + ' groups and ' + userCount + ' users.'); } async simulate() { diff --git a/src/app/tabs/settings.component.html b/src/app/tabs/settings.component.html index 1553d5cc..9fc7bae2 100644 --- a/src/app/tabs/settings.component.html +++ b/src/app/tabs/settings.component.html @@ -114,6 +114,18 @@ + +
+

{{'organization' | i18n}}

+
+
+ + +
+
+
diff --git a/src/app/tabs/settings.component.ts b/src/app/tabs/settings.component.ts index a3a553f3..83d2ba10 100644 --- a/src/app/tabs/settings.component.ts +++ b/src/app/tabs/settings.component.ts @@ -7,6 +7,9 @@ import { } from '@angular/core'; import { I18nService } from 'jslib/abstractions/i18n.service'; +import { StateService } from 'jslib/abstractions/state.service'; + +import { ProfileOrganizationResponse } from 'jslib/models/response/profileOrganizationResponse'; import { ConfigurationService } from '../../services/configuration.service'; @@ -30,10 +33,13 @@ export class SettingsComponent implements OnInit, OnDestroy { azure = new AzureConfiguration(); okta = new OktaConfiguration(); sync = new SyncConfiguration(); + organizationId: string; directoryOptions: any[]; + organizationOptions: any[]; constructor(private i18nService: I18nService, private configurationService: ConfigurationService, - private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone) { + private changeDetectorRef: ChangeDetectorRef, private ngZone: NgZone, + private stateService: StateService) { this.directoryOptions = [ { name: i18nService.t('select'), value: null }, { name: 'Active Directory / LDAP', value: DirectoryType.Ldap }, @@ -44,6 +50,15 @@ export class SettingsComponent implements OnInit, OnDestroy { } async ngOnInit() { + this.organizationOptions = [{ name: this.i18nService.t('select'), value: null }]; + const orgs = await this.stateService.get('profileOrganizations'); + if (orgs != null) { + for (const org of orgs) { + this.organizationOptions.push({ name: org.name, value: org.id }); + } + } + + this.organizationId = await this.configurationService.getOrganizationId(); this.directory = await this.configurationService.getDirectoryType(); this.ldap = (await this.configurationService.getDirectory(DirectoryType.Ldap)) || this.ldap; @@ -74,6 +89,7 @@ export class SettingsComponent implements OnInit, OnDestroy { this.sync.groupNameAttribute = 'name'; } + await this.configurationService.saveOrganizationId(this.organizationId); await this.configurationService.saveDirectoryType(this.directory); await this.configurationService.saveDirectory(DirectoryType.Ldap, this.ldap); await this.configurationService.saveDirectory(DirectoryType.GSuite, this.gsuite); diff --git a/src/services/configuration.service.ts b/src/services/configuration.service.ts index e8e65eed..e5cffdde 100644 --- a/src/services/configuration.service.ts +++ b/src/services/configuration.service.ts @@ -107,9 +107,9 @@ export class ConfigurationService { async saveDirectoryType(type: DirectoryType) { const currentType = await this.getDirectoryType(); if (type !== currentType) { - await this.saveUserDeltaToken(null); - await this.saveGroupDeltaToken(null); + await this.clearStatefulSettings(); } + return this.storageService.save(Keys.directoryType, type); } @@ -137,8 +137,12 @@ export class ConfigurationService { } } - getLastUserSyncDate(): Promise { - return this.storageService.get(Keys.lastUserSync); + async getLastUserSyncDate(): Promise { + const dateString = await this.storageService.get(Keys.lastUserSync); + if (dateString == null) { + return null; + } + return new Date(dateString); } saveLastUserSyncDate(date: Date) { @@ -149,8 +153,12 @@ export class ConfigurationService { } } - getLastGroupSyncDate(): Promise { - return this.storageService.get(Keys.lastGroupSync); + async getLastGroupSyncDate(): Promise { + const dateString = await this.storageService.get(Keys.lastGroupSync); + if (dateString == null) { + return null; + } + return new Date(dateString); } saveLastGroupSyncDate(date: Date) { @@ -177,11 +185,23 @@ export class ConfigurationService { return this.storageService.get(Keys.organizationId); } - saveOrganizationId(id: string) { + async saveOrganizationId(id: string) { + const currentId = await this.getOrganizationId(); + if (currentId !== id) { + await this.clearStatefulSettings(); + } + if (id == null) { return this.storageService.remove(Keys.organizationId); } else { return this.storageService.save(Keys.organizationId, id); } } + + private async clearStatefulSettings() { + await this.saveUserDeltaToken(null); + await this.saveGroupDeltaToken(null); + await this.saveLastGroupSyncDate(null); + await this.saveLastUserSyncDate(null); + } } diff --git a/src/services/okta-directory.service.ts b/src/services/okta-directory.service.ts index 80a0c40b..7b4a88ad 100644 --- a/src/services/okta-directory.service.ts +++ b/src/services/okta-directory.service.ts @@ -121,7 +121,7 @@ export class OktaDirectoryService extends BaseDirectoryService implements Direct return baseFilter; } - const updatedFilter = 'lastUpdated gt ' + lastSync.toISOString(); + const updatedFilter = 'lastUpdated gt "' + lastSync.toISOString() + '"'; if (baseFilter == null) { return updatedFilter; } diff --git a/src/services/sync.service.ts b/src/services/sync.service.ts index 12306462..57648543 100644 --- a/src/services/sync.service.ts +++ b/src/services/sync.service.ts @@ -1,6 +1,7 @@ import { DirectoryType } from '../enums/directoryType'; import { GroupEntry } from '../models/groupEntry'; +import { SyncConfiguration } from '../models/syncConfiguration'; import { UserEntry } from '../models/userEntry'; import { ImportDirectoryRequest } from 'jslib/models/request/importDirectoryRequest'; @@ -51,14 +52,18 @@ export class SyncService { this.messagingService.send('dirSyncStarted'); try { const entries = await directoryService.getEntries(force, test); - const groups = entries[0]; - const users = entries[1]; + let groups = entries[0]; + let users = entries[1]; if (groups != null && groups.length > 0) { this.flattenUsersToGroups(groups, null, groups); } if (test || groups == null || groups.length === 0 || users == null || users.length === 0) { + if (!test) { + await this.saveSyncTimes(syncConfig, now); + } + this.messagingService.send('dirSyncCompleted', { successfully: true }); return [groups, users]; } @@ -81,14 +86,12 @@ export class SyncService { const res = await this.apiService.postImportDirectory(orgId, req); await this.configurationService.saveLastSyncHash(hash); - if (syncConfig.groups) { - await this.configurationService.saveLastGroupSyncDate(now); - } - if (syncConfig.users) { - await this.configurationService.saveLastUserSyncDate(now); - } + } else { + groups = null; + users = null; } + await this.saveSyncTimes(syncConfig, now); this.messagingService.send('dirSyncCompleted', { successfully: true }); return [groups, users]; } catch (e) { @@ -156,4 +159,13 @@ export class SyncService { return model; } + + private async saveSyncTimes(syncConfig: SyncConfiguration, time: Date) { + if (syncConfig.groups) { + await this.configurationService.saveLastGroupSyncDate(time); + } + if (syncConfig.users) { + await this.configurationService.saveLastUserSyncDate(time); + } + } }