mirror of
https://github.com/clearml/clearml-web
synced 2025-06-26 18:27:02 +00:00
@@ -27,11 +27,17 @@
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
"src/app/webapp-common/assets"
|
||||
"src/app/webapp-common/assets",
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "node_modules/ace-builds/src-min",
|
||||
"output": "./assets/ace-builds/"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"node_modules/primeicons/primeicons.css",
|
||||
"node_modules/primeng/resources/components/table/table.css",
|
||||
"node_modules/ngx-markdown-editor/assets/highlight.js/agate.min.css",
|
||||
"src/styles.scss",
|
||||
"src/fonts.scss"
|
||||
],
|
||||
|
||||
22376
package-lock.json
generated
22376
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
94
package.json
94
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ClearML-webapp",
|
||||
"version": "0.17.0",
|
||||
"version": "1.1.0",
|
||||
"license": "",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "npx ng serve --proxy-config proxy.config.js --live-reload false --port 4300",
|
||||
"hmr": "npx ng serve --proxy-config proxy.config.js --hmr --port 4300",
|
||||
"build": "node --max_old_space_size=3248 ./node_modules/.bin/ng build --prod --source-map --extract-css=false --vendor-chunk --crossOrigin=use-credentials",
|
||||
"build": "node --max_old_space_size=3248 ./node_modules/.bin/ng build --prod --source-map --vendor-chunk --crossOrigin=use-credentials",
|
||||
"build-demo": "node --max_old_space_size=3248 ./node_modules/.bin/ng build --configuration demo --source-map --extract-css=false --crossOrigin=use-credentials",
|
||||
"build-guest": "node --max_old_space_size=3248 ./node_modules/.bin/ng build --prod --configuration guest --source-map --extract-css=false --crossOrigin=use-credentials",
|
||||
"build-community": "node --max_old_space_size=3248 ./node_modules/.bin/ng build --prod --configuration community --source-map --extract-css=false --crossOrigin=use-credentials",
|
||||
@@ -20,33 +20,35 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^11.2.7",
|
||||
"@angular/cdk": "^11.2.6",
|
||||
"@angular/common": "^11.2.7",
|
||||
"@angular/compiler": "^11.2.7",
|
||||
"@angular/core": "^11.2.7",
|
||||
"@angular/forms": "^11.2.7",
|
||||
"@angular/material": "^11.2.6",
|
||||
"@angular/platform-browser": "^11.2.7",
|
||||
"@angular/platform-browser-dynamic": "^11.2.7",
|
||||
"@angular/platform-server": "^11.2.7",
|
||||
"@angular/router": "^11.2.7",
|
||||
"@angular/service-worker": "^11.2.7",
|
||||
"@ngrx/effects": "^11.0.1",
|
||||
"@ngrx/entity": "^11.0.1",
|
||||
"@ngrx/router-store": "^11.0.1",
|
||||
"@ngrx/store": "^11.0.1",
|
||||
"@angular/animations": "^11.2.10",
|
||||
"@angular/cdk": "^11.2.9",
|
||||
"@angular/common": "^11.2.10",
|
||||
"@angular/compiler": "^11.2.10",
|
||||
"@angular/core": "^11.2.10",
|
||||
"@angular/forms": "^11.2.10",
|
||||
"@angular/material": "^11.2.9",
|
||||
"@angular/platform-browser": "^11.2.10",
|
||||
"@angular/platform-browser-dynamic": "^11.2.10",
|
||||
"@angular/platform-server": "^11.2.10",
|
||||
"@angular/router": "^11.2.10",
|
||||
"@angular/service-worker": "^11.2.10",
|
||||
"@ngrx/effects": "^11.1.0",
|
||||
"@ngrx/entity": "^11.1.0",
|
||||
"@ngrx/router-store": "^11.1.0",
|
||||
"@ngrx/store": "^11.1.0",
|
||||
"@types/d3-selection": "^2.0.0",
|
||||
"@types/plotly.js": "^1.54.10",
|
||||
"ace-builds": "^1.4.12",
|
||||
"amazon-s3-uri": "0.1.0",
|
||||
"angular-google-tag-manager": "^1.3.0",
|
||||
"amazon-s3-uri": "0.1.1",
|
||||
"angular-google-tag-manager": "~1.3.2",
|
||||
"angular-resizable-element": "^3.3.5",
|
||||
"angular-split": "^5.0.0",
|
||||
"ansi-to-html": "^0.6.14",
|
||||
"aws-sdk": "^2.874.0",
|
||||
"aws-sdk": "^2.888.0",
|
||||
"bootstrap": "^4.6.0",
|
||||
"britecharts": "^2.17.6",
|
||||
"britecharts": "^2.18.0",
|
||||
"diff": "^5.0.0",
|
||||
"has-ansi": "^4.0.1",
|
||||
"has-ansi": "^5.0.0",
|
||||
"hocon-parser": "^1.0.1",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"localforage": "^1.9.0",
|
||||
@@ -55,43 +57,43 @@
|
||||
"ngx-clipboard": "^14.0.1",
|
||||
"ngx-color-picker": "^11.0.0",
|
||||
"ngx-filesize": "^2.0.16",
|
||||
"ngx-markdown-editor": "^3.3.2",
|
||||
"ngx-markdown-editor": "^3.3.3",
|
||||
"object-hash": "^2.1.1",
|
||||
"primeicons": "^4.1.0",
|
||||
"primeng": "^11.3.1",
|
||||
"primeng": "^11.3.2",
|
||||
"process": "^0.11.10",
|
||||
"rxjs": "^6.6.7",
|
||||
"string-to-color": "^2.2.2",
|
||||
"tslib": "^2.1.0",
|
||||
"tslib": "^2.2.0",
|
||||
"uuid": "^8.3.2",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.1102.6",
|
||||
"@angular-devkit/core": "^11.2.6",
|
||||
"@angular-devkit/schematics": "^11.2.6",
|
||||
"@angular-devkit/schematics-cli": "^0.1102.6",
|
||||
"@angular-eslint/builder": "^2.0.2",
|
||||
"@angular-eslint/eslint-plugin": "^2.0.2",
|
||||
"@angular-eslint/eslint-plugin-template": "^2.0.2",
|
||||
"@angular-eslint/schematics": "2.0.2",
|
||||
"@angular-eslint/template-parser": "^2.0.2",
|
||||
"@angular/cli": "^11.2.6",
|
||||
"@angular/compiler-cli": "^11.2.7",
|
||||
"@angular/language-service": "^11.2.7",
|
||||
"@angular-devkit/build-angular": "~0.1102.9",
|
||||
"@angular-devkit/core": "^11.2.9",
|
||||
"@angular-devkit/schematics": "^11.2.9",
|
||||
"@angular-devkit/schematics-cli": "^0.1102.9",
|
||||
"@angular-eslint/builder": "^4.0.0",
|
||||
"@angular-eslint/eslint-plugin": "^4.0.0",
|
||||
"@angular-eslint/eslint-plugin-template": "^4.0.0",
|
||||
"@angular-eslint/schematics": "4.0.0",
|
||||
"@angular-eslint/template-parser": "^4.0.0",
|
||||
"@angular/cli": "^11.2.9",
|
||||
"@angular/compiler-cli": "^11.2.10",
|
||||
"@angular/language-service": "^11.2.10",
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"@ngrx/schematics": "^11.0.1",
|
||||
"@ngrx/store-devtools": "^11.0.1",
|
||||
"@schematics/schematics": "^0.1102.6",
|
||||
"@ngrx/schematics": "^11.1.0",
|
||||
"@ngrx/store-devtools": "^11.1.0",
|
||||
"@schematics/schematics": "^0.1102.9",
|
||||
"@types/d3-selection": "^2.0.0",
|
||||
"@types/lodash": "^4.14.168",
|
||||
"@types/node": "^14.14.37",
|
||||
"@types/plotly.js": "^1.54.10",
|
||||
"@types/node": "^14.14.41",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.19.0",
|
||||
"@typescript-eslint/parser": "4.19.0",
|
||||
"@typescript-eslint/eslint-plugin": "4.22.0",
|
||||
"@typescript-eslint/parser": "4.22.0",
|
||||
"babel-loader": "^8.2.2",
|
||||
"codelyzer": "^6.0.1",
|
||||
"eslint": "^7.23.0",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jsdoc": "32.3.0",
|
||||
"eslint-plugin-prefer-arrow": "1.2.3",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<sm-side-nav *ngIf="currentUser"></sm-side-nav>
|
||||
<div class="app-container" [class.login-page]="!currentUser"
|
||||
[class.notifier-open]="update?.active || ((showSurvey$ | async) && showSurvey && !demo && !guestUser)">
|
||||
<sm-header *ngIf="currentUser" [isDashboard]="isDashboardContext" [isLogin]="isLoginContext"
|
||||
<sm-header *ngIf="currentUser" [isLogin]="isLoginContext"
|
||||
[isShareMode]="isSharedAndNotOwner$ | async"></sm-header>
|
||||
<router-outlet class="main-router"></router-outlet>
|
||||
</div>
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
import {ApiUsersService} from './business-logic/api-services/users.service';
|
||||
import {selectCurrentUser, selectActiveWorkspace} from './webapp-common/core/reducers/users-reducer';
|
||||
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
|
||||
import {Component, OnDestroy, OnInit, ViewEncapsulation, HostListener, Renderer2, Injector} from '@angular/core';
|
||||
import {ActivatedRoute, NavigationEnd, Router, Params, RouterEvent} from '@angular/router';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {selectLoggedOut} from './webapp-common/core/reducers/view-reducer';
|
||||
import {selectLoggedOut} from '@common/core/reducers/view-reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {get} from 'lodash/fp';
|
||||
import {selectRouterParams, selectRouterUrl} from './webapp-common/core/reducers/router-reducer';
|
||||
import {selectRouterParams, selectRouterUrl} from '@common/core/reducers/router-reducer';
|
||||
import {ApiProjectsService} from './business-logic/api-services/projects.service';
|
||||
import {Project} from './business-logic/model/projects/project';
|
||||
import {GetAllProjects, SetSelectedProjectId, UpdateProject} from './webapp-common/core/actions/projects.actions';
|
||||
import {selectSelectedProject} from './webapp-common/core/reducers/projects.reducer';
|
||||
import {GetAllSystemProjects, SetSelectedProjectId, UpdateProject} from '@common/core/actions/projects.actions';
|
||||
import {selectSelectedProject} from '@common/core/reducers/projects.reducer';
|
||||
import {
|
||||
selectS3BucketCredentialsBucketCredentials,
|
||||
selectS3PopUpDetails,
|
||||
selectShowLocalFilesPopUp,
|
||||
selectShowS3PopUp
|
||||
} from './webapp-common/core/reducers/common-auth-reducer';
|
||||
} from '@common/core/reducers/common-auth-reducer';
|
||||
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
import {S3AccessResolverComponent} from './webapp-common/layout/s3-access-resolver/s3-access-resolver.component';
|
||||
import {cancelS3Credentials, getTutorialBucketCredentials} from './webapp-common/core/actions/common-auth.actions';
|
||||
import {termsOfUseAccepted} from './webapp-common/core/actions/users.actions';
|
||||
import {S3AccessResolverComponent} from '@common/layout/s3-access-resolver/s3-access-resolver.component';
|
||||
import {cancelS3Credentials, getTutorialBucketCredentials} from '@common/core/actions/common-auth.actions';
|
||||
import {termsOfUseAccepted} from '@common/core/actions/users.actions';
|
||||
import {debounceTime, distinctUntilChanged, filter, map, tap, withLatestFrom} from 'rxjs/operators';
|
||||
import * as routerActions from './webapp-common/core/actions/router.actions';
|
||||
import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {selectBreadcrumbsStrings} from './webapp-common/layout/layout.reducer';
|
||||
import {selectBreadcrumbsStrings} from '@common/layout/layout.reducer';
|
||||
import {prepareNames} from './layout/breadcrumbs/breadcrumbs.utils';
|
||||
import {formatStaticCrumb} from './webapp-common/layout/breadcrumbs/breadcrumbs-common.utils';
|
||||
import {ServerUpdatesService} from './webapp-common/shared/services/server-updates.service';
|
||||
import {formatStaticCrumb} from '@common/layout/breadcrumbs/breadcrumbs-common.utils';
|
||||
import {ServerUpdatesService} from '@common/shared/services/server-updates.service';
|
||||
import {selectAvailableUpdates, selectShowSurvey} from './core/reducers/view-reducer';
|
||||
import {UPDATE_SERVER_PATH} from './app.constants';
|
||||
import {firstLogin, plotlyReady, setScaleFactor, VisibilityChanged} from './webapp-common/core/actions/layout.actions';
|
||||
import {UiUpdatesService} from './webapp-common/shared/services/ui-updates.service';
|
||||
import {firstLogin, plotlyReady, setScaleFactor, visibilityChanged} from '@common/core/actions/layout.actions';
|
||||
import {UiUpdatesService} from '@common/shared/services/ui-updates.service';
|
||||
import {UsageStatsService} from './core/Services/usage-stats.service';
|
||||
import {dismissSurvey} from './core/Actions/layout.actions';
|
||||
import {getScaleFactor} from './webapp-common/shared/utils/shared-utils';
|
||||
import {dismissSurvey} from './core/actions/layout.actions';
|
||||
import {getScaleFactor} from '@common/shared/utils/shared-utils';
|
||||
import {User} from './business-logic/model/users/user';
|
||||
import {ConfigurationService} from './webapp-common/shared/services/configuration.service';
|
||||
import {ConfigurationService} from '@common/shared/services/configuration.service';
|
||||
import {GoogleTagManagerService} from 'angular-google-tag-manager';
|
||||
import {selectIsSharedAndNotOwner} from './features/experiments/reducers';
|
||||
import {TipsService} from './webapp-common/shared/services/tips.service';
|
||||
import {TipsService} from '@common/shared/services/tips.service';
|
||||
import {USER_PREFERENCES_KEY} from '@common/user-preferences';
|
||||
|
||||
@Component({
|
||||
@@ -50,7 +50,6 @@ import {USER_PREFERENCES_KEY} from '@common/user-preferences';
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
public loggedOut$: Observable<any>;
|
||||
public isDashboardContext: boolean;
|
||||
public activeFeature: string;
|
||||
private urlSubscription: Subscription;
|
||||
public selectedProject$: Observable<Project>;
|
||||
@@ -78,7 +77,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private plotlyURL: string;
|
||||
|
||||
@HostListener('document:visibilitychange') onVisibilityChange() {
|
||||
this.store.dispatch(new VisibilityChanged(!document.hidden));
|
||||
this.store.dispatch(visibilityChanged({visible: !document.hidden}));
|
||||
}
|
||||
|
||||
@HostListener('window:beforeunload', ['$event'])
|
||||
@@ -163,7 +162,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
distinctUntilChanged((prev, next) => prev?.id === next?.id)
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.store.dispatch(new GetAllProjects());
|
||||
this.store.dispatch(new GetAllSystemProjects());
|
||||
this.store.dispatch(getTutorialBucketCredentials());
|
||||
this.store.dispatch(termsOfUseAccepted());
|
||||
this.uiUpdatesService.checkForUiUpdate();
|
||||
@@ -192,13 +191,9 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
|
||||
this.store.select(selectActiveWorkspace).pipe(filter(ws => !!ws))
|
||||
.subscribe(workspace => this.activeWorkspace = workspace?.id);
|
||||
|
||||
this.urlSubscription = combineLatest([this.store.select(selectRouterUrl), this.store.select(selectRouterParams)])
|
||||
.subscribe(([url, params]) => {
|
||||
this.projectId = get('projectId', params);
|
||||
this.isDashboardContext = url && url.includes('dashboard');
|
||||
this.isLoginContext = url && url.includes('login');
|
||||
this.isWorkersContext = url && url.includes('workers-and-queues');
|
||||
if (this.projectId) {
|
||||
@@ -319,4 +314,3 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
setTimeout(init);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,14 +53,6 @@ export const TASK_TYPES = {
|
||||
TESTING : 'testing',
|
||||
};
|
||||
|
||||
|
||||
const formPrefix = 'FORM_';
|
||||
export const FORMS_ACTIONS = {
|
||||
SET_FORM_DATA : formPrefix + 'SET_FORM_DATA',
|
||||
SET_FORM_STATUS : formPrefix + 'SET_FORM_STATUS',
|
||||
SET_FORM_SUBMITTED: formPrefix + 'SET_FORM_SUBMITTED'
|
||||
};
|
||||
|
||||
const recentTasksPrefix = 'RECENT_TASKS';
|
||||
|
||||
export const RECENT_TASKS_ACTIONS = {
|
||||
@@ -69,28 +61,6 @@ export const RECENT_TASKS_ACTIONS = {
|
||||
};
|
||||
|
||||
export const VIEW_PREFIX = 'VIEW_';
|
||||
export const VIEW_ACTIONS = {
|
||||
SET_SERVER_UPDATES_AVAILABLE: VIEW_PREFIX + 'SET_SERVER_UPDATES_AVAILABLE',
|
||||
SET_SERVER_ERROR : VIEW_PREFIX + 'SET_SERVER_ERROR',
|
||||
SET_RESULT_MESSAGE : VIEW_PREFIX + 'SET_RESULT_MESSAGE',
|
||||
DEACTIVE_LOADER : VIEW_PREFIX + 'DEACTIVE_LOADER',
|
||||
ACTIVE_LOADER : VIEW_PREFIX + 'ACTIVE_LOADER',
|
||||
VISIBILITY_CHANGED : VIEW_PREFIX + 'VISIBILITY_CHANGED',
|
||||
RESET_LOADER : VIEW_PREFIX + 'RESET_LOADER',
|
||||
SET_BACKDROP : VIEW_PREFIX + 'SET_BACKDROP',
|
||||
OPEN_DIALOG : VIEW_PREFIX + 'OPEN_DIALOG',
|
||||
CLOSE_DIALOG : VIEW_PREFIX + 'CLOSE_DIALOG',
|
||||
ADD_MESSAGE : VIEW_PREFIX + 'ADD_MESSAGE',
|
||||
REMOVE_MESSAGE : VIEW_PREFIX + 'REMOVE_MESSAGE',
|
||||
SET_SERVER_ERROR_STATE : VIEW_PREFIX + 'SET_SERVER_ERROR_STATE',
|
||||
SET_MORE_INFO : VIEW_PREFIX + 'SET_MORE_INFO',
|
||||
SET_AUTO_REFRESH : VIEW_PREFIX + 'SET_AUTO_REFRESH',
|
||||
SET_NOTIFICATION_DIALOG : VIEW_PREFIX + 'SET_NOTIFICATION_DIALOG',
|
||||
SET_COMPARE_AUTO_REFRESH : VIEW_PREFIX + 'SET_COMPARE_AUTO_REFRESH'
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export type MediaContentTypeEnum = 'image/bmp' | 'image/jpeg' | 'image/png' | 'video/mp4' | 'image/jpeg';
|
||||
|
||||
@@ -105,56 +75,6 @@ export const MESSAGES_SEVERITY = {
|
||||
WARN : 'warn' as MessageSeverityEnum
|
||||
};
|
||||
|
||||
const TASKS_PREFIX = 'TASKS_';
|
||||
|
||||
export const TASKS_ACTIONS = {
|
||||
SET_TASK_IN_TABLE : TASKS_PREFIX + 'SET_TASK_IN_TABLE',
|
||||
GET_TASK_BY_ID_AFTER_EFFECT: TASKS_PREFIX + 'GET_TASK_BY_ID_AFTER_EFFECT',
|
||||
DEQUEUE_TASK : TASKS_PREFIX + 'DEQUEUE_TASK',
|
||||
SET_TASK_FOR_METRICS : TASKS_PREFIX + 'SET_TASK_FOR_METRICS',
|
||||
GET_TASK_FOR_METRICS : TASKS_PREFIX + 'GET_TASK_FOR_METRICS',
|
||||
CLOSE_TASK : TASKS_PREFIX + 'CLOSE_TASK',
|
||||
FAIL_TASK : TASKS_PREFIX + 'FAIL_TASK',
|
||||
RESUM_TASK : TASKS_PREFIX + 'RESUM_TASK',
|
||||
PUBLISH_TASK : TASKS_PREFIX + 'PUBLISH_TASK',
|
||||
RESET_TASK : TASKS_PREFIX + 'RESET_TASK',
|
||||
GET_TASK_TOKEN : TASKS_PREFIX + ' GET_TASK_TOKEN',
|
||||
ADD_SELECTED_TASK : TASKS_PREFIX + 'ADD_SELECTED_TASK',
|
||||
AFTER_TASK_CHANGED : TASKS_PREFIX + 'AFTER_TASK_CHANGED',
|
||||
CLOSE_TASK_LOG : TASKS_PREFIX + 'CLOSE_TASK_LOG',
|
||||
CLEAR_TOKEN : TASKS_PREFIX + 'CLEAR_TOKEN',
|
||||
GET_ALL : TASKS_PREFIX + 'GET_ALL',
|
||||
GET_TASK_SUCCESS : TASKS_PREFIX + 'GET_TASK_SUCCESS',
|
||||
GET_PROTOTEXT_SUCCESS : TASKS_PREFIX + 'GET_PROTOTEXT_SUCCESS',
|
||||
GET_TASK_LOG_SUCCESS : TASKS_PREFIX + 'GET_TASK_LOG_SUCCESS',
|
||||
GLOBAL_FILTER_CHANGED : TASKS_PREFIX + 'GLOBAL_FILTER_CHANGED',
|
||||
VIEW_MODE_CHANGED : TASKS_PREFIX + 'VIEW_MODE_CHANGED',
|
||||
TASK_CREATED : TASKS_PREFIX + 'TASK_CREATED',
|
||||
TASK_CHECKED : TASKS_PREFIX + 'TASK_CHECKED',
|
||||
TASK_UNCHECKED : TASKS_PREFIX + 'TASK_UNCHECKED',
|
||||
TABLE_SORT_CHANGED : TASKS_PREFIX + 'TABLE_SORT_CHANGED',
|
||||
TABLE_FILTER_CHANGED : TASKS_PREFIX + 'TABLE_FILTER_CHANGED',
|
||||
TOGGLE_HIDDEN : TASKS_PREFIX + 'TOGGLE_HIDDEN',
|
||||
TASK_DELETED_SUCCESS : TASKS_PREFIX + 'TASK_DELETED_SUCCESS',
|
||||
TASK_ENQUEUE : TASKS_PREFIX + 'TASK_ENQUEUE',
|
||||
TASKS_SUCCESS : TASKS_PREFIX + 'GET_ALL_SUCCESS',
|
||||
TASKS_OPTIMISTIC : TASKS_PREFIX + 'OPTIMISTIC',
|
||||
TASKS_TRAINING_SUCCESS : TASKS_PREFIX + 'TRAINING_SUCCESS',
|
||||
TASKS_IMPORT_SUCCESS : TASKS_PREFIX + 'IMPORT_SUCCESS',
|
||||
TASKS_TESTING_SUCCESS : TASKS_PREFIX + 'TESTING_SUCCESS',
|
||||
RESET_SELECTED_TASK : TASKS_PREFIX + 'RESET_SELECTED_TASK',
|
||||
REMOVE_SELECTED_TASK : TASKS_PREFIX + 'REMOVE_SELECTED_TASK',
|
||||
RESET_SUCCESS : TASKS_PREFIX + 'RESET_SUCCESS',
|
||||
SET_FIRST : TASKS_PREFIX + 'SET_FIRST',
|
||||
SET_GLOBAL_FILTER : TASKS_PREFIX + 'SET_GLOBAL_FILTER',
|
||||
SET_SELECTED_TASK : TASKS_PREFIX + 'SET_SELECTED_TASK',
|
||||
SET_SHOW_CHECKED_TASKS : TASKS_PREFIX + 'SET_SHOW_CHECKED_TASKS',
|
||||
SET_TASKS_DATA : TASKS_PREFIX + 'SET_TASKS_DATA',
|
||||
SET_TABLE_COLUMNS : TASKS_PREFIX + 'SET_TABLE_COLUMNS',
|
||||
STOP_CHECKED_TASKS : TASKS_PREFIX + 'STOP_CHECKED_TASKS',
|
||||
STOP_TASK : TASKS_PREFIX + 'STOP_TASK',
|
||||
UPDATE_TASK : TASKS_PREFIX + 'UPDATE_TASK',
|
||||
};
|
||||
export const USERS_PREFIX = 'USERS_';
|
||||
export const USERS_ACTIONS = {
|
||||
FETCH_CURRENT_USER: USERS_PREFIX + 'FETCH_USER',
|
||||
@@ -207,93 +127,12 @@ if (environment.fileBaseUrl) {
|
||||
|
||||
apiBaseUrl += ENVIRONMENT.API_VERSION;
|
||||
|
||||
const HTTP_PREFIX = 'HTTP_';
|
||||
export const HTTP_ACTIONS = {
|
||||
REQUEST_FAILED: HTTP_PREFIX + 'REQUEST_FAILED'
|
||||
};
|
||||
export const HTTP_PREFIX = 'HTTP_';
|
||||
|
||||
export const HTTP = {
|
||||
API_REQUEST : 'HTTP_API_REQUEST',
|
||||
API_REQUEST_SUCCESS : 'API_REQUEST_SUCCESS',
|
||||
API_REQUEST_CANCELED : 'API_REQUEST_CANCELED',
|
||||
API_BASE_URL : apiBaseUrl, // <-- DIRECT CALL DOESN'T WORK
|
||||
API_BASE_URL_NO_VERSION: apiBaseUrlNoVersion,
|
||||
FILE_BASE_URL: fileBaseUrl,
|
||||
|
||||
API_METHODS: {
|
||||
GET : 'GET',
|
||||
POST: 'POST'
|
||||
},
|
||||
|
||||
AUTH: {
|
||||
CREATE : 'auth.create_credentials',
|
||||
GET_ALL : 'auth.get_credentials',
|
||||
REVOKE : 'auth.revoke_credentials',
|
||||
GET_TASK_TOKEN: 'auth.get_task_token'
|
||||
},
|
||||
|
||||
MODELS: {
|
||||
GET_ALL : 'models.get_all',
|
||||
GET_BY_ID : 'models.get_by_id',
|
||||
CREATE_MODEL: 'models.create',
|
||||
UPDATE : 'models.update'
|
||||
},
|
||||
|
||||
USERS: {
|
||||
GET_CURRENT_USER: 'users.get_current_user',
|
||||
GET_BY_ID : 'users.get_by_id',
|
||||
LOGOUT : '/logout'
|
||||
},
|
||||
|
||||
QUEUES: {
|
||||
GET_ALL: 'queues.get_all'
|
||||
},
|
||||
|
||||
PROJECTS: {
|
||||
GET_ALL: 'projects.get_all',
|
||||
CREATE : 'projects.create',
|
||||
DELETE : 'projects.delete'
|
||||
},
|
||||
|
||||
TASKS: {
|
||||
CLOSE : 'tasks.close',
|
||||
PUBLISH : 'tasks.publish',
|
||||
GET_ALL : 'tasks.get_all',
|
||||
GET_BY_ID : 'tasks.get_by_id',
|
||||
ENQUEUE : 'tasks.enqueue',
|
||||
DEQUEUE : 'tasks.dequeue',
|
||||
CREATE : 'tasks.create',
|
||||
UPDATE : 'tasks.update',
|
||||
EDIT : 'tasks.edit',
|
||||
RESET : 'tasks.reset',
|
||||
START : 'tasks.started',
|
||||
STOP : 'tasks.stop',
|
||||
STOPPED : 'tasks.stopped',
|
||||
COMPLETED : 'tasks.completed',
|
||||
TASK_LOG : 'events.get_task_log',
|
||||
RESUME : 'tasks.resume',
|
||||
FAILED : 'tasks.failed',
|
||||
DEBUG_IMAGES : 'events.debug_images',
|
||||
VECTOR_METRICS : 'events.get_vector_metrics_and_variants',
|
||||
VECTOR_METRICS_HISTOGRAM: 'events.vector_metrics_iter_histogram',
|
||||
SCALAR_METRICS : 'events.scalar_metrics_iter_histogram',
|
||||
PLOT : 'events.get_task_plots',
|
||||
DELETE : 'tasks.delete'
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
FRAMES: {
|
||||
GET_BATCH : 'frames.get_next',
|
||||
GET_BATCH_FOR_TASK: 'frames.get_next_for_task',
|
||||
SET_ROIS : 'frames.set_rois',
|
||||
COMMIT : 'frames.commit',
|
||||
},
|
||||
|
||||
SOURCES: {
|
||||
GET_ALL: 'storage.get_all',
|
||||
}
|
||||
};
|
||||
|
||||
export class EmptyAction implements Action {
|
||||
|
||||
@@ -6,32 +6,29 @@ import {BusinessLogicModule} from './business-logic/business-logic.module';
|
||||
import {AppComponent} from './app.component';
|
||||
import {routes} from './app.routes';
|
||||
import {SMCoreModule} from './core/core.module';
|
||||
import {SMSharedModule} from './webapp-common/shared/shared.module';
|
||||
import {SMSharedModule} from '@common/shared/shared.module';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {CommonLayoutModule} from './webapp-common/layout/layout.module';
|
||||
import {CommonLayoutModule} from '@common/layout/layout.module';
|
||||
import {HTTP_INTERCEPTORS} from '@angular/common/http';
|
||||
import {WebappIntercptor} from './webapp-common/core/interceptors/webapp-interceptor';
|
||||
import {CustomReuseStrategy} from './webapp-common/core/router-reuse-strategy';
|
||||
import {WebappInterceptor} from '@common/core/interceptors/webapp-interceptor';
|
||||
import {CustomReuseStrategy} from '@common/core/router-reuse-strategy';
|
||||
import {ApiUsersService} from './business-logic/api-services/users.service';
|
||||
import {loadUserAndPreferences} from './webapp-common/user-preferences';
|
||||
import {AdminModule} from './webapp-common/admin/admin.module';
|
||||
import {loadUserAndPreferences} from '@common/user-preferences';
|
||||
import {AdminModule} from '@common/admin/admin.module';
|
||||
import {AngularSplitModule} from 'angular-split';
|
||||
import {NotifierModule} from './webapp-common/angular-notifier';
|
||||
import {NotifierModule} from '@common/angular-notifier';
|
||||
import {LayoutModule} from './layout/layout.module';
|
||||
import {ColorHashService} from './webapp-common/shared/services/color-hash/color-hash.service';
|
||||
import {LoginService} from './webapp-common/shared/services/login.service';
|
||||
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
|
||||
import {LoginService} from '@common/shared/services/login.service';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {SharedModule} from './shared/shared.module';
|
||||
import {ErrorService} from './webapp-common/shared/services/error.service';
|
||||
import {ConfigurationService} from './webapp-common/shared/services/configuration.service';
|
||||
import {ProjectsSharedModule} from "./features/projects/shared/projects-shared.module";
|
||||
import { UserManagementComponent } from './webapp-common/user-management/user-management.component';
|
||||
import { UserManagementDialogComponent } from './webapp-common/user-management/user-management-dialog/user-management-dialog.component';
|
||||
import { UserManagementInvitesComponent } from './webapp-common/user-management/user-managment-invites/user-management-invites.component';
|
||||
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from "@angular/material/form-field";
|
||||
import {ErrorService} from '@common/shared/services/error.service';
|
||||
import {ConfigurationService} from '@common/shared/services/configuration.service';
|
||||
import {ProjectsSharedModule} from './features/projects/shared/projects-shared.module';
|
||||
import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from '@angular/material/form-field';
|
||||
|
||||
@NgModule({
|
||||
declarations : [AppComponent, UserManagementComponent, UserManagementDialogComponent, UserManagementInvitesComponent],
|
||||
declarations : [AppComponent],
|
||||
imports: [
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@@ -72,7 +69,7 @@ import {MAT_FORM_FIELD_DEFAULT_OPTIONS} from "@angular/material/form-field";
|
||||
deps : [ApiUsersService, LoginService, Store, ErrorService, ConfigurationService]
|
||||
},
|
||||
ColorHashService,
|
||||
{provide: HTTP_INTERCEPTORS, useClass: WebappIntercptor, multi: true},
|
||||
{provide: HTTP_INTERCEPTORS, useClass: WebappInterceptor, multi: true},
|
||||
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy},
|
||||
{
|
||||
provide: 'googleTagManagerId',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Routes} from '@angular/router';
|
||||
import {AdminComponent} from './webapp-common/admin/admin.component';
|
||||
import {AccountAdministrationGuard} from "./webapp-common/shared/guards/account-administration.guard";
|
||||
import {UserManagementComponent} from "./webapp-common/user-management/user-management.component";
|
||||
import {ProjectRedirectGuardGuard} from './webapp-common/shared/guards/project-redirect.guard';
|
||||
|
||||
|
||||
@@ -22,7 +21,6 @@ export const routes: Routes = [
|
||||
{path: 'signup', loadChildren: () => import('./webapp-common/login/login.module').then(m => m.LoginModule)},
|
||||
|
||||
{path: 'profile', component: AdminComponent, data: {workspaceNeutral: true}},
|
||||
{path: 'account-administration', component: UserManagementComponent, data: {workspaceNeutral: false, }, canActivate: [AccountAdministrationGuard]},
|
||||
|
||||
{
|
||||
path: 'projects',
|
||||
|
||||
@@ -51,6 +51,8 @@ import {ProjectsGetHyperparamValuesRequest} from '../model/projects/projectsGetH
|
||||
import {ProjectsGetHyperparamValuesResponse} from '../model/projects/projectsGetHyperparamValuesResponse';
|
||||
import {ProjectsMoveRequest} from "../model/projects/projectsMoveRequest";
|
||||
import {ProjectsMoveResponse} from "../model/projects/projectsMoveResponse";
|
||||
import { ProjectsValidateDeleteRequest } from '../model/projects/projectsValidateDeleteRequest';
|
||||
import { ProjectsValidateDeleteResponse } from '../model/projects/projectsValidateDeleteResponse';
|
||||
|
||||
|
||||
@Injectable()
|
||||
@@ -633,7 +635,7 @@ export class ApiProjectsService {
|
||||
* @param request request body
|
||||
|
||||
*/
|
||||
public projectsGetHyperparamValuesWithHttpInfo(request: ProjectsGetHyperparamValuesRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
|
||||
public projectsGetHyperparamValues(request: ProjectsGetHyperparamValuesRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
|
||||
if (request === null || request === undefined) {
|
||||
throw new Error('Required parameter request was null or undefined when calling projectsUpdate.');
|
||||
}
|
||||
@@ -670,4 +672,50 @@ export class ApiProjectsService {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Validates that the project existis and can be deleted
|
||||
* @param request request body
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public projectsValidateDelete(request: ProjectsValidateDeleteRequest, options?: any, observe: any = 'body', reportProgress: boolean = false ): Observable<any> {
|
||||
if (request === null || request === undefined) {
|
||||
throw new Error('Required parameter request was null or undefined when calling projectsValidateDelete.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
if (options && options.async_enable) {
|
||||
headers = headers.set(this.configuration.asyncHeader, '1');
|
||||
}
|
||||
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
const httpHeaderAcceptSelected: string | undefined = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
if (httpHeaderAcceptSelected != undefined) {
|
||||
headers = headers.set("Accept", httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
// to determine the Content-Type header
|
||||
const consumes: string[] = [
|
||||
];
|
||||
const httpContentTypeSelected:string | undefined = this.configuration.selectHeaderContentType(consumes);
|
||||
if (httpContentTypeSelected != undefined) {
|
||||
headers = headers.set("Content-Type", httpContentTypeSelected);
|
||||
}
|
||||
|
||||
return this.apiRequest.post<ProjectsValidateDeleteResponse>(`${this.basePath}/projects.validate_delete`,
|
||||
request,
|
||||
{
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ export interface ProjectsGetAllExRequest {
|
||||
stats_for_state?: ProjectsGetAllExRequest.StatsForStateEnum;
|
||||
|
||||
check_own_contents?: boolean;
|
||||
|
||||
search_hidden?: boolean;
|
||||
}
|
||||
|
||||
export namespace ProjectsGetAllExRequest {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* projects
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface ProjectsValidateDeleteRequest {
|
||||
/**
|
||||
* Project ID
|
||||
*/
|
||||
project: string;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* projects
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* OpenAPI spec version: 2.14
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by the swagger code generator program.
|
||||
* https://github.com/swagger-api/swagger-codegen.git
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export interface ProjectsValidateDeleteResponse {
|
||||
/**
|
||||
* The total number of tasks under the project and all its children
|
||||
*/
|
||||
tasks?: number;
|
||||
/**
|
||||
* The total number of non-archived tasks under the project and all its children
|
||||
*/
|
||||
non_archived_tasks?: number;
|
||||
/**
|
||||
* The total number of models under the project and all its children
|
||||
*/
|
||||
models?: number;
|
||||
/**
|
||||
* The total number of non-archived models under the project and all its children
|
||||
*/
|
||||
non_archived_models?: number;
|
||||
/**
|
||||
* The total number of dataviews under the project and all its children
|
||||
*/
|
||||
dataviews?: number;
|
||||
/**
|
||||
* The total number of non-archived dataviews under the project and all its children
|
||||
*/
|
||||
non_archived_dataviews?: number;
|
||||
}
|
||||
@@ -29,4 +29,5 @@ export interface ServerInfoResponse {
|
||||
* Server UID
|
||||
*/
|
||||
uid?: string;
|
||||
api_version?: string;
|
||||
}
|
||||
|
||||
@@ -132,4 +132,5 @@ export interface Task {
|
||||
* Task configuration params
|
||||
*/
|
||||
configuration?: { [key: string]: ConfigurationItem; };
|
||||
runtime?: { [key: string]: string};
|
||||
}
|
||||
|
||||
@@ -89,4 +89,6 @@ export interface TasksGetAllExRequest {
|
||||
* If set to 'true' and project field is set then tasks from the subprojects are searched too
|
||||
*/
|
||||
include_subprojects?: boolean;
|
||||
|
||||
search_hidden?: boolean;
|
||||
}
|
||||
|
||||
7
src/app/core/Actions/users.action.ts
Normal file
7
src/app/core/Actions/users.action.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import {createAction, props} from '@ngrx/store';
|
||||
import {USERS_PREFIX} from '../../app.constants';
|
||||
import {GetCurrentUserResponseUserObject} from '../../business-logic/model/users/getCurrentUserResponseUserObject';
|
||||
|
||||
export const setCurrentUser = createAction(USERS_PREFIX + 'SET_CURRENT_USER',
|
||||
props<{user: GetCurrentUserResponseUserObject; terms_of_use?: any}>()
|
||||
);
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {filter} from 'rxjs/operators';
|
||||
import {updateUsageStats} from '../Actions/usage-stats.actions';
|
||||
import {updateUsageStats} from '../actions/usage-stats.actions';
|
||||
import {selectPromptUser} from '../reducers/usage-stats.reducer';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ConfirmDialogComponent} from '../../webapp-common/shared/ui-components/overlay/confirm-dialog/confirm-dialog.component';
|
||||
|
||||
@@ -8,7 +8,6 @@ import {sourcesReducer} from './reducers/sources-reducer';
|
||||
import {viewReducer} from './reducers/view-reducer';
|
||||
import {USERS_PREFIX, VIEW_PREFIX} from '../app.constants';
|
||||
import {merge, pick} from 'lodash/fp';
|
||||
import {usersReducer} from '../webapp-common/core/reducers/users-reducer';
|
||||
import {projectsReducer} from '../webapp-common/core/reducers/projects.reducer';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {SmSyncStateSelectorService} from '../webapp-common/core/services/sync-state-selector.service';
|
||||
@@ -27,6 +26,7 @@ import {PROJECTS_PREFIX} from '../webapp-common/core/actions/projects.actions';
|
||||
import {loginReducer} from '../webapp-common/login/login-reducer';
|
||||
import {ConfigurationService} from '../webapp-common/shared/services/configuration.service';
|
||||
import {AUTH_PREFIX} from '../webapp-common/core/actions/common-auth.actions';
|
||||
import {usersReducer} from './reducers/users.reducer';
|
||||
|
||||
export const reducers = {
|
||||
auth: commonAuthReducer,
|
||||
@@ -93,7 +93,7 @@ export function setViewPreferencesReducer(reducer: ActionReducer<any>): ActionRe
|
||||
}
|
||||
|
||||
export function setRootProjectPreferencesReducer(reducer: ActionReducer<any>): ActionReducer<any> {
|
||||
return createLocalStorageReducer('rootProjects', ['rootProjects.tagsColors', 'rootProjects.tagsFilterByProject'] , [PROJECTS_PREFIX])(reducer);
|
||||
return createLocalStorageReducer('rootProjects', ['rootProjects.tagsColors', 'rootProjects.tagsFilterByProject', 'rootProjects.graphVariant'] , [PROJECTS_PREFIX])(reducer);
|
||||
}
|
||||
|
||||
export function getMetaReducers() {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Actions, Effect, ofType, createEffect} from '@ngrx/effects';
|
||||
import {USERS_ACTIONS} from '../../app.constants';
|
||||
import {filter, take, mergeMap, switchMap} from 'rxjs/operators';
|
||||
import {CookiesService} from '../../shared/cookies.service';
|
||||
import {ApiAuthService} from '../../business-logic/api-services/auth.service';
|
||||
import {ApiServerService} from '../../business-logic/api-services/server.service';
|
||||
import {ServerReportStatsOptionResponse} from '../../business-logic/model/server/serverReportStatsOptionResponse';
|
||||
import {setUsageStats, updateUsageStats} from '../Actions/usage-stats.actions';
|
||||
import {FetchCurrentUser} from '../../webapp-common/core/actions/users.actions';
|
||||
import {setUsageStats, updateUsageStats} from '../actions/usage-stats.actions';
|
||||
import {fetchCurrentUser} from '@common/core/actions/users.actions';
|
||||
|
||||
|
||||
@Injectable()
|
||||
@@ -18,7 +17,7 @@ export class UserEffects {
|
||||
|
||||
@Effect()
|
||||
setUser$ = this.actions.pipe(
|
||||
ofType<FetchCurrentUser>(USERS_ACTIONS.SET_CURRENT_USER),
|
||||
ofType(fetchCurrentUser),
|
||||
filter(user => !!user),
|
||||
take(1),
|
||||
mergeMap(() => this.serverService.serverReportStatsOption({})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Action, createReducer, on, createSelector} from '@ngrx/store';
|
||||
import {setUsageStats} from '../Actions/usage-stats.actions';
|
||||
import {setUsageStats} from '../actions/usage-stats.actions';
|
||||
|
||||
|
||||
export const userStatsFeatureKey = 'userStats';
|
||||
|
||||
18
src/app/core/reducers/users.reducer.ts
Normal file
18
src/app/core/reducers/users.reducer.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import {createReducer, createSelector, on} from '@ngrx/store';
|
||||
|
||||
import {initUsers, users, usersReducerFunctions, UsersState} from '../../webapp-common/core/reducers/users-reducer';
|
||||
import {setCurrentUser} from '../actions/users.action';
|
||||
|
||||
export const selectHasDataFeature = createSelector(users, () => false);
|
||||
export const selectHasUserManagement = createSelector(users, () => false);
|
||||
|
||||
export const usersReducer = createReducer<UsersState>(initUsers,
|
||||
...usersReducerFunctions,
|
||||
on(setCurrentUser, (state, action) => ({
|
||||
...state,
|
||||
currentUser: action.user,
|
||||
activeWorkspace: action.user.company,
|
||||
userWorkspaces: [action.user.company],
|
||||
termsOfUse: action.terms_of_use,
|
||||
}))
|
||||
);
|
||||
@@ -1,12 +1,11 @@
|
||||
import {VIEW_ACTIONS} from '../../app.constants';
|
||||
import {createSelector} from '@ngrx/store';
|
||||
import {
|
||||
initViewState as commonInitState,
|
||||
viewReducer as commonViewReducer,
|
||||
ViewState as CommonViewState
|
||||
} from '../../webapp-common/core/reducers/view-reducer';
|
||||
import {dismissSurvey} from '../Actions/layout.actions';
|
||||
import {setServerUpdatesAvailable} from '../../webapp-common/core/actions/layout.actions';
|
||||
import {dismissSurvey} from '../actions/layout.actions';
|
||||
import {setServerUpdatesAvailable} from '@common/core/actions/layout.actions';
|
||||
|
||||
interface ViewState extends CommonViewState {
|
||||
availableUpdates: string;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { MatSlideToggleChange } from '@angular/material/slide-toggle';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {selectAllowed} from '../../../core/reducers/usage-stats.reducer';
|
||||
import {Observable} from 'rxjs';
|
||||
import { updateUsageStats } from '../../../core/Actions/usage-stats.actions';
|
||||
import { updateUsageStats } from '../../../core/actions/usage-stats.actions';
|
||||
import {ConfigurationService} from '../../../webapp-common/shared/services/configuration.service';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {ConfigurationService} from '../../../webapp-common/shared/services/confi
|
||||
styleUrls: ['./usage-stats.component.scss']
|
||||
})
|
||||
export class UsageStatsComponent implements OnInit {
|
||||
public shown = true;
|
||||
public demo = ConfigurationService.globalEnvironment.demo;
|
||||
public allowed$: Observable<boolean>;
|
||||
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<sm-dashboard-search [ngClass]="{'dashboard-search': (activeSearch$ | async)}"></sm-dashboard-search>
|
||||
<div *ngIf="!(activeSearch$ | async)" class="recent">
|
||||
<div class="container h-100">
|
||||
<div *ngIf="!(activeSearch$ | async)" class="container h-100">
|
||||
<div class="recent">
|
||||
<sm-dashboard-projects></sm-dashboard-projects>
|
||||
<sm-dashboard-experiments>
|
||||
<div header-buttons>
|
||||
<button
|
||||
*smCheckPermission="true"
|
||||
class="btn btn-primary d-flex align-items-center"
|
||||
(click)="redirectToWorkers()"
|
||||
><i class="al-icon al-ico-queues al-color light-grey-blue sm mr-2"></i>MANAGE WORKERS AND QUEUES</button>
|
||||
</div>
|
||||
</sm-dashboard-experiments>
|
||||
<sm-dashboard-search [class.dashboard-search]="activeSearch$ | async"></sm-dashboard-search>
|
||||
<div *ngIf="(activeSearch$ | async) !== true" class="dashboard-body">
|
||||
<div class="recent">
|
||||
<sm-dashboard-projects (width)="width = $event"></sm-dashboard-projects>
|
||||
<sm-dashboard-experiments
|
||||
[style.width]="width && width > 900 ? width + 'px' : '100%'"
|
||||
[recentTasks]="recentTasks$| async"
|
||||
>
|
||||
<div header-buttons>
|
||||
<button
|
||||
*smCheckPermission="true"
|
||||
class="btn btn-primary d-flex align-items-center"
|
||||
(click)="redirectToWorkers()"
|
||||
><i class="al-icon al-ico-queues al-color light-grey-blue sm mr-2"></i>MANAGE WORKERS AND QUEUES</button>
|
||||
</div>
|
||||
</div>
|
||||
</sm-dashboard-experiments>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,31 +2,15 @@
|
||||
@import "../../webapp-common/shared/ui-components/styles/variables";
|
||||
|
||||
:host {
|
||||
.projects.row {
|
||||
min-height: 228px;
|
||||
}
|
||||
|
||||
.datasets.row {
|
||||
min-height: 228px;
|
||||
.dashboard-body {
|
||||
height: 100%;
|
||||
padding: 0 24px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.recent {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
@include recent-title();
|
||||
|
||||
.recent-title {
|
||||
color: #5d6787;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.recent-row {
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
@@ -66,7 +50,8 @@
|
||||
|
||||
sm-dashboard-experiments {
|
||||
display: block;
|
||||
height: calc(100% - 344px);
|
||||
margin: 0 auto;
|
||||
height: calc(100% - 356px);
|
||||
}
|
||||
|
||||
.dashboard-search {
|
||||
|
||||
@@ -2,19 +2,19 @@ import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {selectActiveWorkspace, selectShowOnlyUserWork} from '../../webapp-common/core/reducers/users-reducer';
|
||||
import {selectShowOnlyUserWork} from '../../webapp-common/core/reducers/users-reducer';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {addWorkspace} from '../../webapp-common/core/actions/users.actions';
|
||||
import {distinctUntilKeyChanged, filter, skip, take} from 'rxjs/operators';
|
||||
import {filter, skip, take} from 'rxjs/operators';
|
||||
import {LoginService} from '../../webapp-common/shared/services/login.service';
|
||||
import {ResetSelectedProject, setDeep} from '../../webapp-common/core/actions/projects.actions';
|
||||
import {setDeep} from '../../webapp-common/core/actions/projects.actions';
|
||||
import {GetRecentProjects, GetRecentTasks} from '../../webapp-common/dashboard/common-dashboard.actions';
|
||||
import {InitSearch} from '../../webapp-common/common-search/common-search.actions';
|
||||
import {selectActiveSearch} from '../../webapp-common/common-search/common-search.reducer';
|
||||
import {selectFirstLogin} from '../../webapp-common/core/reducers/view-reducer';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {WelcomeMessageComponent} from '../../webapp-common/dashboard/dumb/welcome-message/welcome-message.component';
|
||||
import {firstLogin} from '../../webapp-common/core/actions/layout.actions';
|
||||
import {IRecentTask, selectRecentTasks} from '../../webapp-common/dashboard/common-dashboard.reducer';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -24,10 +24,12 @@ import {firstLogin} from '../../webapp-common/core/actions/layout.actions';
|
||||
})
|
||||
export class DashboardComponent implements OnInit, OnDestroy {
|
||||
public activeSearch$: Observable<boolean>;
|
||||
public recentTasks$: Observable<Array<IRecentTask>>;
|
||||
public workspace: GetCurrentUserResponseUserObjectCompany;
|
||||
public width: number;
|
||||
private workspaceSub: Subscription;
|
||||
private welcomeSub: Subscription;
|
||||
showOnlyUserWorkSub: Subscription;
|
||||
private showOnlyUserWorkSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
@@ -38,22 +40,13 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
) {
|
||||
const inviteId: string = this.activatedRoute.snapshot.queryParams['invite'];
|
||||
this.activeSearch$ = this.store.select(selectActiveSearch);
|
||||
this.recentTasks$ = this.store.select(selectRecentTasks);
|
||||
|
||||
this.showOnlyUserWorkSub = this.store.select(selectShowOnlyUserWork).pipe(skip(1)).subscribe(() => {
|
||||
this.store.dispatch(new GetRecentProjects());
|
||||
this.store.dispatch(new GetRecentTasks());
|
||||
});
|
||||
|
||||
this.workspaceSub = this.store.select(selectActiveWorkspace).pipe(
|
||||
filter(ws => !!ws),
|
||||
distinctUntilKeyChanged('id'),
|
||||
skip(1)
|
||||
).subscribe(() => {
|
||||
this.store.dispatch(new ResetSelectedProject());
|
||||
this.store.dispatch(new InitSearch('Search for all'));
|
||||
this.store.dispatch(new GetRecentProjects());
|
||||
this.store.dispatch(new GetRecentTasks());
|
||||
});
|
||||
if (inviteId) {
|
||||
this.store.dispatch(addWorkspace({inviteId}));
|
||||
this.removeInviteFromURL();
|
||||
|
||||
24
src/app/features/delete-entity/delete-dialog.effects.ts
Normal file
24
src/app/features/delete-entity/delete-dialog.effects.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {Actions} from '@ngrx/effects';
|
||||
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {AdminService} from '../admin/admin.service';
|
||||
import {ApiTasksService} from '../../business-logic/api-services/tasks.service';
|
||||
import {ApiModelsService} from '../../business-logic/api-services/models.service';
|
||||
import {DeleteDialogEffectsBase} from '../../webapp-common/shared/entity-page/entity-delete/base-delete-dialog.effects';
|
||||
import {ConfigurationService} from '../../webapp-common/shared/services/configuration.service';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class DeleteDialogEffects extends DeleteDialogEffectsBase {
|
||||
constructor(actions$: Actions,
|
||||
store: Store<any>,
|
||||
tasksApi: ApiTasksService,
|
||||
modelsApi: ApiModelsService,
|
||||
projectsApi: ApiProjectsService,
|
||||
adminService: AdminService,
|
||||
configService: ConfigurationService) {
|
||||
super(actions$, store, tasksApi, modelsApi, projectsApi, adminService, configService);
|
||||
}
|
||||
// (Nir) don't delete this. Other repositories need to override some base functions.
|
||||
}
|
||||
@@ -1,30 +1,6 @@
|
||||
export const COMPARE_DETAILS_ONLY_FIELDS = [
|
||||
'id',
|
||||
'name',
|
||||
'type',
|
||||
'status',
|
||||
'last_update',
|
||||
'project.name',
|
||||
'models.input.name',
|
||||
'models.output.name',
|
||||
'models.output.model.name',
|
||||
'models.output.model.uri',
|
||||
'models.output.model.framework',
|
||||
'models.output.model.design',
|
||||
'models.input.name',
|
||||
'models.input.model.name',
|
||||
'models.input.model.uri',
|
||||
'models.input.model.framework',
|
||||
'models.input.model.labels',
|
||||
'models.input.model.design',
|
||||
'execution.artifacts',
|
||||
'container.*',
|
||||
'script',
|
||||
'tags',
|
||||
'published',
|
||||
'last_iteration',
|
||||
'configuration'
|
||||
];
|
||||
import {COMPARE_DETAILS_ONLY_FIELDS_BASE} from '../../webapp-common/experiments-compare/experiments-compare.constants';
|
||||
|
||||
export const getCompareDetailsOnlyFields = param => COMPARE_DETAILS_ONLY_FIELDS_BASE;
|
||||
|
||||
export const COMPARE_PARAMS_ONLY_FIELDS = [
|
||||
'id',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export abstract class ExperimentCompareDetailsBase {
|
||||
public buildExperimentTree(experiment, baseExperiment, mergedExperiment) {
|
||||
public buildExperimentTree(experiment, baseExperiment, mergedExperiment, param) {
|
||||
return {
|
||||
artifacts: this.buildSectionTree(experiment, 'artifacts', mergedExperiment),
|
||||
execution: this.buildSectionTree(experiment, 'execution', mergedExperiment),
|
||||
@@ -8,5 +8,6 @@ export abstract class ExperimentCompareDetailsBase {
|
||||
}
|
||||
|
||||
|
||||
abstract buildCompareTree(experiments, hasDataFeature?);
|
||||
abstract buildSectionTree(experiment: any, execution1: string, mergedExperiment: any);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import {isReadOnly} from '../../../../webapp-common/shared/utils/shared-utils';
|
||||
import {selectRouterConfig, selectRouterParams, selectRouterQueryParams} from '../../../../webapp-common/core/reducers/router-reducer';
|
||||
import * as commonInfoActions from '../../../../webapp-common/experiments/actions/common-experiments-info.actions';
|
||||
import {ExperimentDetailsUpdated} from '../../../../webapp-common/experiments/actions/common-experiments-info.actions';
|
||||
import {AddMessage} from '../../../../webapp-common/core/actions/layout.actions';
|
||||
import {IExperimentInfo, ISelectedExperiment} from '../../shared/experiment-info.model';
|
||||
import {addMessage} from '../../../../webapp-common/core/actions/layout.actions';
|
||||
import {IExperimentInfo} from '../../shared/experiment-info.model';
|
||||
import {selectSelectedTableExperiment} from '../../../../webapp-common/experiments/reducers';
|
||||
import {ITableExperiment} from '../../../../webapp-common/experiments/shared/common-experiment-model.model';
|
||||
|
||||
@@ -99,7 +99,7 @@ export class ExperimentInfoComponent implements OnInit, OnDestroy {
|
||||
if (name.trim().length > 2) {
|
||||
this.store.dispatch(new ExperimentDetailsUpdated({id: this.selectedExperiment.id, changes: {name: name}}));
|
||||
} else {
|
||||
this.store.dispatch(new AddMessage(MESSAGES_SEVERITY.ERROR, 'Name must be more than three letters long'));
|
||||
this.store.dispatch(addMessage(MESSAGES_SEVERITY.ERROR, 'Name must be more than three letters long'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
>
|
||||
</sm-experiment-info-header>
|
||||
<nav [class.minimized]="minimized">
|
||||
<nav [class.minimized]="minimized" [smOverflows]="'nav'" (onOverflows)="overflow = $event">
|
||||
<ng-container *ngIf="!minimized">
|
||||
<span [routerLink]="['execution']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="execution" [active]="routerConfig.includes('execution')"></sm-navbar-item>
|
||||
@@ -28,18 +28,32 @@
|
||||
<sm-navbar-item header="info" [active]="routerConfig.includes('general')"></sm-navbar-item>
|
||||
</span>
|
||||
</ng-container>
|
||||
<span [routerLink]="['log']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="console" [active]="routerConfig.includes('log')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','scalar']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="Scalars" [active]="routerConfig.includes('metrics') && routerConfig.includes('scalar')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','plots']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="PLOTS" [active]="routerConfig.includes('metrics') && routerConfig.includes('plots')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['debugImages']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item header="DEBUG SAMPLES" [active]="routerConfig.includes('debugImages')"></sm-navbar-item>
|
||||
|
||||
<span [matMenuTriggerFor]="results" *ngIf="!minimized && overflow">
|
||||
<sm-navbar-item header="results" [multi]="true" [active]="console.active || scalar.active || plots.active || samples.active"></sm-navbar-item>
|
||||
</span>
|
||||
|
||||
<mat-menu #results="matMenu">
|
||||
<button mat-menu-item [routerLink]="['log']" [class.active]="routerConfig.includes('log')">CONSOLE</button>
|
||||
<button mat-menu-item [routerLink]="['metrics','scalar']" [class.active]="routerConfig.includes('metrics') && routerConfig.includes('scalar')">SCALARS</button>
|
||||
<button mat-menu-item [routerLink]="['metrics','plots']" [class.active]="routerConfig.includes('metrics') && routerConfig.includes('plots')">PLOTS</button>
|
||||
<button mat-menu-item [routerLink]="['debugImages']" [class.active]="routerConfig.includes('debugImages')">DEBUG SAMPLES</button>
|
||||
</mat-menu>
|
||||
|
||||
<div class="d-inline-block" [style.visibility]="overflow && !minimized ? 'hidden' : 'visible'">
|
||||
<span [routerLink]="['log']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #console header="console" [active]="routerConfig.includes('log')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','scalar']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #scalar header="Scalars" [active]="routerConfig.includes('metrics') && routerConfig.includes('scalar')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['metrics','plots']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #plots header="PLOTS" [active]="routerConfig.includes('metrics') && routerConfig.includes('plots')"></sm-navbar-item>
|
||||
</span>
|
||||
<span [routerLink]="['debugImages']" queryParamsHandling="preserve">
|
||||
<sm-navbar-item #samples header="DEBUG SAMPLES" [active]="routerConfig.includes('debugImages')"></sm-navbar-item>
|
||||
</span>
|
||||
</div>
|
||||
<span class="refresh-position">
|
||||
<sm-experiment-settings
|
||||
[class.maximized]="!minimized"
|
||||
|
||||
@@ -7,4 +7,5 @@ import {BaseExperimentOutputComponent} from '../../../../webapp-common/experimen
|
||||
styleUrls: ['../../../../webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss']
|
||||
})
|
||||
export class ExperimentOutputComponent extends BaseExperimentOutputComponent {
|
||||
public overflow: boolean;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export {EXPERIMENT_INFO_ONLY_FIELDS} from '@common/experiments/experiment.consts';
|
||||
import {EXPERIMENT_INFO_ONLY_FIELDS_BASE} from '@common/experiments/experiment.consts';
|
||||
export {INITIAL_EXPERIMENT_TABLE_COLS} from '../../webapp-common/experiments/experiment.consts';
|
||||
|
||||
|
||||
export const GET_ALL_QUERY_ANY_FIELDS = ['id', 'name', 'comment', 'system_tags', 'models.output.model', 'models.input.model'];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const getExperimentInfoOnlyFields = (hasDataFeature: boolean) => EXPERIMENT_INFO_ONLY_FIELDS_BASE;
|
||||
|
||||
export const DEFAULT_EXPERIMENT_TAB = 'execution';
|
||||
|
||||
@@ -34,10 +34,10 @@ import {ExperimentOutputComponent} from './containers/experiment-ouptut/experime
|
||||
|
||||
|
||||
const syncedKeys = [
|
||||
'view.tableSortField',
|
||||
'view.tableSortOrder',
|
||||
'view.tableFilters',
|
||||
'view.hiddenTableCols',
|
||||
'view.projectColumnsSortOrder',
|
||||
'view.projectColumnFilter',
|
||||
'view.projectColumnsWidth',
|
||||
'view.hiddenProjectTableCols',
|
||||
'view.metricsCols',
|
||||
'view.colsOrder',
|
||||
'info.userKnowledge',
|
||||
|
||||
@@ -2,14 +2,13 @@ import {ActionReducerMap, createSelector} from '@ngrx/store';
|
||||
import {experimentsViewReducer, IExperimentsViewState} from './experiments-view.reducer';
|
||||
import {experimentInfoReducer, IExperimentInfoState} from './experiment-info.reducer';
|
||||
import {experimentOutputReducer} from './experiment-output.reducer';
|
||||
import {IExperimentInfo, ISelectedExperiment} from '../shared/experiment-info.model';
|
||||
import {IExperimentInfo} from '../shared/experiment-info.model';
|
||||
import {TaskStatusEnum} from '../../../business-logic/model/tasks/taskStatusEnum';
|
||||
import {MetricVariantResult} from '../../../business-logic/model/projects/metricVariantResult';
|
||||
import {isReadOnly, isSharedAndNotOwner} from '../../../webapp-common/shared/utils/shared-utils';
|
||||
import {EXPERIMENTS_STORE_KEY} from '../../../webapp-common/experiments/shared/common-experiments.const';
|
||||
import {CommonExperimentOutputState} from '../../../webapp-common/experiments/reducers/common-experiment-output.reducer';
|
||||
import {selectActiveWorkspace} from '../../../webapp-common/core/reducers/users-reducer';
|
||||
import {selectSelectedModel} from "../../../webapp-common/models/reducers";
|
||||
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
|
||||
|
||||
export const experimentsReducers: ActionReducerMap<any, any> = {
|
||||
view: experimentsViewReducer,
|
||||
@@ -42,12 +41,12 @@ export const selectShowExtraDataSpinner = createSelector(experimentInfo, state =
|
||||
// output selectors
|
||||
export const experimentOutput = createSelector(experiments, (state): CommonExperimentOutputState => state ? state.output : {});
|
||||
|
||||
export const selectIsExperimentEditable = createSelector(selectSelectedExperiment, selectActiveWorkspace,
|
||||
(experiment, user): boolean => experiment && experiment.status === TaskStatusEnum.Created && !isReadOnly(experiment) && !isSharedAndNotOwner(experiment, user));
|
||||
export const selectIsSharedAndNotOwner = createSelector(selectSelectedExperiment, selectSelectedModel, selectActiveWorkspace,
|
||||
export const selectIsExperimentEditable = createSelector(selectSelectedExperiment, selectCurrentUser,
|
||||
(experiment, user): boolean => experiment && experiment.status === TaskStatusEnum.Created && !isReadOnly(experiment) && !isSharedAndNotOwner(experiment, user.company));
|
||||
export const selectIsSharedAndNotOwner = createSelector(selectSelectedExperiment, selectSelectedModel, selectCurrentUser,
|
||||
(experiment, model, user): boolean => {
|
||||
const item = experiment || model;
|
||||
return item && isSharedAndNotOwner(item, user);
|
||||
return item && isSharedAndNotOwner(item, user.company);
|
||||
}
|
||||
);
|
||||
export const selectExperimentInfoDataFreeze = createSelector(experimentInfo, (state): IExperimentInfo => state.infoDataFreeze);
|
||||
|
||||
@@ -1 +1 @@
|
||||
|
||||
export {ProjectRoute, PROJECT_ROUTES} from '@common/projects/common-projects.consts';
|
||||
|
||||
@@ -2,10 +2,8 @@ import {NgModule} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {SMSharedModule} from '../../webapp-common/shared/shared.module';
|
||||
import {StoreModule} from '@ngrx/store';
|
||||
import {ProjectRouterModule} from '../projects/projects-routing.module';
|
||||
import {EffectsModule} from '@ngrx/effects';
|
||||
import {projectsReducer} from '../projects/projects.reducer';
|
||||
import {ProjectsEffects} from './projects.effects';
|
||||
import {ProjectRouterModule} from './projects-routing.module';
|
||||
import {projectsReducer} from './projects.reducer';
|
||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||
import {CommonProjectsModule} from '../../webapp-common/projects/common-projects.module';
|
||||
|
||||
@@ -18,7 +16,6 @@ import {CommonProjectsModule} from '../../webapp-common/projects/common-projects
|
||||
ReactiveFormsModule,
|
||||
CommonProjectsModule,
|
||||
StoreModule.forFeature('projects', projectsReducer),
|
||||
EffectsModule.forFeature([ProjectsEffects]),
|
||||
],
|
||||
declarations : []
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Task} from '../../business-logic/model/tasks/task';
|
||||
import {selectSelectedTableModel} from '../../webapp-common/models/reducers';
|
||||
import {createSelector} from '@ngrx/store';
|
||||
import {selectSelectedExperiment} from '../../features/experiments/reducers';
|
||||
import {selectProjects, selectSelectedProject} from '../../webapp-common/core/reducers/projects.reducer';
|
||||
import {selectRootProjects, selectSelectedProject} from '../../webapp-common/core/reducers/projects.reducer';
|
||||
import {formatStaticCrumb, prepareLinkData} from '../../webapp-common/layout/breadcrumbs/breadcrumbs-common.utils';
|
||||
|
||||
export interface IBreadcrumbs {
|
||||
@@ -17,13 +17,12 @@ export interface IBreadcrumbs {
|
||||
}
|
||||
|
||||
export const selectBreadcrumbsStringsBase = createSelector(
|
||||
selectSelectedProject, selectSelectedExperiment, selectSelectedTableModel,selectProjects,
|
||||
selectSelectedProject, selectSelectedExperiment, selectSelectedTableModel, selectRootProjects,
|
||||
(project, experiment, model,projects) =>
|
||||
({project, experiment, model, projects}) as IBreadcrumbs);
|
||||
|
||||
|
||||
|
||||
export function prepareNames(data: IBreadcrumbs) {
|
||||
export const prepareNames = (data: IBreadcrumbs) => {
|
||||
const project = prepareLinkData(data.project, true);
|
||||
if (data.project) {
|
||||
const subProjects = [];
|
||||
@@ -35,12 +34,12 @@ export function prepareNames(data: IBreadcrumbs) {
|
||||
...data.projects,
|
||||
{id: '*', name: 'All Experiments'},
|
||||
{...data.project}
|
||||
].find(project => currentName === project.name);
|
||||
].find(proj => currentName === proj.name);
|
||||
subProjects.push(foundProject);
|
||||
});
|
||||
const subProjectsLinks = subProjects.map(project => ({
|
||||
name: project?.name.substring(project?.name.lastIndexOf('/') + 1),
|
||||
url: `projects/${project?.id}/projects`
|
||||
const subProjectsLinks = subProjects.map(subProject => ({
|
||||
name: subProject?.name.substring(subProject?.name.lastIndexOf('/') + 1),
|
||||
url: `projects/${subProject?.id}/projects`
|
||||
})) as { name: string; url: string }[];
|
||||
project.name = project.name.substring(project.name.lastIndexOf('/') + 1);
|
||||
project.subCrumbs = subProjectsLinks;
|
||||
@@ -71,9 +70,9 @@ export function prepareNames(data: IBreadcrumbs) {
|
||||
artifacts: formatStaticCrumb('artifacts'),
|
||||
general: formatStaticCrumb('general'),
|
||||
log: formatStaticCrumb('logs'),
|
||||
'scalar': formatStaticCrumb('scalars'),
|
||||
'plots': formatStaticCrumb('plots'),
|
||||
scalar: formatStaticCrumb('scalars'),
|
||||
plots: formatStaticCrumb('plots'),
|
||||
accountAdministration,
|
||||
debugImages: formatStaticCrumb('Debug Samples'),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
import {TaskStatusEnum} from '../../business-logic/model/tasks/taskStatusEnum';
|
||||
import {TaskTypeEnum} from '../../business-logic/model/tasks/taskTypeEnum';
|
||||
|
||||
export interface ProjectRoute {
|
||||
header: 'overview' | 'models' | 'experiments';
|
||||
subHeader: string;
|
||||
}
|
||||
|
||||
export const PROJECT_ROUTES = [
|
||||
{header: 'overview', subHeader: ''},
|
||||
{header: 'experiments', subHeader: '(ARCHIVED)'},
|
||||
{header: 'models', subHeader: '(ARCHIVED)'},
|
||||
] as ProjectRoute[];
|
||||
|
||||
export enum EntityTypeEnum {
|
||||
experiment = 'experiment',
|
||||
model = 'model',
|
||||
|
||||
@@ -14,7 +14,7 @@ export class CheckPermissionDirective implements OnDestroy{
|
||||
private blocked = true;
|
||||
|
||||
@Input() set smCheckPermission(permission: boolean | string) {
|
||||
this.blocked = !permission;
|
||||
this.blocked = permission === false;
|
||||
this.setUpView();
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
cd -
|
||||
@@ -14,7 +14,7 @@
|
||||
<td>{{credential?.last_used_from ? credential?.last_used_from : 'Not available'}}</td>
|
||||
<td>
|
||||
<span class="delete-button" (click)="confirmPopUp(credential)" type="button" smTooltip="Revoke">
|
||||
<i class="fa" [ngClass]="ICONS.REMOVE"></i>
|
||||
<i class="al-icon sm-md" [ngClass]="ICONS.REMOVE"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -37,11 +37,8 @@ tbody {
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $light-grey-blue;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
<div class="body">
|
||||
<div class="title first">User Preferences</div>
|
||||
<div class="section toggles">
|
||||
<sm-usage-stats></sm-usage-stats>
|
||||
<sm-usage-stats #usage></sm-usage-stats>
|
||||
<mat-slide-toggle
|
||||
*ngIf="supportReScaling"
|
||||
[class.first]="!usage.shown"
|
||||
(change)="HidpiChange($event)"
|
||||
[checked]="disableHidpi">
|
||||
Disable HiDPI browser scale override
|
||||
@@ -33,13 +34,15 @@
|
||||
<sm-s3-access [S3BucketCredentials]="S3BucketCredentials | async"
|
||||
(S3BucketCredentialsChanged)="onS3BucketCredentialsChanged($event)"></sm-s3-access>
|
||||
</div>
|
||||
<ng-container *smCheckPermission="'manageAppCredentials'">
|
||||
<ng-container *smCheckPermission="'engineer'">
|
||||
<div *ngIf="communityServer; else credsTitle" class="title">WORKSPACES</div>
|
||||
<ng-template #credsTitle><div class="title">APP CREDENTIALS<i
|
||||
class="fas fa-info-circle info"
|
||||
smTooltip="Credentials providing API access to this workspace"
|
||||
matTooltipPosition="above"
|
||||
></i></div></ng-template>
|
||||
<ng-template #credsTitle>
|
||||
<div class="title">APP CREDENTIALS<i
|
||||
class="fas fa-info-circle info"
|
||||
smTooltip="Credentials providing API access to this workspace"
|
||||
matTooltipPosition="above"
|
||||
></i></div>
|
||||
</ng-template>
|
||||
<div *ngIf="communityServer; else credsSection" class="section mb-4">
|
||||
<mat-expansion-panel
|
||||
*ngFor="let workspace of workspaces; trackBy: trackByWorkspace"
|
||||
@@ -72,9 +75,9 @@
|
||||
(credentialRevoked)="onCredentialRevoked($event, workspace)">
|
||||
</sm-admin-credential-table>
|
||||
<span
|
||||
class="add-button d-flex align-items-center pointer"
|
||||
class="add-button d-flex align-items-center pointer"
|
||||
[class.disabled]="creatingCredentials"
|
||||
(click)="createCredential(workspace)"
|
||||
(click)="createCredential(workspace)"
|
||||
>
|
||||
<i class="al-icon sm al-ico-plus mr-1"></i> Create new credentials
|
||||
</span>
|
||||
@@ -83,15 +86,7 @@
|
||||
<div class="space-title"><i class="al-icon al-ico-users mr-2"></i>Members</div>
|
||||
<div class="d-flex align-items-center flex-wrap">
|
||||
<div class="util-text">Workspace has {{workspace?.allocated}} of {{workspace?.allowed}} users</div>
|
||||
<div class="circles">
|
||||
<div *ngFor="let user of workspace?.users; let index=index; trackBy: trackByUser"
|
||||
class="circle"
|
||||
[class.empty]="!user.name"
|
||||
[class.long]="user.initials?.length > 4"
|
||||
[style.left.px]="-6 * index"
|
||||
[smTooltip]="user.name" matTooltipPosition="above"
|
||||
>{{user.name ? user.initials : ''}}</div>
|
||||
</div>
|
||||
<sm-circles-in-row [data]="workspace?.users"></sm-circles-in-row>
|
||||
</div>
|
||||
<ng-container *ngIf="workspace?.id === currentUser.company.id; else leave">
|
||||
<button class="btn button-outline-dark mt-4"
|
||||
@@ -134,5 +129,12 @@
|
||||
<span class="notice">Please <a href="https://www.clear.ml/contact-us" target="_blank">contact us</a> to remove your account from the service.</span>
|
||||
</ng-template>
|
||||
</ng-container>
|
||||
<span class="version float-right">Version: {{version}}</span>
|
||||
<div class="version float-right">
|
||||
<span>Version: </span>
|
||||
<span>{{version}}</span>
|
||||
<ng-container *ngIf="(serverVersions$ | async) as serverVersions">
|
||||
<span> • {{serverVersions?.server}}</span>
|
||||
<span> • {{serverVersions?.api}}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -69,9 +69,20 @@ $tab-width: 15vw;
|
||||
margin: 6px 0 0 24px;
|
||||
}
|
||||
|
||||
.toggles > * {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
.toggles {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
margin-top: 0;
|
||||
|
||||
& > *:not(:first-child){
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
|
||||
&.first {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mat-expansion-panel, mat-expansion-panel-header {
|
||||
@@ -191,53 +202,23 @@ $tab-width: 15vw;
|
||||
}
|
||||
}
|
||||
|
||||
.circles {
|
||||
sm-circles-in-row {
|
||||
flex: 1;
|
||||
max-width: 290px;
|
||||
display: flex;
|
||||
margin: 6px 0 0 24px;
|
||||
|
||||
.circle {
|
||||
width: 34px;
|
||||
min-width: 34px;
|
||||
height: 34px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
background-color: $light-grey-blue;
|
||||
color: $blue-800;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
border: $blue-800 solid 2px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
||||
&.empty {
|
||||
background-color: $blue-700;
|
||||
border-color: $light-grey-blue;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
&.long {
|
||||
width: unset;
|
||||
min-width: unset;
|
||||
border-radius: 18px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.reload {
|
||||
color: transparent;
|
||||
text-decoration: underline;
|
||||
transition: color 0.5s;
|
||||
position: absolute;
|
||||
right: 50px;
|
||||
transition: color 0.5s ease-in-out, right 0.5s ease-in-out;
|
||||
|
||||
&.highlight {
|
||||
color: $neon-yellow;
|
||||
transition: color 0.5s;
|
||||
right: -100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {AdminService} from '../../features/admin/admin.service';
|
||||
import {selectActiveWorkspace, selectCurrentUser, selectUserWorkspaces} from '../core/reducers/users-reducer';
|
||||
import {selectActiveWorkspace, selectCurrentUser, selectServerVersions, selectUserWorkspaces} from '../core/reducers/users-reducer';
|
||||
import {createCredential, credentialRevoked, getAllCredentials, updateS3Credential} from '../core/actions/common-auth.actions';
|
||||
import {selectCredentials, selectNewCredential, selectS3BucketCredentials} from '../core/reducers/common-auth-reducer';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {CreateCredentialDialogComponent} from './create-credential-dialog/create-credential-dialog.component';
|
||||
import {debounceTime, filter, take, tap} from 'rxjs/operators';
|
||||
import {
|
||||
getApiVersion,
|
||||
getUserWorkspaces,
|
||||
leaveWorkspace,
|
||||
logout,
|
||||
@@ -64,6 +65,7 @@ export class AdminComponent implements OnInit, OnDestroy {
|
||||
creatingCredentials = false;
|
||||
public panelState = {} as { [workspaceId: string]: boolean };
|
||||
private confSub: Subscription;
|
||||
public serverVersions$: Observable<{ server: string; api: string }>;
|
||||
|
||||
constructor(
|
||||
public adminService: AdminService,
|
||||
@@ -95,9 +97,11 @@ export class AdminComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.neverShowTipsAgain$ = store.select(selectNeverShowPopups);
|
||||
this.credentials$ = this.store.select(selectCredentials);
|
||||
this.serverVersions$ = this.store.select(selectServerVersions);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(getApiVersion());
|
||||
this.store.select(selectCurrentUser)
|
||||
.pipe(filter(user => !!user), take(1))
|
||||
.subscribe(() => {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {from, Observable, Subject} from 'rxjs';
|
||||
import {HTTP} from '../../app.constants';
|
||||
import {
|
||||
selectDontShowAgainForBucketEndpoint, selectRevokeSucceed, selectS3BucketCredentials, selectS3BucketCredentialsBucketCredentials
|
||||
} from '../core/reducers/common-auth-reducer';
|
||||
@@ -16,16 +15,17 @@ import {ConfigurationService} from '../shared/services/configuration.service';
|
||||
import {selectActiveWorkspace} from '../core/reducers/users-reducer';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {Environment} from '../../../environments/base';
|
||||
import {apiRequest} from '@common/core/actions/http.actions';
|
||||
|
||||
const LOCAL_SERVER_PORT = 27878;
|
||||
|
||||
@Injectable()
|
||||
export class BaseAdminService {
|
||||
bucketCredentials: Observable<any>;
|
||||
public S3Services = {};
|
||||
public s3Services = {};
|
||||
revokeSucceed: Observable<any>;
|
||||
protected store: Store<any>;
|
||||
private S3BucketCredentials: Observable<any>;
|
||||
private readonly s3BucketCredentials: Observable<any>;
|
||||
private previouslySignedUrls = {};
|
||||
private localServerWorking = false;
|
||||
private workspace: GetCurrentUserResponseUserObjectCompany;
|
||||
@@ -36,8 +36,8 @@ export class BaseAdminService {
|
||||
this.store = store;
|
||||
this.revokeSucceed = store.pipe(select(selectRevokeSucceed));
|
||||
this.revokeSucceed.subscribe(this.onRevokeSucceed);
|
||||
this.S3BucketCredentials = store.pipe(select(selectS3BucketCredentials));
|
||||
this.S3BucketCredentials.pipe(skip(1)).subscribe(bucketEndpoint => {
|
||||
this.s3BucketCredentials = store.pipe(select(selectS3BucketCredentials));
|
||||
this.s3BucketCredentials.pipe(skip(1)).subscribe(() => {
|
||||
this.previouslySignedUrls = {};
|
||||
});
|
||||
this.bucketCredentials = store.pipe(select(selectS3BucketCredentialsBucketCredentials));
|
||||
@@ -47,30 +47,35 @@ export class BaseAdminService {
|
||||
}
|
||||
|
||||
showS3PopUp(bucketKeyEndpoint, error = null, isAzure = false) {
|
||||
delete this.S3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
delete this.s3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
const selectDontShowAgainBucketEndpoint = this.syncSelector.selectSync(selectDontShowAgainForBucketEndpoint);
|
||||
if (selectDontShowAgainBucketEndpoint !== (bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint)) {
|
||||
this.store.dispatch(showS3PopUp({payload: {credentials: bucketKeyEndpoint, credentialsError: error, isAzure}}));
|
||||
}
|
||||
return this.S3BucketCredentials;
|
||||
return this.s3BucketCredentials;
|
||||
}
|
||||
|
||||
showLocalFilePopUp(url) {
|
||||
this.store.dispatch(showLocalFilePopUp({url}));
|
||||
}
|
||||
|
||||
signUrlIfNeeded(url, skipLocalFile = true, skipFileServer = true) {
|
||||
signUrlIfNeeded(url: string, config?: {skipLocalFile?: boolean; skipFileServer?: boolean; disableCache?: number}) {
|
||||
config = {...{skipLocalFile: true, skipFileServer: true, disableCache: null}, ...config};
|
||||
|
||||
if (isFileserverUrl(url, window.location.hostname)) {
|
||||
if (this.environment.communityServer) {
|
||||
url = this.addTenant(url);
|
||||
}
|
||||
if (!skipFileServer && this.environment.useFilesProxy) {
|
||||
if (!config.skipFileServer && this.environment.useFilesProxy) {
|
||||
return convertToReverseProxy(url);
|
||||
}
|
||||
if (config.disableCache) {
|
||||
url = this.addS3TimeStamp(url, config.disableCache);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
if (this.isLocalFile(url) && !skipLocalFile) {
|
||||
if (this.isLocalFile(url) && !config.skipLocalFile) {
|
||||
this.checkLocalServerRunning(url);
|
||||
return this.redirectToLocalServer(url);
|
||||
}
|
||||
@@ -97,11 +102,13 @@ export class BaseAdminService {
|
||||
}
|
||||
const s3 = this.findOrInitBucketS3(bucketKeyEndpoint);
|
||||
if (s3) {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const bucketKey = {
|
||||
Bucket: bucketKeyEndpoint.Bucket,
|
||||
Key: bucketKeyEndpoint.Key,
|
||||
Expires: 60 * 60 * 24 * 4
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
let signedURL = '';
|
||||
try {
|
||||
signedURL = s3.getSignedUrl('getObject', bucketKey);
|
||||
@@ -110,10 +117,13 @@ export class BaseAdminService {
|
||||
}
|
||||
const timeToExperation = (new Date()).setDate(now.getDate() + 3);
|
||||
this.previouslySignedUrls[url] = {
|
||||
signedURL: signedURL,
|
||||
signedURL,
|
||||
expires: timeToExperation,
|
||||
bucketKeyEndpoint: bucketKeyEndpoint
|
||||
bucketKeyEndpoint
|
||||
};
|
||||
if(config.disableCache){
|
||||
return this.addS3TimeStamp(signedURL, config.disableCache);
|
||||
}
|
||||
return signedURL;
|
||||
} else {
|
||||
delete bucketKeyEndpoint.Key;
|
||||
@@ -133,13 +143,13 @@ export class BaseAdminService {
|
||||
|
||||
findOrInitBucketS3(bucketKeyEndpoint) {
|
||||
const set = this.findS3CredentialsInStore(bucketKeyEndpoint);
|
||||
const S3Service = this.S3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
if (set && S3Service) {
|
||||
return S3Service;
|
||||
const s3Service = this.s3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
if (set && s3Service) {
|
||||
return s3Service;
|
||||
} else {
|
||||
if (set) {
|
||||
this.S3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint] = this.createS3Service(set);
|
||||
return this.S3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
this.s3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint] = this.createS3Service(set);
|
||||
return this.s3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -160,31 +170,29 @@ export class BaseAdminService {
|
||||
}
|
||||
|
||||
resetS3Services() {
|
||||
this.S3Services = {};
|
||||
this.s3Services = {};
|
||||
}
|
||||
|
||||
public checkImgUrl(src) {
|
||||
const bucketKeyEndpoint = src && this.getBucketAndKeyFromSrc(src);
|
||||
if (bucketKeyEndpoint) {
|
||||
delete this.S3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
const S3OldCredentials = this.syncSelector.selectSync(selectS3BucketCredentialsBucketCredentials)
|
||||
delete this.s3Services[bucketKeyEndpoint.Bucket + bucketKeyEndpoint.Endpoint];
|
||||
const s3OldCredentials = this.syncSelector.selectSync(selectS3BucketCredentialsBucketCredentials)
|
||||
.filter(bucket => bucket.Bucket === bucketKeyEndpoint.Bucket && bucket.Endpoint === bucketKeyEndpoint.Endpoint);
|
||||
if (S3OldCredentials[0]) {
|
||||
this.showS3PopUp(S3OldCredentials[0], 'Error', this.isAzureUrl(src));
|
||||
if (s3OldCredentials[0]) {
|
||||
this.showS3PopUp(s3OldCredentials[0], 'Error', this.isAzureUrl(src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public getAllCredentials() {
|
||||
this.store.dispatch({
|
||||
type: HTTP.API_REQUEST,
|
||||
meta: {
|
||||
this.store.dispatch(apiRequest({
|
||||
method: 'GET',
|
||||
endpoint: 'auth.get_credentials',
|
||||
success: 'AUTH_GET_SUCCESS'
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -205,39 +213,45 @@ export class BaseAdminService {
|
||||
}
|
||||
|
||||
public getBucketAndKeyFromSrc = (src) => {
|
||||
let Key = '';
|
||||
let key = '';
|
||||
if (src && src.includes('azure://')) {
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
return {
|
||||
Bucket: 'azure',
|
||||
Endpoint: 'azure',
|
||||
Key: 'azure',
|
||||
Secret: Key
|
||||
Secret: key
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
}
|
||||
const src_arr = src.split('/');
|
||||
const srcArr = src.split('/');
|
||||
if (!this.isS3Url(src)) {
|
||||
return null;
|
||||
} else if (src_arr[2].includes(':')) {
|
||||
src_arr.forEach((part, index) => {
|
||||
} else if (srcArr[2].includes(':')) {
|
||||
srcArr.forEach((part, index) => {
|
||||
if (index > 3) {
|
||||
Key += part + '/';
|
||||
key += part + '/';
|
||||
}
|
||||
});
|
||||
Key = Key.slice(0, -1);
|
||||
key = key.slice(0, -1);
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
return {
|
||||
Bucket: src_arr[3],
|
||||
Endpoint: src_arr[2],
|
||||
Key: Key,
|
||||
Bucket: srcArr[3],
|
||||
Endpoint: srcArr[2],
|
||||
Key: key,
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} else {
|
||||
try {
|
||||
src = this.encodeSpecialCharacters(src);
|
||||
const amazon = AmazonS3URI(src);
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
return {
|
||||
Bucket: amazon.bucket,
|
||||
Key: amazon.key,
|
||||
Endpoint: null
|
||||
};
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return null;
|
||||
@@ -268,7 +282,6 @@ export class BaseAdminService {
|
||||
|
||||
signGoogleCloudUrl(url: string): string {
|
||||
return 'https://storage.cloud.google.com' + (url.slice(4));
|
||||
|
||||
}
|
||||
|
||||
signAzureUrl(url: string, azureBucket) {
|
||||
@@ -307,6 +320,7 @@ export class BaseAdminService {
|
||||
const bucketKeyEndpoint = url && this.getBucketAndKeyFromSrc(url);
|
||||
const s3 = this.findOrInitBucketS3(bucketKeyEndpoint) as S3;
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
const req = {
|
||||
Bucket: bucketKeyEndpoint.Bucket,
|
||||
Delete: {
|
||||
@@ -314,13 +328,14 @@ export class BaseAdminService {
|
||||
Objects: files.map(file => ({Key: file} as S3.ObjectIdentifier))
|
||||
}
|
||||
} as S3.Types.DeleteObjectsRequest;
|
||||
/* eslint-enable @typescript-eslint/naming-convention */
|
||||
|
||||
if (s3) {
|
||||
s3.deleteObjects(req, (err, data) => {
|
||||
s3.deleteObjects(req, (err) => {
|
||||
if (err) {
|
||||
this.deleteS3FilesSubject.next({success: false, files: files});
|
||||
this.deleteS3FilesSubject.next({success: false, files});
|
||||
} else {
|
||||
this.deleteS3FilesSubject.next({success: true, files: files});
|
||||
this.deleteS3FilesSubject.next({success: true, files});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -330,4 +345,16 @@ export class BaseAdminService {
|
||||
}
|
||||
return !skipSubjectReturn && this.deleteS3FilesSubject.asObservable();
|
||||
}
|
||||
|
||||
addS3TimeStamp(url: string, timestamp: number) {
|
||||
timestamp = timestamp || new Date().getTime();
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
parsed.searchParams.append('X-Amz-Date', `${timestamp}`);
|
||||
return parsed.toString();
|
||||
} catch {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</div>
|
||||
<div class="col-2 d-flex justify-content-end">
|
||||
<span class="delete-button" (click)="removeBucket(i)" type="button" smTooltip="Remove">
|
||||
<i class="fa" [ngClass]="ICONS.REMOVE"></i>
|
||||
<i class="al-icon sm-md" [ngClass]="ICONS.REMOVE"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,10 +27,8 @@
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: '#{$icomoon-font-family}';
|
||||
src: url('./#{$icomoon-font-family}.ttf?23s406') format('truetype');
|
||||
src: url('./#{$icomoon-font-family}.ttf?ck7fvi') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
@@ -43,9 +43,9 @@
|
||||
content: $al-ico-projects;
|
||||
}
|
||||
}
|
||||
.al-ico-datasets1 {
|
||||
.al-ico-datasets {
|
||||
&:before {
|
||||
content: $al-ico-datasets1;
|
||||
content: $al-ico-datasets;
|
||||
}
|
||||
}
|
||||
.al-ico-queues {
|
||||
@@ -88,11 +88,6 @@
|
||||
content: $al-ico-compare-c;
|
||||
}
|
||||
}
|
||||
.al-ico-datasets {
|
||||
&:before {
|
||||
content: $al-ico-datasets;
|
||||
}
|
||||
}
|
||||
.al-ico-published-title {
|
||||
&:before {
|
||||
content: $al-ico-published-title;
|
||||
@@ -884,6 +879,82 @@
|
||||
content: $al-ico-dequeue;
|
||||
}
|
||||
}
|
||||
.al-ico-applications {
|
||||
&:before {
|
||||
content: $al-ico-applications;
|
||||
}
|
||||
}
|
||||
.al-ico-ico-chevron-up {
|
||||
&:before {
|
||||
content: $al-ico-ico-chevron-up;
|
||||
}
|
||||
}
|
||||
.al-ico-ico-chevron-down {
|
||||
&:before {
|
||||
content: $al-ico-ico-chevron-down;
|
||||
}
|
||||
}
|
||||
.al-ico-no-data-graph {
|
||||
&:before {
|
||||
content: $al-ico-no-data-graph;
|
||||
}
|
||||
}
|
||||
.al-ico-no-scatter-graph {
|
||||
&:before {
|
||||
content: $al-ico-no-scatter-graph;
|
||||
}
|
||||
}
|
||||
.al-ico-applications-exp {
|
||||
&:before {
|
||||
content: $al-ico-applications-exp;
|
||||
}
|
||||
}
|
||||
.al-ico-auto-refresh-play .path1 {
|
||||
&:before {
|
||||
content: $al-ico-auto-refresh-play-path1;
|
||||
color: rgb(90, 101, 142);
|
||||
}
|
||||
}
|
||||
.al-ico-auto-refresh-play .path2 {
|
||||
&:before {
|
||||
content: $al-ico-auto-refresh-play-path2;
|
||||
margin-left: -1em;
|
||||
color: rgb(211, 253, 0);
|
||||
}
|
||||
}
|
||||
.al-ico-auto-refresh-pause .path1 {
|
||||
&:before {
|
||||
content: $al-ico-auto-refresh-pause-path1;
|
||||
color: rgb(90, 101, 142);
|
||||
}
|
||||
}
|
||||
.al-ico-auto-refresh-pause .path2 {
|
||||
&:before {
|
||||
content: $al-ico-auto-refresh-pause-path2;
|
||||
margin-left: -1em;
|
||||
color: rgb(211, 253, 0);
|
||||
}
|
||||
}
|
||||
.al-ico-sqr-ok {
|
||||
&:before {
|
||||
content: $al-ico-sqr-ok;
|
||||
}
|
||||
}
|
||||
.al-ico-sqr-cancel {
|
||||
&:before {
|
||||
content: $al-ico-sqr-cancel;
|
||||
}
|
||||
}
|
||||
.al-ico-queue-lg {
|
||||
&:before {
|
||||
content: $al-ico-queue-lg;
|
||||
}
|
||||
}
|
||||
.al-ico-started-lg {
|
||||
&:before {
|
||||
content: $al-ico-started-lg;
|
||||
}
|
||||
}
|
||||
.al-ico-status-draft {
|
||||
&:before {
|
||||
content: $al-ico-status-draft;
|
||||
|
||||
Binary file not shown.
@@ -5,7 +5,7 @@ $al-ico-t-logo-b: "\e908";
|
||||
$al-ico-bars-menu: "\e933";
|
||||
$al-ico-home: "\e909";
|
||||
$al-ico-projects: "\e90a";
|
||||
$al-ico-datasets1: "\e90b";
|
||||
$al-ico-datasets: "\e90b";
|
||||
$al-ico-queues: "\e90c";
|
||||
$al-ico-annotator: "\e90d";
|
||||
$al-ico-account: "\e998";
|
||||
@@ -14,7 +14,6 @@ $al-ico-archive: "\e92c";
|
||||
$al-ico-logout: "\e9a6";
|
||||
$al-ico-how-to: "\e997";
|
||||
$al-ico-compare-c: "\e93a";
|
||||
$al-ico-datasets: "\e944";
|
||||
$al-ico-published-title: "\e9d2";
|
||||
$al-ico-publish: "\e9cf";
|
||||
$al-ico-unpublish: "\ea0f";
|
||||
@@ -171,6 +170,20 @@ $al-ico-task-desc-outline: "\e970";
|
||||
$al-ico-manage-queue: "\e968";
|
||||
$al-ico-enqueue: "\e969";
|
||||
$al-ico-dequeue: "\e971";
|
||||
$al-ico-applications: "\e972";
|
||||
$al-ico-ico-chevron-up: "\e973";
|
||||
$al-ico-ico-chevron-down: "\e974";
|
||||
$al-ico-no-data-graph: "\e975";
|
||||
$al-ico-no-scatter-graph: "\e976";
|
||||
$al-ico-applications-exp: "\e944";
|
||||
$al-ico-auto-refresh-play-path1: "\e977";
|
||||
$al-ico-auto-refresh-play-path2: "\e978";
|
||||
$al-ico-auto-refresh-pause-path1: "\e979";
|
||||
$al-ico-auto-refresh-pause-path2: "\e97a";
|
||||
$al-ico-sqr-ok: "\e97b";
|
||||
$al-ico-sqr-cancel: "\e97c";
|
||||
$al-ico-queue-lg: "\e97d";
|
||||
$al-ico-started-lg: "\e97e";
|
||||
$al-ico-status-draft: "\e902";
|
||||
$al-ico-status-pending: "\e903";
|
||||
$al-ico-status-running: "\e904";
|
||||
@@ -179,3 +192,4 @@ $al-ico-status-published: "\e906";
|
||||
$al-ico-status-failed: "\e907";
|
||||
$al-ico-status-aborted: "\e917";
|
||||
$al-ico-status-aborted-sec: "\e918";
|
||||
|
||||
|
||||
@@ -36,7 +36,12 @@ $green-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent, $cust
|
||||
$dark-theme: mat-dark-theme($mat-allegro, $mat-allegro, $mat-allegro);
|
||||
$light-theme: mat-light-theme($mat-allegro, $mat-allegro, $mat-allegro);
|
||||
$sm-theme: mat-light-theme($sm-theme-primary, $sm-theme-accent, $sm-theme-warn);
|
||||
|
||||
.cdk-drag-preview.form-group-drag {
|
||||
padding: 8px 16px 32px 16px;
|
||||
border-radius: 4px;
|
||||
border: solid 1px #d4d6e0;
|
||||
background-color: white;
|
||||
}
|
||||
.dark-theme {
|
||||
@include mat-slide-toggle-theme($green-theme);
|
||||
@include mat-radio-theme($green-theme);
|
||||
@@ -66,11 +71,21 @@ $sm-theme: mat-light-theme($sm-theme-primary, $sm-theme-accent, $sm-theme-warn);
|
||||
@include mat-slide-toggle-theme($sm-theme);
|
||||
@include mat-radio-theme($sm-theme);
|
||||
@include mat-select-theme($light-theme);
|
||||
|
||||
.mat-checkbox-frame,
|
||||
.mat-radio-outer-circle {
|
||||
border-color: $purple;
|
||||
}
|
||||
|
||||
.mat-radio-button.mat-accent.disabled, .mat-radio-button.mat-accent.disabled.mat-radio-checked {
|
||||
.mat-radio-outer-circle, .mat-radio-inner-circle {
|
||||
border-color: $blue-300;
|
||||
}
|
||||
.mat-radio-inner-circle{
|
||||
background-color: $blue-300;
|
||||
}
|
||||
}
|
||||
|
||||
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-outer-circle,
|
||||
.light-theme .mat-radio-button.mat-radio-disabled .mat-radio-label-content {
|
||||
border-color: $blue-200 !important;
|
||||
@@ -126,6 +141,11 @@ h5.al-header {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.grab {
|
||||
cursor: grab;
|
||||
}
|
||||
@@ -141,8 +161,10 @@ h5.al-header {
|
||||
background: #ff7272 !important;
|
||||
}
|
||||
}
|
||||
mat-expansion-panel{
|
||||
|
||||
mat-expansion-panel {
|
||||
box-shadow: unset;
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -214,6 +236,12 @@ button {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.grabbing{
|
||||
cursor: grab;
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-middle {
|
||||
display: flex;
|
||||
@@ -401,12 +429,21 @@ html {
|
||||
max-width: none;
|
||||
min-width: 114px;
|
||||
min-height: 32px;
|
||||
|
||||
&.custom-columns {
|
||||
width: 370px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sm-menu-header {
|
||||
text-align: center;
|
||||
padding: 15px 15px;
|
||||
background: $blue-25;
|
||||
color: $blue-400;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mat-menu-item {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
@@ -459,10 +496,10 @@ as-split {
|
||||
}
|
||||
|
||||
$type-colors: (
|
||||
string: #ff8400,
|
||||
number: $neon-yellow-betterinchrome,
|
||||
boolean: #b938a4,
|
||||
date: #05668D,
|
||||
string: #ff8400,
|
||||
number: $neon-yellow-betterinchrome,
|
||||
boolean: #b938a4,
|
||||
date: #05668D,
|
||||
);
|
||||
|
||||
.dark-theme {
|
||||
@@ -547,7 +584,6 @@ body .mat-menu-content:not(:empty) {
|
||||
padding-bottom: 0px;
|
||||
|
||||
.search-results {
|
||||
height: 200px;
|
||||
overflow: auto;
|
||||
max-width: 222px;
|
||||
}
|
||||
@@ -563,6 +599,7 @@ body .mat-menu-content:not(:empty) {
|
||||
body .mat-menu-panel .mat-form-field.tags-menu-input {
|
||||
.mat-form-field-wrapper {
|
||||
padding: 0;
|
||||
|
||||
.mat-form-field-flex {
|
||||
padding: 0 14px;
|
||||
align-items: center;
|
||||
@@ -621,7 +658,7 @@ button.btn.button-outline-dark {
|
||||
.sm-card-list-layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, 352px);
|
||||
grid-gap: 24px;
|
||||
grid-gap: 32px 24px;
|
||||
padding: 0 24px 24px;
|
||||
justify-content: center;
|
||||
|
||||
@@ -648,7 +685,7 @@ button.btn.button-outline-dark {
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
|
||||
&>.menu {
|
||||
& > .menu {
|
||||
color: $blue-300;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -60,9 +60,33 @@ export const ICONS = {
|
||||
DOWNLOAD: 'al-ico-download',
|
||||
WORKER: 'al-ico-workers',
|
||||
TAG: 'al-ico-tag',
|
||||
SHARE: 'al-ico-shared-item'
|
||||
SHARE: 'al-ico-shared-item',
|
||||
ARROW_DOWN: 'al-ico-ico-chevron-down',
|
||||
ARROW_UP: 'al-ico-ico-chevron-up',
|
||||
};
|
||||
|
||||
export type IconNames = keyof typeof ICONS;
|
||||
export type ObjectKeysRecord<T extends object> = { [P in keyof T]: P };
|
||||
export type IconsList = ObjectKeysRecord<typeof ICONS>;
|
||||
export type IconsValues = typeof ICONS[keyof typeof ICONS];
|
||||
|
||||
export const PALLET = {
|
||||
blue25: '#f9fafb',
|
||||
blue50: '#f2f4fc', //242,244,252
|
||||
blue100: '#dce0ee', //220,224,238
|
||||
blue200: '#c3cdf0', //195,205,240
|
||||
blue250: '#A4ADCD',
|
||||
blue280: '#a7b2d8',
|
||||
blue300: '#8492c2', //132,146,19
|
||||
blue400: '#5a658e', //90,101,142
|
||||
blue450: '#657099',
|
||||
blue480: '#707ba3',
|
||||
blue500: '#384161', //56,65,97
|
||||
blue550: '#47527A',
|
||||
blue570: '#323a56',
|
||||
blue600: '#2c3246', //44,50,70
|
||||
blue650: '#24293c',
|
||||
blue700: '#202432', //32,36,50
|
||||
blue800: '#1a1e2c', //26,30,44
|
||||
blue900: '#141722', //20,23,34
|
||||
blue950: '#0d0e15', //20,23,34
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import {HTTP_ACTIONS} from '../../../app.constants';
|
||||
import {ISmAction} from '../models/actions';
|
||||
import {HTTP_PREFIX} from '../../../app.constants';
|
||||
import {omit} from 'lodash/fp';
|
||||
import {HttpErrorResponse} from '@angular/common/http';
|
||||
import {createAction, props} from '@ngrx/store';
|
||||
|
||||
export class RequestFailed implements ISmAction {
|
||||
public type = HTTP_ACTIONS.REQUEST_FAILED;
|
||||
public payload: { err: any };
|
||||
|
||||
constructor(err: HttpErrorResponse) {
|
||||
this.payload = { err: omit(['headers'], err) };
|
||||
}
|
||||
}
|
||||
export const requestFailed = createAction(
|
||||
HTTP_PREFIX + 'REQUEST_FAILED',
|
||||
(err: HttpErrorResponse) => ({err: omit(['headers'], err)})
|
||||
);
|
||||
|
||||
export const apiRequest = createAction(
|
||||
HTTP_PREFIX + 'API Request',
|
||||
props<{method: string; endpoint: string; success: string}>()
|
||||
);
|
||||
|
||||
@@ -1,119 +1,79 @@
|
||||
import {MessageSeverityEnum, VIEW_ACTIONS, VIEW_PREFIX} from '../../../app.constants';
|
||||
import {Action, createAction, props} from '@ngrx/store';
|
||||
import {MessageSeverityEnum, VIEW_PREFIX} from '../../../app.constants';
|
||||
import {createAction, props} from '@ngrx/store';
|
||||
import {omit} from 'lodash/fp';
|
||||
import {HttpErrorResponse} from '@angular/common/http';
|
||||
|
||||
export class SetAutoRefresh {
|
||||
public type = VIEW_ACTIONS.SET_AUTO_REFRESH;
|
||||
public payload: { autoRefresh: boolean };
|
||||
export const setAutoRefresh = createAction(
|
||||
VIEW_PREFIX + '[set auto refresh]',
|
||||
props<{autoRefresh: boolean}>()
|
||||
);
|
||||
|
||||
constructor(autoRefresh: boolean) {
|
||||
this.payload = {autoRefresh};
|
||||
}
|
||||
}
|
||||
export const setCompareAutoRefresh = createAction(
|
||||
VIEW_PREFIX + '[set compare auto refresh]',
|
||||
props<{autoRefresh: boolean}>()
|
||||
);
|
||||
|
||||
export class SetCompareAutoRefresh {
|
||||
public type = VIEW_ACTIONS.SET_COMPARE_AUTO_REFRESH;
|
||||
public payload: { autoRefresh: boolean };
|
||||
export const setServerError = createAction(
|
||||
VIEW_PREFIX + '[set server error]',
|
||||
(serverError: HttpErrorResponse, contextSubCode?: number, customMessage?: string, aggregateSimilar = false) => ({
|
||||
serverError: omit(['headers'], serverError) as Omit<HttpErrorResponse, 'headers'>,
|
||||
contextSubCode,
|
||||
customMessage,
|
||||
aggregateSimilar
|
||||
})
|
||||
);
|
||||
|
||||
constructor(autoRefresh: boolean) {
|
||||
this.payload = {autoRefresh};
|
||||
}
|
||||
}
|
||||
export const setNotificationDialog = createAction(
|
||||
VIEW_PREFIX + '[set notification dialog]',
|
||||
props<{message: string; title: string}>()
|
||||
);
|
||||
|
||||
export class SetServerError {
|
||||
public type = VIEW_ACTIONS.SET_SERVER_ERROR;
|
||||
public payload: {
|
||||
serverError: Omit<HttpErrorResponse, 'headers'>;
|
||||
contextSubCode?: number;
|
||||
customMessage?: string;
|
||||
aggregateSimilar: boolean;
|
||||
};
|
||||
export const resetLoader = createAction(VIEW_PREFIX + '[reset loader]');
|
||||
|
||||
constructor(serverError: HttpErrorResponse, contextSubCode?: number, customMessage?: string, aggregateSimilar = false) {
|
||||
this.payload = {
|
||||
serverError: omit(['headers'], serverError) as Omit<HttpErrorResponse, 'headers'>,
|
||||
contextSubCode,
|
||||
customMessage,
|
||||
aggregateSimilar};
|
||||
}
|
||||
}
|
||||
export const setBackdrop = createAction(
|
||||
VIEW_PREFIX + '[set backdrop]',
|
||||
props<{payload: boolean}>()
|
||||
);
|
||||
|
||||
export class SetNotificationDialog {
|
||||
public type = VIEW_ACTIONS.SET_NOTIFICATION_DIALOG;
|
||||
public payload: { message: string; title: string };
|
||||
export const activeLoader = createAction(
|
||||
VIEW_PREFIX + '[activate loader]',
|
||||
(endpoint: string) => ({endpoint})
|
||||
);
|
||||
|
||||
constructor(payload: { message: string; title: string }) {
|
||||
this.payload = payload;
|
||||
}
|
||||
}
|
||||
export const deactivateLoader = createAction(
|
||||
VIEW_PREFIX + '[deactivate loader]',
|
||||
(endpoint: string) => ({endpoint})
|
||||
);
|
||||
|
||||
export class ResetLoader implements Action {
|
||||
readonly type = VIEW_ACTIONS.RESET_LOADER;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
export const visibilityChanged = createAction(
|
||||
VIEW_PREFIX + '[visibility changed]',
|
||||
props<{visible: boolean}>()
|
||||
);
|
||||
|
||||
export class SetBackdrop implements Action {
|
||||
readonly type = VIEW_ACTIONS.SET_BACKDROP;
|
||||
export const addMessage = createAction(
|
||||
VIEW_PREFIX + '[add message]',
|
||||
(severity: MessageSeverityEnum, msg: string, userActions?: {actions: any[]; name: string}[]) =>
|
||||
({severity, msg, userActions})
|
||||
);
|
||||
|
||||
constructor(public payload: boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
export class ActiveLoader {
|
||||
type = VIEW_ACTIONS.ACTIVE_LOADER;
|
||||
public payload: any;
|
||||
|
||||
constructor(endpoint = 'default') {
|
||||
this.payload = {endpoint};
|
||||
}
|
||||
}
|
||||
|
||||
export class DeactiveLoader {
|
||||
type = VIEW_ACTIONS.DEACTIVE_LOADER;
|
||||
public payload: any;
|
||||
|
||||
constructor(endpoint = 'default') {
|
||||
this.payload = {endpoint};
|
||||
}
|
||||
}
|
||||
|
||||
export class VisibilityChanged {
|
||||
type = VIEW_ACTIONS.VISIBILITY_CHANGED;
|
||||
|
||||
constructor(public visible: boolean) {}
|
||||
}
|
||||
|
||||
export class AddMessage implements Action {
|
||||
readonly type = VIEW_ACTIONS.ADD_MESSAGE;
|
||||
public payload: {
|
||||
severity: string;
|
||||
msg: string;
|
||||
userActions?: {actions: any[]; name: string}[];
|
||||
};
|
||||
|
||||
constructor(severity: MessageSeverityEnum, msg: string, userActions?: {actions: any[]; name: string}[]) {
|
||||
this.payload = {severity, msg, userActions};
|
||||
}
|
||||
}
|
||||
export const removeMessage = createAction(VIEW_PREFIX + '[remove message]');
|
||||
|
||||
export const setServerUpdatesAvailable = createAction(
|
||||
VIEW_ACTIONS.SET_SERVER_UPDATES_AVAILABLE,
|
||||
VIEW_PREFIX + '[set server updates available]',
|
||||
props<{availableUpdates}>()
|
||||
);
|
||||
|
||||
export const setScaleFactor = createAction(
|
||||
VIEW_ACTIONS + '[set scale]',
|
||||
VIEW_PREFIX + '[set scale]',
|
||||
props<{scale: number}>()
|
||||
);
|
||||
|
||||
export const firstLogin = createAction(
|
||||
VIEW_ACTIONS + '[set first Login]',
|
||||
VIEW_PREFIX + '[set first Login]',
|
||||
props<{first: boolean}>()
|
||||
);
|
||||
|
||||
export const neverShowPopupAgain = createAction(VIEW_PREFIX + 'NEVER_SHOW_POPUP_AGAIN', props<{ popupId: string; reset?: boolean }>());
|
||||
|
||||
export const plotlyReady = createAction(VIEW_ACTIONS + '[plotly ready]');
|
||||
export const plotlyReady = createAction(VIEW_PREFIX + '[plotly ready]');
|
||||
|
||||
@@ -12,11 +12,12 @@ import {TasksArchiveManyResponse} from '../../../business-logic/model/tasks/task
|
||||
import {TasksPublishManyResponse} from '../../../business-logic/model/tasks/tasksPublishManyResponse';
|
||||
import {TasksStopManyResponse} from '../../../business-logic/model/tasks/tasksStopManyResponse';
|
||||
import {EntityTypeEnum} from '../../../shared/constants/non-common-consts';
|
||||
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
import {ProjectStatsGraphData} from '@common/core/reducers/projects.reducer';
|
||||
|
||||
export const PROJECTS_PREFIX = '[ROOT_PROJECTS] ';
|
||||
export const GET_PROJECTS = PROJECTS_PREFIX + 'GET_PROJECTS';
|
||||
export const SET_PROJECTS = PROJECTS_PREFIX + 'SET_PROJECTS';
|
||||
export const SET_SELECTED_PROJECT = PROJECTS_PREFIX + 'SET_SELECTED_PROJECT';
|
||||
export const SET_SELECTED_PROJECT_ID = PROJECTS_PREFIX + 'SET_SELECTED_PROJECT_ID';
|
||||
export const RESET_SELECTED_PROJECT = PROJECTS_PREFIX + 'RESET_SELECTED_PROJECT';
|
||||
export const RESET_PROJECT_SELECTION = PROJECTS_PREFIX + 'RESET_PROJECT_SELECTION';
|
||||
@@ -28,7 +29,7 @@ export interface TagColor {
|
||||
background: string;
|
||||
}
|
||||
|
||||
export class GetAllProjects implements ISmAction {
|
||||
export class GetAllSystemProjects implements ISmAction {
|
||||
readonly type = GET_PROJECTS;
|
||||
}
|
||||
|
||||
@@ -59,14 +60,10 @@ export class SetSelectedProjectId implements ISmAction {
|
||||
}
|
||||
}
|
||||
|
||||
export class SetSelectedProject implements ISmAction {
|
||||
readonly type = SET_SELECTED_PROJECT;
|
||||
public payload: { project: Project };
|
||||
|
||||
constructor(project: Project) {
|
||||
this.payload = {project};
|
||||
}
|
||||
}
|
||||
export const setSelectedProject = createAction(
|
||||
PROJECTS_PREFIX + 'SET_SELECTED_PROJECT',
|
||||
props<{project: Project}>()
|
||||
);
|
||||
|
||||
export class ResetSelectedProject implements ISmAction {
|
||||
readonly type = RESET_SELECTED_PROJECT;
|
||||
@@ -103,3 +100,15 @@ export const openMoreInfoPopup = createAction(
|
||||
PROJECTS_PREFIX + '[open more info popup]',
|
||||
props<{ parentAction: ReturnType<typeof archivedSelectedModels>; operationName: string; entityType: EntityTypeEnum; res: ModelsPublishManyResponse | ModelsArchiveManyResponse | ModelsDeleteManyResponse | TasksResetManyResponse | TasksEnqueueManyResponse | TasksArchiveManyResponse | TasksPublishManyResponse | TasksStopManyResponse }>()
|
||||
);
|
||||
|
||||
export const setMetricVariant = createAction(
|
||||
PROJECTS_PREFIX + '[set selected metric variant for graph]',
|
||||
props<{projectId: string; col: MetricColumn}>()
|
||||
|
||||
);
|
||||
export const fetchGraphData = createAction(PROJECTS_PREFIX + '[fetch stats for project graph]');
|
||||
|
||||
export const setGraphData = createAction(
|
||||
PROJECTS_PREFIX + '[set project stats]',
|
||||
props<{stats: ProjectStatsGraphData[]}>()
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {TASKS_ACTIONS} from '../../../app.constants';
|
||||
import {ISmAction} from '../models/actions';
|
||||
import {Action} from '@ngrx/store';
|
||||
import {Task} from '../../../business-logic/model/tasks/task';
|
||||
|
||||
export class StopTask implements ISmAction {
|
||||
type = TASKS_ACTIONS.STOP_TASK;
|
||||
@@ -23,58 +22,3 @@ export class SetTaskInListAndInSelectedTask implements Action {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// new actions
|
||||
|
||||
export class SetTasksData implements ISmAction {
|
||||
type = TASKS_ACTIONS.SET_TASKS_DATA;
|
||||
public payload: { tasksData: Array<Task> };
|
||||
|
||||
constructor(tasksData: Array<Task>) {
|
||||
this.payload = {tasksData};
|
||||
}
|
||||
}
|
||||
|
||||
export class GetTaskForMetricsPage implements ISmAction {
|
||||
public type = TASKS_ACTIONS.GET_TASK_FOR_METRICS;
|
||||
public payload: { taskId: string };
|
||||
|
||||
constructor(taskId: string) {
|
||||
this.payload = {taskId};
|
||||
}
|
||||
}
|
||||
|
||||
export class SetSelectedTask implements ISmAction {
|
||||
public type = TASKS_ACTIONS.SET_SELECTED_TASK;
|
||||
public payload: { task: Task };
|
||||
|
||||
constructor(task: Task) {
|
||||
this.payload = {task};
|
||||
}
|
||||
}
|
||||
|
||||
export class SetTaskInTable implements ISmAction {
|
||||
public type = TASKS_ACTIONS.SET_TASK_IN_TABLE;
|
||||
public payload: { task: Task };
|
||||
constructor(task: Task) {
|
||||
this.payload = {task};
|
||||
}
|
||||
}
|
||||
|
||||
export class SetTaskMetric implements ISmAction {
|
||||
public type = TASKS_ACTIONS.SET_TASK_FOR_METRICS;
|
||||
public payload: { task: Task };
|
||||
|
||||
constructor(task: Task) {
|
||||
this.payload = {task};
|
||||
}
|
||||
}
|
||||
|
||||
export class UpdateTask implements ISmAction {
|
||||
public type = TASKS_ACTIONS.UPDATE_TASK;
|
||||
public payload: { id: string; changes: Partial<Task> };
|
||||
|
||||
constructor(id: string, changes: Partial<Task>) {
|
||||
this.payload = {id, changes};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +1,61 @@
|
||||
import {Action, createAction, props} from '@ngrx/store';
|
||||
import {USERS_ACTIONS, USERS_PREFIX} from '../../../app.constants';
|
||||
import {User} from '../../../business-logic/model/users/user';
|
||||
import {OrganizationCreateInviteResponse} from "../../../business-logic/model/organization/organizationCreateInviteResponse";
|
||||
import {createAction, props} from '@ngrx/store';
|
||||
import {USERS_PREFIX} from '../../../app.constants';
|
||||
import {OrganizationCreateInviteResponse} from '../../../business-logic/model/organization/organizationCreateInviteResponse';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {LoginGetSettingsResponse} from "../../../business-logic/model/login/loginGetSettingsResponse";
|
||||
|
||||
export const fetchCurrentUser = createAction(USERS_PREFIX +'FETCH_USER');
|
||||
|
||||
export class FetchCurrentUser implements Action {
|
||||
type = USERS_ACTIONS.FETCH_CURRENT_USER;
|
||||
|
||||
}
|
||||
|
||||
export const setCurrentUser = createAction(
|
||||
USERS_ACTIONS.SET_CURRENT_USER,
|
||||
props<{user: User; terms_of_use?: any}>()
|
||||
export const setCurrentUserName = createAction(USERS_PREFIX + 'SET_CURRENT_USER_NAME',
|
||||
props<{name: string}>()
|
||||
);
|
||||
|
||||
export const termsOfUseAccepted = createAction(USERS_PREFIX + '[TOS accepted]');
|
||||
|
||||
export const logout = createAction(USERS_ACTIONS.LOGOUT, props<{provider?: string}>());
|
||||
export const logoutSuccess = createAction(USERS_ACTIONS.LOGOUT_SUCCESS);
|
||||
|
||||
|
||||
export class SetPreferences implements Action {
|
||||
type = USERS_ACTIONS.SET_PREF;
|
||||
|
||||
constructor(public payload) {}
|
||||
}
|
||||
export const logout = createAction(USERS_PREFIX + 'LOGOUT', props<{provider?: string}>());
|
||||
export const logoutSuccess = createAction(USERS_PREFIX + 'LOGOUT_SUCCESS');
|
||||
|
||||
export const setPreferences = createAction(USERS_PREFIX +'SET_PREF', props<{payload: any}>());
|
||||
export const getInviteUserLink = createAction(USERS_PREFIX +'GET_INVITE_USER_LINK');
|
||||
export const setInviteUserLink = createAction(USERS_PREFIX +'SET_INVITE_USER_LINK',
|
||||
props<OrganizationCreateInviteResponse>()
|
||||
);
|
||||
|
||||
export const addWorkspace = createAction(
|
||||
'[login] add workspace',
|
||||
props<{inviteId: string}>()
|
||||
);
|
||||
export const addWorkspace = createAction(USERS_PREFIX + ' add workspace', props<{inviteId: string}>());
|
||||
export const setFilterByUser = createAction(USERS_PREFIX +'SET_FILTERED_BY_USER', props<{showOnlyUserWork: boolean}>());
|
||||
export const leaveWorkspace = createAction(
|
||||
'[login] leave workspace',
|
||||
USERS_PREFIX + ' leave workspace',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany}>()
|
||||
);
|
||||
|
||||
export const setWorkspace = createAction(
|
||||
'[users] set workspace',
|
||||
USERS_PREFIX + ' set workspace',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany}>()
|
||||
);
|
||||
|
||||
export const setActiveWorkspace = createAction(
|
||||
'[users] set active workspace',
|
||||
USERS_PREFIX + ' set active workspace',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany}>()
|
||||
);
|
||||
|
||||
export const setSelectedWorkspaceTab = createAction(
|
||||
'[users] set selected workspace tab',
|
||||
USERS_PREFIX + ' set selected workspace tab',
|
||||
props<{workspace: GetCurrentUserResponseUserObjectCompany}>()
|
||||
);
|
||||
|
||||
export const removeWorkspace = createAction(
|
||||
'[users] set selected workspace',
|
||||
USERS_PREFIX + ' set selected workspace',
|
||||
props<{workspaceId: string}>()
|
||||
);
|
||||
|
||||
export const getUserWorkspaces = createAction('[login] get user workspaces');
|
||||
export const getUserWorkspaces = createAction(USERS_PREFIX + ' get user workspaces');
|
||||
|
||||
export const setUserWorkspaces = createAction(
|
||||
'[users] set user workspaces',
|
||||
USERS_PREFIX + ' set user workspaces',
|
||||
props<{workspaces: any[]}>()
|
||||
);
|
||||
|
||||
export const setUserWorkspacesFromUser = createAction('[users] set user workspaces from current user');
|
||||
export const setUserWorkspacesFromUser = createAction(USERS_PREFIX + ' set user workspaces from current user');
|
||||
|
||||
export const setAccountAdministrationPage = createAction(`${USERS_PREFIX} route to account-administration` );
|
||||
export const getWhitelistEntries = createAction(`${USERS_PREFIX} get invited users`);
|
||||
export const setWhitelistEntries = createAction(`${USERS_PREFIX} set invited users`,
|
||||
props<{whitelistEntries: LoginGetSettingsResponse }>());
|
||||
export const removeWhitelistEntry = createAction(`${USERS_PREFIX} remove invited user`,
|
||||
props<{remove: string[]}>());
|
||||
export const deleteWhitelistEntry = createAction(`${USERS_PREFIX} delete invited user`,
|
||||
props<{user: User}>());
|
||||
export const addWhitelistEntries = createAction(`${USERS_PREFIX} add invited user`,
|
||||
props<{whitelistEntries: string[]}>());
|
||||
export const setAddWhitelistEntries = createAction(`${USERS_PREFIX} set add invited user`,
|
||||
props<{whitelistEntries: string[]}>());
|
||||
export const setRemoveWhitelistEntry = createAction(`${USERS_PREFIX} set remove invited user`,
|
||||
props<{removed: string[]}>());
|
||||
export const getApiVersion = createAction(`${USERS_PREFIX} get api version` );
|
||||
export const setApiVersion = createAction(`${USERS_PREFIX} set api version`, props<{serverVersions: {server: string; api: string}}>());
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ import {Injectable} from '@angular/core';
|
||||
import {act, Actions, Effect, ofType} from '@ngrx/effects';
|
||||
import {ApiAuthService} from '../../../business-logic/api-services/auth.service';
|
||||
import * as authActions from '../actions/common-auth.actions';
|
||||
import {RequestFailed} from '../actions/http.actions';
|
||||
import {ActiveLoader, DeactiveLoader, SetServerError} from '../actions/layout.actions';
|
||||
import {requestFailed} from '../actions/http.actions';
|
||||
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
|
||||
import {catchError, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import {AuthGetCredentialsResponse} from '../../../business-logic/model/auth/authGetCredentialsResponse';
|
||||
import {Store} from '@ngrx/store';
|
||||
@@ -19,7 +19,7 @@ export class CommonAuthEffectsService {
|
||||
@Effect()
|
||||
activeLoader = this.actions.pipe(
|
||||
ofType(authActions.getAllCredentials, authActions.createCredential),
|
||||
map(action => new ActiveLoader(action.type))
|
||||
map(action => activeLoader(action.type))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
@@ -29,9 +29,9 @@ export class CommonAuthEffectsService {
|
||||
withLatestFrom(this.store.select(selectCurrentUser)),
|
||||
mergeMap(([res, user]: [AuthGetCredentialsResponse, GetCurrentUserResponseUserObject]) => [
|
||||
authActions.updateAllCredentials({credentials: res.credentials, extra: res.additional_credentials, workspace: user.company.id}),
|
||||
new DeactiveLoader(action.type)
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [new RequestFailed(error), new DeactiveLoader(action.type)])
|
||||
catchError(error => [requestFailed(error), deactivateLoader(action.type)])
|
||||
))
|
||||
);
|
||||
|
||||
@@ -39,11 +39,11 @@ export class CommonAuthEffectsService {
|
||||
revokeCredential = this.actions.pipe(
|
||||
ofType(authActions.credentialRevoked),
|
||||
mergeMap(action => this.credentialsApi.authRevokeCredentials({access_key: action.accessKey}).pipe(
|
||||
mergeMap(() => [authActions.removeCredential(action), new DeactiveLoader(action.type)]),
|
||||
mergeMap(() => [authActions.removeCredential(action), deactivateLoader(action.type)]),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
new DeactiveLoader(action.type),
|
||||
new SetServerError(error, null, 'Can\'t delete this credentials.')
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Can\'t delete this credentials.')
|
||||
])
|
||||
))
|
||||
);
|
||||
@@ -54,12 +54,12 @@ export class CommonAuthEffectsService {
|
||||
mergeMap(action => this.credentialsApi.authCreateCredentials({}).pipe(
|
||||
mergeMap(res => [
|
||||
authActions.addCredential({newCredential: res.credentials, workspaceId: action.workspaceId}),
|
||||
new DeactiveLoader(action.type)]),
|
||||
deactivateLoader(action.type)]),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
new SetServerError(error, null, 'Unable to create credentials'),
|
||||
requestFailed(error),
|
||||
setServerError(error, null, 'Unable to create credentials'),
|
||||
authActions.addCredential({newCredential: {}, workspaceId: action.workspaceId}),
|
||||
new DeactiveLoader(action.type)])
|
||||
deactivateLoader(action.type)])
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Actions, Effect, ofType} from '@ngrx/effects';
|
||||
import {EmptyAction, VIEW_ACTIONS} from '../../../app.constants';
|
||||
import {EmptyAction} from '../../../app.constants';
|
||||
import * as layoutActions from '../actions/layout.actions';
|
||||
import {filter, map, switchMap, take, mergeMap} from 'rxjs/operators';
|
||||
import {get} from 'lodash/fp';
|
||||
@@ -28,9 +28,9 @@ export class LayoutEffects {
|
||||
) {}
|
||||
|
||||
@Effect({dispatch: false})
|
||||
serverErrorMoreInfo = this.actions.pipe(ofType<layoutActions.SetServerError>(VIEW_ACTIONS.SET_SERVER_ERROR),
|
||||
filter(action => !!action.payload.serverError),
|
||||
map(action => this.parseError(get('error.meta.result_msg', action.payload.serverError))),
|
||||
serverErrorMoreInfo = this.actions.pipe(ofType(layoutActions.setServerError),
|
||||
filter(action => !!action.serverError),
|
||||
map(action => this.parseError(get('error.meta.result_msg', action.serverError))),
|
||||
filter(([ids]) => !!ids),
|
||||
switchMap(([ids, entity]) => this.getAllEntity(ids, entity).pipe(
|
||||
filter(res => !!res),
|
||||
@@ -45,15 +45,15 @@ export class LayoutEffects {
|
||||
|
||||
@Effect({dispatch: false})
|
||||
popupError = this.actions.pipe(
|
||||
ofType<layoutActions.SetServerError>(VIEW_ACTIONS.SET_SERVER_ERROR),
|
||||
filter(action => action.payload.serverError?.status !== 401),
|
||||
ofType(layoutActions.setServerError),
|
||||
filter(action => action.serverError?.status !== 401),
|
||||
map((action) => {
|
||||
if (action.payload?.serverError?.status === 502) {
|
||||
console.log('Gateway Error', action.payload.serverError);
|
||||
if (action.serverError?.status === 502) {
|
||||
console.log('Gateway Error', action.serverError);
|
||||
return;
|
||||
}
|
||||
const customMessage: string = action.payload.customMessage;
|
||||
if (action.payload.aggregateSimilar) {
|
||||
const customMessage: string = action.customMessage;
|
||||
if (action.aggregateSimilar) {
|
||||
const lastTS = this.errors[customMessage];
|
||||
const now = (new Date()).getTime();
|
||||
if (lastTS && lastTS + ERROR_AGGREGATION > now) {
|
||||
@@ -62,9 +62,9 @@ export class LayoutEffects {
|
||||
this.errors[customMessage] = now;
|
||||
}
|
||||
let resultMessage: string;
|
||||
const subcode = get('error.meta.result_subcode', action.payload.serverError);
|
||||
const subcode = get('error.meta.result_subcode', action.serverError);
|
||||
if (subcode) {
|
||||
resultMessage = `Error ${subcode} : ${get('error.meta.result_msg', action.payload.serverError)}`;
|
||||
resultMessage = `Error ${subcode} : ${get('error.meta.result_msg', action.serverError)}`;
|
||||
}
|
||||
this.alertDialogRef = this.dialog.open(AlertDialogComponent, {
|
||||
data: {alertMessage: 'Error', alertSubMessage: customMessage, resultMessage}
|
||||
@@ -77,8 +77,7 @@ export class LayoutEffects {
|
||||
|
||||
@Effect()
|
||||
addMessage: Observable<any> = this.actions.pipe(
|
||||
ofType<layoutActions.AddMessage>(VIEW_ACTIONS.ADD_MESSAGE),
|
||||
map((action: layoutActions.AddMessage) => action.payload),
|
||||
ofType(layoutActions.addMessage),
|
||||
mergeMap(payload => this.notifierService.show({type: payload.severity, message: payload.msg, actions: payload.userActions})),
|
||||
mergeMap(actions => actions.length > 0? actions : [new EmptyAction()])
|
||||
);
|
||||
|
||||
@@ -1,23 +1,29 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {act, Actions, createEffect, Effect, ofType} from '@ngrx/effects';
|
||||
import {Actions, createEffect, Effect, ofType} from '@ngrx/effects';
|
||||
import {ApiProjectsService} from '../../../business-logic/api-services/projects.service';
|
||||
import * as actions from '../actions/projects.actions';
|
||||
import {
|
||||
ResetSelectedProject,
|
||||
SetSelectedProjectId,
|
||||
UpdateProject,
|
||||
ResetProjectSelection,
|
||||
fetchGraphData,
|
||||
GetAllSystemProjects,
|
||||
getCompanyTags,
|
||||
getTags,
|
||||
openMoreInfoPopup,
|
||||
openTagColorsMenu,
|
||||
RESET_PROJECT_SELECTION,
|
||||
setTags, openTagColorsMenu, getTags, setCompanyTags, getCompanyTags, openMoreInfoPopup
|
||||
ResetProjectSelection,
|
||||
ResetSelectedProject,
|
||||
setCompanyTags,
|
||||
setGraphData,
|
||||
SetSelectedProjectId,
|
||||
setTags,
|
||||
UpdateProject
|
||||
} from '../actions/projects.actions';
|
||||
import {GetAllProjects} from '../actions/projects.actions';
|
||||
|
||||
import {catchError, filter, finalize, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import {RequestFailed} from '../actions/http.actions';
|
||||
import {ActiveLoader, DeactiveLoader, SetServerError} from '../actions/layout.actions';
|
||||
import {SetSelectedExperiments} from '../../experiments/actions/common-experiments-view.actions';
|
||||
import {SetSelectedModels} from '../../models/actions/models-view.actions';
|
||||
import {requestFailed} from '../actions/http.actions';
|
||||
import {activeLoader, deactivateLoader, setServerError} from '../actions/layout.actions';
|
||||
import {setSelectedModels} from '../../models/actions/models-view.actions';
|
||||
import {TagColorMenuComponent} from '../../shared/ui-components/tags/tag-color-menu/tag-color-menu.component';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ApiOrganizationService} from '../../../business-logic/api-services/organization.service';
|
||||
@@ -26,9 +32,15 @@ import {selectRouterParams} from '../reducers/router-reducer';
|
||||
import {forkJoin} from 'rxjs';
|
||||
import {ProjectsGetTaskTagsResponse} from '../../../business-logic/model/projects/projectsGetTaskTagsResponse';
|
||||
import {ProjectsGetModelTagsResponse} from '../../../business-logic/model/projects/projectsGetModelTagsResponse';
|
||||
import {selectSelectedProjectId} from '../reducers/projects.reducer';
|
||||
import {selectSelectedMetricVariantForCurrProject, selectSelectedProjectId} from '../reducers/projects.reducer';
|
||||
import {OperationErrorDialogComponent} from '@common/shared/ui-components/overlay/operation-error-dialog/operation-error-dialog.component';
|
||||
import {EmptyAction} from '../../../app.constants';
|
||||
import {ApiTasksService} from '../../../business-logic/api-services/tasks.service';
|
||||
import {createMetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
import {get} from 'lodash/fp';
|
||||
import {ITask} from '../../../business-logic/model/al-task';
|
||||
import {TasksGetAllExRequest} from '../../../business-logic/model/tasks/tasksGetAllExRequest';
|
||||
import {setSelectedExperiments} from '../../experiments/actions/common-experiments-view.actions';
|
||||
import {selectShowHidden} from "@common/projects/common-projects.reducer";
|
||||
|
||||
const ALL_PROJECTS_OBJECT = {id: '*', name: 'All Experiments'};
|
||||
|
||||
@@ -38,7 +50,7 @@ export class ProjectsEffects {
|
||||
|
||||
constructor(
|
||||
private actions$: Actions, private projectsApi: ApiProjectsService, private orgApi: ApiOrganizationService,
|
||||
private store: Store<any>, private dialog: MatDialog
|
||||
private store: Store<any>, private dialog: MatDialog, private tasksApi: ApiTasksService
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -46,28 +58,29 @@ export class ProjectsEffects {
|
||||
activeLoader = this.actions$.pipe(
|
||||
ofType(actions.SET_SELECTED_PROJECT_ID),
|
||||
filter((action: any) => !!action.payload?.projectId),
|
||||
map(action => new ActiveLoader(action.type))
|
||||
map(action => activeLoader(action.type))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
getProjects$ = this.actions$.pipe(
|
||||
ofType<GetAllProjects>(actions.GET_PROJECTS),
|
||||
switchMap(() =>
|
||||
this.projectsApi.projectsGetAllEx({only_fields: ['name', 'company', 'parent']})
|
||||
ofType<GetAllSystemProjects>(actions.GET_PROJECTS),
|
||||
withLatestFrom(this.store.select(selectShowHidden)),
|
||||
switchMap(([action, showHidden]) =>
|
||||
this.projectsApi.projectsGetAllEx({only_fields: ['name', 'company', 'parent'], search_hidden: showHidden})
|
||||
.pipe(map(res => new actions.SetAllProjects(res.projects)))
|
||||
)
|
||||
);
|
||||
|
||||
@Effect()
|
||||
ResetProjects$ = this.actions$.pipe(
|
||||
resetProjects$ = this.actions$.pipe(
|
||||
ofType<ResetSelectedProject>(actions.RESET_SELECTED_PROJECT),
|
||||
mergeMap(() => [new ResetProjectSelection()])
|
||||
);
|
||||
|
||||
@Effect()
|
||||
ResetProjectSelections$ = this.actions$.pipe(
|
||||
resetProjectSelections$ = this.actions$.pipe(
|
||||
ofType<ResetProjectSelection>(RESET_PROJECT_SELECTION),
|
||||
mergeMap(() => [new SetSelectedExperiments([]), new SetSelectedModels([])])
|
||||
mergeMap(() => [setSelectedExperiments({experiments: []}), setSelectedModels({models: []})])
|
||||
);
|
||||
|
||||
@Effect()
|
||||
@@ -76,12 +89,12 @@ export class ProjectsEffects {
|
||||
switchMap((action) =>
|
||||
this.projectsApi.projectsUpdate({project: action.payload.id, ...action.payload.changes})
|
||||
.pipe(
|
||||
mergeMap((res) => [
|
||||
mergeMap(() => [
|
||||
new actions.UpdateProjectCompleted()
|
||||
]),
|
||||
catchError(err => [
|
||||
new RequestFailed(err),
|
||||
new SetServerError(err, null, 'Update project failed'),
|
||||
requestFailed(err),
|
||||
setServerError(err, null, 'Update project failed'),
|
||||
new actions.SetSelectedProjectId(action.payload.id)
|
||||
])
|
||||
)
|
||||
@@ -92,16 +105,16 @@ export class ProjectsEffects {
|
||||
ofType<SetSelectedProjectId>(actions.SET_SELECTED_PROJECT_ID),
|
||||
withLatestFrom(this.store.select(selectSelectedProjectId)),
|
||||
switchMap(([action, selectedProjectId]) => {
|
||||
if(!action.payload.projectId){
|
||||
return [new actions.SetSelectedProject(null)];
|
||||
if (!action.payload.projectId) {
|
||||
return [actions.setSelectedProject({project: null})];
|
||||
}
|
||||
if (action.payload.projectId === selectedProjectId) {
|
||||
return [new DeactiveLoader(action.type)];
|
||||
return [deactivateLoader(action.type)];
|
||||
}
|
||||
if (action.payload.projectId === '*') {
|
||||
return [
|
||||
new actions.SetSelectedProject(ALL_PROJECTS_OBJECT),
|
||||
new DeactiveLoader(action.type)];
|
||||
actions.setSelectedProject({project: ALL_PROJECTS_OBJECT}),
|
||||
deactivateLoader(action.type)];
|
||||
} else {
|
||||
this.fetchingExampleExperiment = action.payload.example && action.payload.projectId;
|
||||
return this.projectsApi.projectsGetAllEx({
|
||||
@@ -112,13 +125,13 @@ export class ProjectsEffects {
|
||||
.pipe(
|
||||
finalize(() => this.fetchingExampleExperiment = null),
|
||||
mergeMap(res => [
|
||||
new actions.SetSelectedProject(res.projects[0]),
|
||||
new DeactiveLoader(action.type),
|
||||
]
|
||||
actions.setSelectedProject({project: res.projects[0]}),
|
||||
deactivateLoader(action.type),
|
||||
]
|
||||
),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
new DeactiveLoader(action.type)
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type)
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -128,7 +141,7 @@ export class ProjectsEffects {
|
||||
@Effect({dispatch: false})
|
||||
openTagColor = this.actions$.pipe(
|
||||
ofType(openTagColorsMenu),
|
||||
map(action => {
|
||||
map(() => {
|
||||
this.dialog.open(TagColorMenuComponent);
|
||||
})
|
||||
);
|
||||
@@ -139,7 +152,7 @@ export class ProjectsEffects {
|
||||
switchMap(() => this.orgApi.organizationGetTags({include_system: true})
|
||||
.pipe(
|
||||
map((res: OrganizationGetTagsResponse) => setCompanyTags({tags: res.tags, systemTags: res.system_tags})),
|
||||
catchError(error => [new RequestFailed(error)])
|
||||
catchError(error => [requestFailed(error)])
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -157,12 +170,12 @@ export class ProjectsEffects {
|
||||
Array.from(new Set(res[0].tags.concat(res[1].tags))).sort()),
|
||||
mergeMap((tags: string[]) => [
|
||||
setTags({tags}),
|
||||
new DeactiveLoader(action.type)
|
||||
deactivateLoader(action.type)
|
||||
]),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
new DeactiveLoader(action.type),
|
||||
new SetServerError(error, null, 'Fetch tags failed')]
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Fetch tags failed')]
|
||||
)
|
||||
))
|
||||
);
|
||||
@@ -170,15 +183,54 @@ export class ProjectsEffects {
|
||||
openMoreInfoPopupEffect = createEffect(() => this.actions$.pipe(
|
||||
ofType(openMoreInfoPopup),
|
||||
switchMap(action => this.dialog.open(OperationErrorDialogComponent, {
|
||||
data: {
|
||||
title: `${action.operationName} ${action.entityType}`,
|
||||
action,
|
||||
iconClass: `d-block al-ico-${action.operationName} al-icon w-auto`,
|
||||
}
|
||||
}).afterClosed()
|
||||
data: {
|
||||
title: `${action.operationName} ${action.entityType}`,
|
||||
action,
|
||||
iconClass: `d-block al-ico-${action.operationName} al-icon w-auto`,
|
||||
}
|
||||
}).afterClosed()
|
||||
)
|
||||
), {dispatch: false});
|
||||
|
||||
fetchProjectStats = createEffect(() => this.actions$.pipe(
|
||||
ofType(fetchGraphData),
|
||||
withLatestFrom(
|
||||
this.store.select(selectSelectedProjectId),
|
||||
this.store.select(selectSelectedMetricVariantForCurrProject)
|
||||
),
|
||||
filter(([, , variant]) => !!variant),
|
||||
switchMap(([, projectId, variant]) => {
|
||||
const col = createMetricColumn(variant, projectId);
|
||||
return this.tasksApi.tasksGetAllEx({
|
||||
project: [projectId],
|
||||
only_fields: ['started', 'last_iteration', 'user.name', 'type', 'name', 'status', 'active_duration', col.id],
|
||||
[col.id]: [0, null],
|
||||
started: ['2000-01-01T00:00:00', null],
|
||||
status: ['completed', 'published', 'failed', 'stopped', 'closed'],
|
||||
order_by: ['-started'],
|
||||
type: ['__$not', 'annotation_manual', '__$not', 'annotation', '__$not', 'dataset_import'],
|
||||
system_tags: ['-archived'],
|
||||
page_size: 1000
|
||||
} as unknown as TasksGetAllExRequest).pipe(
|
||||
map((res) =>
|
||||
setGraphData({
|
||||
stats: res.tasks.map((task: ITask) => {
|
||||
const started = new Date(task.started).getTime();
|
||||
const end = started + (task.active_duration ?? 0) * 1000;
|
||||
return {
|
||||
id: task.id,
|
||||
y: get(col.id, task),
|
||||
x: end,
|
||||
name: task.name,
|
||||
status: task.status,
|
||||
type: task.type,
|
||||
user: task.user.name,
|
||||
};
|
||||
})
|
||||
}))
|
||||
);
|
||||
})
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,45 +1,27 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {Actions, createEffect, Effect, ofType} from '@ngrx/effects';
|
||||
import {ActionType, Store} from '@ngrx/store';
|
||||
import {MESSAGES_SEVERITY, USERS_ACTIONS} from '../../../app.constants';
|
||||
import {ApiUsersService} from '../../../business-logic/api-services/users.service';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {MESSAGES_SEVERITY} from '~/app.constants';
|
||||
import {ApiUsersService} from '~/business-logic/api-services/users.service';
|
||||
import {
|
||||
addWhitelistEntries,
|
||||
FetchCurrentUser,
|
||||
getWhitelistEntries,
|
||||
getInviteUserLink,
|
||||
getUserWorkspaces,
|
||||
leaveWorkspace,
|
||||
logout,
|
||||
removeWorkspace,
|
||||
setActiveWorkspace,
|
||||
setCurrentUser,
|
||||
setAddWhitelistEntries,
|
||||
setWhitelistEntries,
|
||||
setInviteUserLink,
|
||||
setUserWorkspaces,
|
||||
setUserWorkspacesFromUser,
|
||||
removeWhitelistEntry,
|
||||
setRemoveWhitelistEntry,
|
||||
deleteWhitelistEntry,
|
||||
setAccountAdministrationPage, logoutSuccess
|
||||
fetchCurrentUser, getApiVersion, getInviteUserLink, getUserWorkspaces, leaveWorkspace, logout, logoutSuccess, removeWorkspace, setAccountAdministrationPage, setActiveWorkspace, setApiVersion,
|
||||
setInviteUserLink, setUserWorkspaces, setUserWorkspacesFromUser
|
||||
} from '../actions/users.actions';
|
||||
import {catchError, map, mergeMap, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import {RequestFailed} from '../actions/http.actions';
|
||||
import {ApiAuthService} from '../../../business-logic/api-services/auth.service';
|
||||
import {ApiOrganizationService} from '../../../business-logic/api-services/organization.service';
|
||||
import {ActiveLoader, AddMessage, DeactiveLoader, SetServerError} from '../actions/layout.actions';
|
||||
import {OrganizationCreateInviteResponse} from '../../../business-logic/model/organization/organizationCreateInviteResponse';
|
||||
import {ApiLoginService} from '../../../business-logic/api-services/login.service';
|
||||
import {OrganizationGetUserCompaniesResponse} from '../../../business-logic/model/organization/organizationGetUserCompaniesResponse';
|
||||
import {requestFailed} from '../actions/http.actions';
|
||||
import {ApiAuthService} from '~/business-logic/api-services/auth.service';
|
||||
import {ApiOrganizationService} from '~/business-logic/api-services/organization.service';
|
||||
import {addMessage, deactivateLoader, setServerError} from '../actions/layout.actions';
|
||||
import {OrganizationCreateInviteResponse} from '~/business-logic/model/organization/organizationCreateInviteResponse';
|
||||
import {ApiLoginService} from '~/business-logic/api-services/login.service';
|
||||
import {OrganizationGetUserCompaniesResponse} from '~/business-logic/model/organization/organizationGetUserCompaniesResponse';
|
||||
import {selectRouterUrl} from '../reducers/router-reducer';
|
||||
import {LoginLogoutResponse} from '../../../business-logic/model/login/loginLogoutResponse';
|
||||
import {LoginLogoutResponse} from '~/business-logic/model/login/loginLogoutResponse';
|
||||
import {ErrorService} from '../../shared/services/error.service';
|
||||
import {AuthDeleteUserResponse} from '../../../business-logic/model/auth/authDeleteUserResponse';
|
||||
import {selectCurrentUser} from '../reducers/users-reducer';
|
||||
import {LoginRemoveWhitelistEntriesResponse} from '../../../business-logic/model/login/loginRemoveWhitelistEntriesResponse';
|
||||
import {LoginAddWhitelistEntriesResponse} from '../../../business-logic/model/login/loginAddWhitelistEntriesResponse';
|
||||
import {ApiServerService} from '~/business-logic/api-services/server.service';
|
||||
import {ServerInfoResponse} from '~/business-logic/model/server/serverInfoResponse';
|
||||
import {setCurrentUser} from '~/core/actions/users.action';
|
||||
|
||||
|
||||
@Injectable()
|
||||
@@ -47,25 +29,23 @@ export class CommonUserEffects {
|
||||
|
||||
constructor(
|
||||
private actions: Actions, private userService: ApiUsersService, private organizationService: ApiOrganizationService,
|
||||
private router: Router, private authService: ApiAuthService, private loginApi: ApiLoginService, private store: Store<any>,
|
||||
private errorService: ErrorService
|
||||
private router: Router, private authService: ApiAuthService, private loginApi: ApiLoginService, private serverService: ApiServerService,
|
||||
private store: Store<any>, private errorService: ErrorService
|
||||
) {
|
||||
}
|
||||
|
||||
@Effect()
|
||||
fetchUser$ = this.actions.pipe(
|
||||
ofType<FetchCurrentUser>(USERS_ACTIONS.FETCH_CURRENT_USER),
|
||||
mergeMap(() => this.userService.usersGetCurrentUser({})
|
||||
fetchUser$ = createEffect(() => this.actions.pipe(
|
||||
ofType(fetchCurrentUser),
|
||||
mergeMap(() => this.userService.usersGetCurrentUser({get_supported_features: true})
|
||||
.pipe(
|
||||
switchMap((res) => [setCurrentUser(res)]),
|
||||
catchError(error => [new RequestFailed(error)])
|
||||
catchError(error => [requestFailed(error)])
|
||||
)
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
@Effect()
|
||||
getUserInviteLink$ = this.actions.pipe(
|
||||
ofType(getInviteUserLink.type),
|
||||
getUserInviteLink$ = createEffect( () => this.actions.pipe(
|
||||
ofType(getInviteUserLink),
|
||||
switchMap((action) =>
|
||||
this.organizationService.organizationCreateInvite({})
|
||||
.pipe(
|
||||
@@ -73,16 +53,15 @@ export class CommonUserEffects {
|
||||
setInviteUserLink(res)
|
||||
]),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
new DeactiveLoader(action.type),
|
||||
new SetServerError(error, null, 'Fetch invite link failed')
|
||||
requestFailed(error),
|
||||
deactivateLoader(action.type),
|
||||
setServerError(error, null, 'Fetch invite link failed')
|
||||
])
|
||||
)
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
@Effect()
|
||||
logoutFlow = this.actions.pipe(
|
||||
logoutFlow = createEffect( () => this.actions.pipe(
|
||||
ofType(logout),
|
||||
mergeMap(action => this.loginApi.loginLogout({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
@@ -97,10 +76,10 @@ export class CommonUserEffects {
|
||||
}
|
||||
return logoutSuccess();
|
||||
}),
|
||||
catchError(err => [new AddMessage(MESSAGES_SEVERITY.ERROR, `Logout Failed
|
||||
catchError(err => [addMessage(MESSAGES_SEVERITY.ERROR, `Logout Failed
|
||||
${this.errorService.getErrorMsg(err?.error)}`)])
|
||||
)),
|
||||
);
|
||||
));
|
||||
|
||||
setUserManagementPage = createEffect(() =>
|
||||
this.actions.pipe(
|
||||
@@ -117,7 +96,7 @@ ${this.errorService.getErrorMsg(err?.error)}`)])
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
mergeMap((action) => this.loginApi.loginLeaveCompany({company_id: action.workspace.id}).pipe(
|
||||
mergeMap(() => [removeWorkspace({workspaceId: action.workspace.id})]),
|
||||
catchError(() => [new AddMessage(MESSAGES_SEVERITY.ERROR, 'Failed to Leave Workspace')])
|
||||
catchError(() => [addMessage(MESSAGES_SEVERITY.ERROR, 'Failed to Leave Workspace')])
|
||||
)),
|
||||
));
|
||||
|
||||
@@ -140,78 +119,13 @@ ${this.errorService.getErrorMsg(err?.error)}`)])
|
||||
}
|
||||
)), {dispatch: false});
|
||||
|
||||
userLoader = createEffect(() =>
|
||||
this.actions.pipe(
|
||||
ofType(removeWhitelistEntry, deleteWhitelistEntry, addWhitelistEntries),
|
||||
mergeMap(action => [new ActiveLoader(action.type)])
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
getInvitedUsers = createEffect(() => this.actions.pipe(
|
||||
ofType(getWhitelistEntries),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
switchMap( () => this.loginApi.loginGetSettings({include_auth: true})),
|
||||
withLatestFrom(this.store.select(selectCurrentUser)),
|
||||
mergeMap( ([whitelistEntries, currentUser]) => {
|
||||
// make the current user to be first!
|
||||
const currentUserPosition = whitelistEntries.whitelist_entries.findIndex( whitelistEntry => whitelistEntry.user?.id === currentUser.id);
|
||||
if (currentUserPosition > -1) {
|
||||
const inviteCurrentUser = whitelistEntries.whitelist_entries.splice(currentUserPosition, 1);
|
||||
whitelistEntries.whitelist_entries = [inviteCurrentUser[0], ...whitelistEntries.whitelist_entries];
|
||||
}
|
||||
return [setWhitelistEntries({whitelistEntries})];
|
||||
})
|
||||
getApiVersion = createEffect(() => this.actions.pipe(
|
||||
ofType(getApiVersion),
|
||||
mergeMap(() => this.serverService.serverInfo({})),
|
||||
map((res: ServerInfoResponse) =>
|
||||
setApiVersion({serverVersions: {server: res.version, api: res.api_version}})
|
||||
),
|
||||
catchError(() => [setUserWorkspacesFromUser()])
|
||||
));
|
||||
|
||||
addInviteUsers = createEffect(() =>
|
||||
this.actions.pipe(
|
||||
ofType(addWhitelistEntries),
|
||||
switchMap( (action) =>
|
||||
this.loginApi.loginAddWhitelistEntries({emails: action.whitelistEntries})
|
||||
.pipe(
|
||||
mergeMap( (invitedEmails: LoginAddWhitelistEntriesResponse) => {
|
||||
const notAddedByServer = action.whitelistEntries.filter( email => !invitedEmails.added.includes(email));
|
||||
const actions: ActionType<any>[] = [new DeactiveLoader(action.type), setAddWhitelistEntries({whitelistEntries: invitedEmails.added})];
|
||||
if (notAddedByServer.length) {
|
||||
actions.push(new AddMessage(MESSAGES_SEVERITY.ERROR, `User addition failed: ${notAddedByServer.join(', ')} `));
|
||||
}
|
||||
return actions;
|
||||
}),
|
||||
catchError(err =>
|
||||
[new DeactiveLoader(action.type), new AddMessage(MESSAGES_SEVERITY.ERROR, err.error.meta.result_msg)]
|
||||
)
|
||||
)
|
||||
),
|
||||
));
|
||||
|
||||
removeWhitelistEntryUser = createEffect(() =>
|
||||
this.actions.pipe(
|
||||
ofType(removeWhitelistEntry),
|
||||
switchMap( (action) => this.loginApi.loginRemoveWhitelistEntries({emails: action.remove}).pipe(
|
||||
mergeMap( (removedEmails: LoginRemoveWhitelistEntriesResponse) => [
|
||||
setRemoveWhitelistEntry({removed: removedEmails.removed}),
|
||||
new DeactiveLoader(action.type)
|
||||
]
|
||||
)
|
||||
)),
|
||||
));
|
||||
|
||||
deleteWhitelistEntryUser = createEffect(() =>
|
||||
this.actions.pipe(
|
||||
ofType(deleteWhitelistEntry),
|
||||
switchMap( (action) =>
|
||||
this.authService.authDeleteUser({user: action.user.id}).pipe(
|
||||
mergeMap( (isRemovedUser: AuthDeleteUserResponse) => {
|
||||
if (isRemovedUser.deleted) {
|
||||
return [setRemoveWhitelistEntry({removed: [action.user.email]}), new DeactiveLoader(action.type)];
|
||||
}
|
||||
return [new AddMessage(MESSAGES_SEVERITY.ERROR, `User ${action.user.name} did not delete`), new DeactiveLoader(action.type)];
|
||||
}),
|
||||
catchError(err =>
|
||||
[new DeactiveLoader(action.type), new AddMessage(MESSAGES_SEVERITY.ERROR, err.error.meta.result_msg)]
|
||||
)
|
||||
)
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import {catchError} from 'rxjs/operators';
|
||||
import { Router } from '@angular/router';
|
||||
import {selectCurrentUser, selectActiveWorkspace, selectSelectedWorkspaceTab} from '../reducers/users-reducer';
|
||||
import {Store} from '@ngrx/store';
|
||||
import { setCurrentUser } from '../actions/users.actions';
|
||||
import {GetCurrentUserResponseUserObject} from '../../../business-logic/model/users/getCurrentUserResponseUserObject';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {GetCurrentUserResponseUserObject} from '~/business-logic/model/users/getCurrentUserResponseUserObject';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '~/business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {ConfigurationService} from '../../shared/services/configuration.service';
|
||||
import {setCurrentUser} from '~/core/actions/users.action';
|
||||
|
||||
@Injectable()
|
||||
export class WebappIntercptor implements HttpInterceptor {
|
||||
export class WebappInterceptor implements HttpInterceptor {
|
||||
private user: GetCurrentUserResponseUserObject;
|
||||
private workspace: GetCurrentUserResponseUserObjectCompany;
|
||||
private selectedWorkspaceTab: GetCurrentUserResponseUserObjectCompany;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import {ActionReducer, Action} from '@ngrx/store';
|
||||
import {merge, pick} from 'lodash/fp';
|
||||
import {userPreferences} from '../../user-preferences';
|
||||
import {USERS_ACTIONS} from '../../../app.constants';
|
||||
import {SetPreferences} from '../actions/users.actions';
|
||||
import {setPreferences} from '../actions/users.actions';
|
||||
|
||||
const firstRun = {};
|
||||
|
||||
@@ -22,8 +21,8 @@ export function createLocalStorageReducer(key: string, syncedKeys: string[], act
|
||||
const savedState = userPreferences.getPreferences(key);
|
||||
nextState = merge(nextState, savedState);
|
||||
}
|
||||
if (action.type === USERS_ACTIONS.SET_PREF) {
|
||||
const savedState = (action as SetPreferences).payload[key];
|
||||
if (action.type === setPreferences.type) {
|
||||
const savedState = (action as ReturnType<typeof setPreferences>).payload[key];
|
||||
nextState = merge(nextState, savedState);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {VIEW_ACTIONS} from '../../../app.constants';
|
||||
import * as actions from '../actions/layout.actions';
|
||||
|
||||
const initialState = {
|
||||
visible: false,
|
||||
@@ -10,13 +10,13 @@ const initialState = {
|
||||
export function messagesReducer(state = initialState, action) {
|
||||
|
||||
switch (action.type) {
|
||||
case VIEW_ACTIONS.ADD_MESSAGE:
|
||||
case actions.addMessage:
|
||||
return {
|
||||
visible: true,
|
||||
severity: action.payload.severity,
|
||||
message : action.payload.msg
|
||||
};
|
||||
case VIEW_ACTIONS.REMOVE_MESSAGE:
|
||||
case actions.removeMessage:
|
||||
return {
|
||||
visible: false
|
||||
};
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
import {createSelector} from '@ngrx/store';
|
||||
import * as projectsActions from '../actions/projects.actions';
|
||||
import {setCompanyTags, setTagColors, setTags, setTagsFilterByProject, TagColor} from '../actions/projects.actions';
|
||||
import {
|
||||
setCompanyTags,
|
||||
setGraphData,
|
||||
setMetricVariant,
|
||||
setTagColors,
|
||||
setTags,
|
||||
setTagsFilterByProject,
|
||||
TagColor
|
||||
} from '../actions/projects.actions';
|
||||
import {Project} from '../../../business-logic/model/projects/project';
|
||||
import {getSystemTags} from '../../../features/experiments/shared/experiments.utils';
|
||||
import {experimentsView} from '../../../features/experiments/reducers';
|
||||
import {ITableExperiment} from '../../experiments/shared/common-experiment-model.model';
|
||||
import {MetricColumn} from '@common/shared/utils/tableParamEncode';
|
||||
|
||||
export const SYSTEM_TAGS_BLACK_LIST = ['archived'];
|
||||
|
||||
export interface ProjectStatsGraphData {
|
||||
id: string;
|
||||
x: number;
|
||||
y: number;
|
||||
name: string;
|
||||
type: string;
|
||||
status: string;
|
||||
user: string;
|
||||
}
|
||||
|
||||
export interface RootProjects {
|
||||
projects: Project[];
|
||||
selectedProject: Project;
|
||||
@@ -18,6 +36,8 @@ export interface RootProjects {
|
||||
systemTags: string[];
|
||||
tagsColors: { [tag: string]: TagColor };
|
||||
tagsFilterByProject: boolean;
|
||||
graphVariant: {[project: string]: MetricColumn};
|
||||
graphData: ProjectStatsGraphData[];
|
||||
}
|
||||
|
||||
const initRootProjects: RootProjects = {
|
||||
@@ -29,11 +49,13 @@ const initRootProjects: RootProjects = {
|
||||
companyTags: [],
|
||||
systemTags: [],
|
||||
tagsColors: {},
|
||||
tagsFilterByProject: true
|
||||
tagsFilterByProject: true,
|
||||
graphVariant: {},
|
||||
graphData: null
|
||||
};
|
||||
|
||||
export const projects = state => state.rootProjects as RootProjects;
|
||||
export const selectProjects = createSelector(projects, (state): Project[] => state.projects);
|
||||
export const selectRootProjects = createSelector(projects, (state): Project[] => state.projects);
|
||||
export const selectSelectedProject = createSelector(projects, state => state.selectedProject);
|
||||
export const selectSelectedProjectDescription = createSelector(projects, state => state.selectedProject?.description);
|
||||
export const selectSelectedProjectId = createSelector(selectSelectedProject, (selectedProject): string => selectedProject ? selectedProject.id : '');
|
||||
@@ -46,9 +68,19 @@ export const selectProjectSystemTags = createSelector(projects, state => getSyst
|
||||
export const selectTagsColors = createSelector(projects, state => state.tagsColors);
|
||||
export const selectTagColors = createSelector(selectTagsColors,
|
||||
(tagsColors, props: { tag: string }) => tagsColors[props.tag]);
|
||||
const selectSelectedProjectsMetricVariant = createSelector(projects, state => state.graphVariant);
|
||||
|
||||
export const selectSelectedMetricVariant = createSelector(selectSelectedProjectsMetricVariant,
|
||||
(projectsVariant, projectId: string) => projectsVariant[projectId]);
|
||||
|
||||
export const selectSelectedMetricVariantForCurrProject = createSelector(
|
||||
selectSelectedProjectsMetricVariant, selectSelectedProjectId,
|
||||
(projectsVariant, projectId) => projectsVariant[projectId]);
|
||||
|
||||
export const selectGraphData = createSelector(projects, state => state.graphData);
|
||||
|
||||
|
||||
export function projectsReducer(state: RootProjects = initRootProjects, action) {
|
||||
export const projectsReducer = (state: RootProjects = initRootProjects, action) => {
|
||||
switch (action.type) {
|
||||
case projectsActions.SET_PROJECTS:
|
||||
return {...state, projects: action.payload};
|
||||
@@ -57,10 +89,11 @@ export function projectsReducer(state: RootProjects = initRootProjects, action)
|
||||
return {
|
||||
...state,
|
||||
...(state.selectedProject?.id !== projectId && {archive: initRootProjects.archive}),
|
||||
graphData: initRootProjects.graphData,
|
||||
};
|
||||
}
|
||||
case projectsActions.SET_SELECTED_PROJECT:
|
||||
return {...state, selectedProject: action.payload.project};
|
||||
case projectsActions.setSelectedProject.type:
|
||||
return {...state, selectedProject: action.project};
|
||||
case projectsActions.RESET_SELECTED_PROJECT:
|
||||
return {...state, selectedProject: initRootProjects.selectedProject};
|
||||
case projectsActions.UPDATE_PROJECT: {
|
||||
@@ -78,7 +111,13 @@ export function projectsReducer(state: RootProjects = initRootProjects, action)
|
||||
return {...state, companyTags: action.tags, systemTags: action.systemTags};
|
||||
case setTagColors.type:
|
||||
return {...state, tagsColors: {...state.tagsColors, [action.tag]: action.colors}};
|
||||
case setMetricVariant.type: {
|
||||
const payload = action as ReturnType<typeof setMetricVariant>;
|
||||
return {...state, graphVariant: {...state.graphVariant, [payload.projectId]: payload.col}};
|
||||
}
|
||||
case setGraphData.type:
|
||||
return {...state, graphData: (action as ReturnType<typeof setGraphData>).stats};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,156 +1,74 @@
|
||||
import {USERS_ACTIONS} from '../../../app.constants';
|
||||
import {createSelector} from '@ngrx/store';
|
||||
import {createSelector, on, ReducerTypes} from '@ngrx/store';
|
||||
import {
|
||||
removeWorkspace,
|
||||
setCurrentUser,
|
||||
setInviteUserLink,
|
||||
setActiveWorkspace,
|
||||
setWorkspace,
|
||||
setSelectedWorkspaceTab,
|
||||
setUserWorkspaces,
|
||||
setUserWorkspacesFromUser,
|
||||
logout,
|
||||
setWhitelistEntries,
|
||||
setAddWhitelistEntries,
|
||||
setRemoveWhitelistEntry,
|
||||
setFilterByUser,
|
||||
termsOfUseAccepted
|
||||
termsOfUseAccepted,
|
||||
setApiVersion,
|
||||
fetchCurrentUser,
|
||||
setCurrentUserName
|
||||
} from '../actions/users.actions';
|
||||
import {GetCurrentUserResponseUserObject} from '../../../business-logic/model/users/getCurrentUserResponseUserObject';
|
||||
import {OrganizationCreateInviteResponse} from '../../../business-logic/model/organization/organizationCreateInviteResponse';
|
||||
import {UsersGetCurrentUserResponseTermsOfUse} from '../../../business-logic/model/users/usersGetCurrentUserResponseTermsOfUse';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {OrganizationGetUserCompaniesResponseCompanies} from '../../../business-logic/model/organization/organizationGetUserCompaniesResponseCompanies';
|
||||
import {UsersGetCurrentUserResponseTermsOfUse} from '../../../business-logic/model/users/usersGetCurrentUserResponseTermsOfUse';
|
||||
import {LoginGetSettingsResponse} from '../../../business-logic/model/login/loginGetSettingsResponse';
|
||||
import {Invite} from '../../../business-logic/model/login/invite';
|
||||
import {WhitelistEntry} from '../../../business-logic/model/login/whitelistEntry';
|
||||
|
||||
export interface UsersState {
|
||||
currentUser: GetCurrentUserResponseUserObject;
|
||||
termsOfUse: UsersGetCurrentUserResponseTermsOfUse;
|
||||
inviteLink: OrganizationCreateInviteResponse;
|
||||
whitelistEntries: LoginGetSettingsResponse;
|
||||
inviteLinkId: string;
|
||||
activeWorkspace: GetCurrentUserResponseUserObjectCompany;
|
||||
userWorkspaces: OrganizationGetUserCompaniesResponseCompanies[];
|
||||
selectedWorkspaceTab: GetCurrentUserResponseUserObjectCompany;
|
||||
workspaces: GetCurrentUserResponseUserObjectCompany[];
|
||||
userWorkspaces: OrganizationGetUserCompaniesResponseCompanies[];
|
||||
termsOfUse: UsersGetCurrentUserResponseTermsOfUse;
|
||||
inviteLink: OrganizationCreateInviteResponse;
|
||||
inviteLinkId: string;
|
||||
showOnlyUserWork: boolean;
|
||||
serverVersions: { server: string; api: string };
|
||||
}
|
||||
|
||||
const initUsers: UsersState = {
|
||||
export const initUsers: UsersState = {
|
||||
currentUser: null,
|
||||
termsOfUse: null,
|
||||
inviteLink: null,
|
||||
whitelistEntries: null,
|
||||
inviteLinkId: null,
|
||||
activeWorkspace: null,
|
||||
userWorkspaces: [],
|
||||
selectedWorkspaceTab: null,
|
||||
workspaces: [],
|
||||
userWorkspaces: [],
|
||||
showOnlyUserWork: false
|
||||
termsOfUse: null,
|
||||
inviteLink: null,
|
||||
inviteLinkId: null,
|
||||
showOnlyUserWork: false,
|
||||
serverVersions: null
|
||||
};
|
||||
|
||||
export const users = state => state.users as UsersState;
|
||||
|
||||
export const selectCurrentUser = createSelector(users, (state): GetCurrentUserResponseUserObject => state.currentUser);
|
||||
export const selectWorkspaces = createSelector(users, (state): GetCurrentUserResponseUserObjectCompany[] => state.workspaces);
|
||||
export const selectActiveWorkspace = createSelector(users, (state): GetCurrentUserResponseUserObjectCompany => state.activeWorkspace);
|
||||
export const selectUserWorkspaces = createSelector(users, (state) => state.userWorkspaces);
|
||||
export const selectSelectedWorkspaceTab = createSelector(users, (state) => state.selectedWorkspaceTab);
|
||||
export const selectWorkspaces = createSelector(users, (state): GetCurrentUserResponseUserObjectCompany[] => state.workspaces);
|
||||
export const selectTermsOfUse = createSelector(users, state => state.termsOfUse);
|
||||
export const selectInviteLink = createSelector(users, state => state.inviteLink);
|
||||
export const selectShowOnlyUserWork = createSelector(users, (state): boolean => state.showOnlyUserWork);
|
||||
export const selectWhitelistEntries = createSelector(users, state => state.whitelistEntries?.whitelist_entries);
|
||||
export const selectServerVersions = createSelector(users, (state): { server: string; api: string } => state.serverVersions);
|
||||
|
||||
|
||||
export function usersReducer(state = initUsers, action) {
|
||||
|
||||
switch (action.type) {
|
||||
case USERS_ACTIONS.FETCH_CURRENT_USER:
|
||||
return {...state};
|
||||
case setInviteUserLink.type:
|
||||
return {...state, inviteLink: {id: action.id, allocated_users: action.allocated_users, allowed_users: action.allowed_users}};
|
||||
case setCurrentUser.type: {
|
||||
const lastWorkspace = window.localStorage.getItem('lastWorkspace');
|
||||
window.localStorage.removeItem('lastWorkspace');
|
||||
const workspaces = action.user ? [action.user.company, ...(action.user?.companies || [])] : [];
|
||||
const activeWorkspace = workspaces.find(workspace => workspace.id === lastWorkspace);
|
||||
const altWorkspace = workspaces.find(workspace => workspace.id === state.activeWorkspace?.id);
|
||||
return {
|
||||
...state,
|
||||
currentUser: action.user,
|
||||
termsOfUse: action.terms_of_use,
|
||||
workspaces,
|
||||
activeWorkspace: activeWorkspace || altWorkspace || action.user?.company
|
||||
};
|
||||
}
|
||||
case termsOfUseAccepted.type:
|
||||
return {...state, termsOfUse: {...state.termsOfUse, accept_required: false}};
|
||||
case logout.type:
|
||||
return {
|
||||
...state,
|
||||
currentUser: null
|
||||
};
|
||||
case setWorkspace.type: {
|
||||
const workspace = (action as ReturnType<typeof setWorkspace>).workspace;
|
||||
return {
|
||||
...state,
|
||||
workspaces: [...state.workspaces, ...(!state.workspaces.find(ws => ws.id === workspace.id) ? [action.workspace] : [])],
|
||||
activeWorkspace: action.workspace
|
||||
};
|
||||
}
|
||||
case setActiveWorkspace.type:
|
||||
return {...state, activeWorkspace: action.workspace};
|
||||
case setSelectedWorkspaceTab.type:
|
||||
return {...state, selectedWorkspaceTab: action.workspace};
|
||||
case removeWorkspace.type: {
|
||||
const workspaceId = (action as ReturnType<typeof removeWorkspace>).workspaceId;
|
||||
const workspaces = state.workspaces.filter(ws => ws.id !== workspaceId);
|
||||
return {
|
||||
...state,
|
||||
...(workspaceId === state.activeWorkspace.id && {activeWorkspace: workspaces[0]}),
|
||||
workspaces,
|
||||
userWorkspaces: state.userWorkspaces.filter(ws => ws.id !== workspaceId)
|
||||
};
|
||||
}
|
||||
case setUserWorkspaces.type:
|
||||
return {...state, userWorkspaces: (action as ReturnType<typeof setUserWorkspaces>).workspaces};
|
||||
case setUserWorkspacesFromUser.type:
|
||||
return {...state, ...(state.currentUser?.company && {userWorkspaces: [state.currentUser?.company]})};
|
||||
case setFilterByUser.type:
|
||||
return {...state, showOnlyUserWork: action.showOnlyUserWork};
|
||||
case setWhitelistEntries.type:
|
||||
return {
|
||||
...state,
|
||||
whitelistEntries: (action as ReturnType<typeof setWhitelistEntries>).whitelistEntries
|
||||
};
|
||||
case setAddWhitelistEntries.type: {
|
||||
const newWhitelistEntry: WhitelistEntry[] = (action as ReturnType<typeof setAddWhitelistEntries>).whitelistEntries.map(email => {
|
||||
return {
|
||||
email,
|
||||
status: Invite.StatusEnum.Pending
|
||||
} as WhitelistEntry;
|
||||
});
|
||||
const newWhitelistEntries = [...state.whitelistEntries.whitelist_entries, ...newWhitelistEntry];
|
||||
const whitelistEntries = {...state.whitelistEntries, whitelist_entries: newWhitelistEntries};
|
||||
return {
|
||||
...state,
|
||||
whitelistEntries
|
||||
};
|
||||
}
|
||||
case setRemoveWhitelistEntry.type: {
|
||||
const filteredWhitelistEntries = state.whitelistEntries.whitelist_entries
|
||||
.filter( (whitelistEntry: WhitelistEntry) => {
|
||||
return !(action as ReturnType<typeof setRemoveWhitelistEntry>).removed.includes(whitelistEntry.email);
|
||||
});
|
||||
const whitelistEntries = {...state.whitelistEntries, whitelist_entries: filteredWhitelistEntries};
|
||||
return {
|
||||
...state,
|
||||
whitelistEntries
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
export const usersReducerFunctions = [
|
||||
on(fetchCurrentUser, state => ({...state})),
|
||||
on(setInviteUserLink, (state, action) => ({
|
||||
...state,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
inviteLink: {id: action.id, allocated_users: action.allocated_users, allowed_users: action.allowed_users}
|
||||
})),
|
||||
on(setCurrentUserName, (state, action) => ({
|
||||
...state,
|
||||
currentUser: {...state.currentUser, name: action.name}
|
||||
})),
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
on(termsOfUseAccepted, state => ({...state, termsOfUse: {...state.termsOfUse, accept_required: false}})),
|
||||
on(logout, state => ({
|
||||
...state,
|
||||
currentUser: null
|
||||
})),
|
||||
on(setFilterByUser, (state, action) => ({...state, showOnlyUserWork: action.showOnlyUserWork})),
|
||||
on(setApiVersion, (state, action) => ({...state, serverVersions: action.serverVersions}))
|
||||
] as ReducerTypes<UsersState, any>[];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {HTTP, HTTP_ACTIONS, VIEW_ACTIONS} from '../../../app.constants';
|
||||
import {createSelector} from '@ngrx/store';
|
||||
import {get} from 'lodash/fp';
|
||||
import {firstLogin, neverShowPopupAgain, plotlyReady, setScaleFactor} from '../actions/layout.actions';
|
||||
import {createReducer, createSelector, on} from '@ngrx/store';
|
||||
import * as layoutActions from '../actions/layout.actions';
|
||||
import {apiRequest, requestFailed} from '@common/core/actions/http.actions';
|
||||
|
||||
export interface ViewState {
|
||||
loading: {[endpoint: string]: boolean};
|
||||
@@ -56,56 +55,33 @@ export const selectPlotlyReady = createSelector(views, state => state.plotlyRead
|
||||
export const selectNeverShowPopups = createSelector(views, (state): string[] => state.neverShowPopupAgain);
|
||||
|
||||
|
||||
export function viewReducer(viewState: ViewState = initViewState, action) {
|
||||
|
||||
switch (action.type) {
|
||||
case HTTP_ACTIONS.REQUEST_FAILED: {
|
||||
const isLoggedOut = action.payload.err && action.payload.err.status === 401;
|
||||
return {...viewState, loggedOut: isLoggedOut};
|
||||
}
|
||||
case VIEW_ACTIONS.DEACTIVE_LOADER:
|
||||
return {
|
||||
...viewState,
|
||||
loading: {...viewState.loading, [action.payload.endpoint]: false}
|
||||
};
|
||||
case VIEW_ACTIONS.ACTIVE_LOADER:
|
||||
return {
|
||||
...viewState,
|
||||
loading: {...viewState.loading, [action.payload.endpoint]: true}
|
||||
};
|
||||
case VIEW_ACTIONS.VISIBILITY_CHANGED:
|
||||
return {...viewState, applicationVisible: action.visible};
|
||||
case setScaleFactor.type:
|
||||
return {...viewState, scaleFactor: action.scale};
|
||||
case firstLogin.type:
|
||||
return {...viewState, firstLogin: (action as ReturnType<typeof firstLogin>).first, firstLoginAt: new Date().getTime()};
|
||||
case plotlyReady.type:
|
||||
return {...viewState, plotlyReady: true};
|
||||
case VIEW_ACTIONS.RESET_LOADER:
|
||||
return {...viewState, loading: {}};
|
||||
case HTTP.API_REQUEST_SUCCESS:
|
||||
return {
|
||||
...viewState,
|
||||
loading: {...viewState.loading, [get('payload.endpoint', action) ? action.payload.endpoint : 'default']: false}
|
||||
};
|
||||
|
||||
case HTTP.API_REQUEST:
|
||||
return {
|
||||
...viewState,
|
||||
loading: {...viewState.loading, [get('payload.endpoint', action) ? action.payload.endpoint : 'default']: true}
|
||||
};
|
||||
|
||||
case VIEW_ACTIONS.SET_NOTIFICATION_DIALOG:
|
||||
return {...viewState, notification: action.payload};
|
||||
case VIEW_ACTIONS.SET_BACKDROP:
|
||||
return {...viewState, backdropActive: action.payload};
|
||||
case VIEW_ACTIONS.SET_AUTO_REFRESH:
|
||||
return {...viewState, autoRefresh: action.payload.autoRefresh};
|
||||
case VIEW_ACTIONS.SET_COMPARE_AUTO_REFRESH:
|
||||
return {...viewState, compareAutoRefresh: action.payload.autoRefresh};
|
||||
case neverShowPopupAgain.type:
|
||||
return {...viewState, neverShowPopupAgain: action.reset? viewState.neverShowPopupAgain.filter( popups => popups !== action.popupId) : [...viewState.neverShowPopupAgain, action.popupId]};
|
||||
default:
|
||||
return viewState;
|
||||
}
|
||||
}
|
||||
export const viewReducer = createReducer(
|
||||
initViewState,
|
||||
on(requestFailed, (state, action) => {
|
||||
const isLoggedOut = action.err && action.err.status === 401;
|
||||
return {...state, loggedOut: isLoggedOut};
|
||||
}),
|
||||
on(layoutActions.deactivateLoader, (state, action) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const {[action.endpoint]: removed, ...loading} = state.loading;
|
||||
return {...state, loading};
|
||||
}),
|
||||
on(layoutActions.activeLoader, (state, action) => ({
|
||||
...state,
|
||||
loading: {...state.loading, [action.endpoint]: true}
|
||||
})),
|
||||
on(layoutActions.visibilityChanged, (state, action) => ({...state, applicationVisible: action.visible})),
|
||||
on(layoutActions.setScaleFactor, (state, action) => ({...state, scaleFactor: action.scale})),
|
||||
on(layoutActions.firstLogin, (state, action) => ({...state, firstLogin: action.first, firstLoginAt: new Date().getTime()})),
|
||||
on(layoutActions.plotlyReady, (state) => ({...state, plotlyReady: true})),
|
||||
on(layoutActions.resetLoader, (state) => ({...state, loading: {}})),
|
||||
on(apiRequest, (state, action) => ({
|
||||
...state,
|
||||
loading: {...state.loading, [action?.endpoint || 'default']: true}
|
||||
})),
|
||||
on(layoutActions.setNotificationDialog, (state, action) => ({...state, notification: action})),
|
||||
on(layoutActions.setBackdrop, (state, action) => ({...state, backdropActive: action.payload})),
|
||||
on(layoutActions.setAutoRefresh, (state, action) => ({...state, autoRefresh: action.autoRefresh})),
|
||||
on(layoutActions.setCompareAutoRefresh, (state, action) => ({...state, compareAutoRefresh: action.autoRefresh})),
|
||||
on(layoutActions.neverShowPopupAgain, (state, action) => ({...state, neverShowPopupAgain: action.reset? state.neverShowPopupAgain.filter( popups => popups !== action.popupId) : Array.from(new Set([...state.neverShowPopupAgain, action.popupId]))}))
|
||||
);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {act, Actions, Effect, ofType} from '@ngrx/effects';
|
||||
import {ActiveLoader, DeactiveLoader} from '../core/actions/layout.actions';
|
||||
import {activeLoader, deactivateLoader} from '../core/actions/layout.actions';
|
||||
import {SearchActivate, SearchClear, searchExperiments, searchModels, searchProjects, searchSetTerm, searchStart, SetExperimentsResults, SetModelsResults, SetProjectsResults} from './dashboard-search.actions';
|
||||
import {EXPERIMENT_SEARCH_ONLY_FIELDS, SEARCH_ACTIONS, SEARCH_PAGE_SIZE} from './dashboard-search.consts';
|
||||
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
|
||||
import {RequestFailed} from '../core/actions/http.actions';
|
||||
import {requestFailed} from '../core/actions/http.actions';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {selectActiveSearch} from './dashboard-search.reducer';
|
||||
import {ProjectsGetAllExRequest} from '../../business-logic/model/projects/projectsGetAllExRequest';
|
||||
@@ -29,7 +29,7 @@ export class DashboardSearchEffects {
|
||||
@Effect()
|
||||
activeLoader = this.actions.pipe(
|
||||
ofType(SEARCH_ACTIONS.SEARCH_PROJECTS, SEARCH_ACTIONS.SEARCH_MODELS, SEARCH_ACTIONS.SEARCH_EXPERIMENTS),
|
||||
map(action => new ActiveLoader(action.type))
|
||||
map(action => activeLoader(action.type))
|
||||
);
|
||||
// add actions for each search
|
||||
@Effect()
|
||||
@@ -64,8 +64,8 @@ export class DashboardSearchEffects {
|
||||
include_stats : true,
|
||||
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination']
|
||||
}).pipe(
|
||||
mergeMap(res => [new SetProjectsResults(res.projects), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new DeactiveLoader(action.type), new RequestFailed(error)])))
|
||||
mergeMap(res => [new SetProjectsResults(res.projects), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
@@ -81,8 +81,8 @@ export class DashboardSearchEffects {
|
||||
system_tags: ['-archived'],
|
||||
only_fields: ['labels', 'ready', 'created', 'framework', 'user.name', 'name', 'parent.name', 'task.name', 'id', 'company']
|
||||
}).pipe(
|
||||
mergeMap(res => [new SetModelsResults(res.models), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new DeactiveLoader(action.type), new RequestFailed(error)])))
|
||||
mergeMap(res => [new SetModelsResults(res.models), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))
|
||||
);
|
||||
|
||||
@Effect()
|
||||
@@ -99,7 +99,7 @@ export class DashboardSearchEffects {
|
||||
type : ['__$not', 'annotation_manual', '__$not', 'annotation', '__$not', 'dataset_import'],
|
||||
system_tags: ['-archived']
|
||||
}).pipe(
|
||||
mergeMap(res => [new SetExperimentsResults(res.tasks), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new DeactiveLoader(action.type), new RequestFailed(error)])))
|
||||
mergeMap(res => [new SetExperimentsResults(res.tasks), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type TaskTableColFieldsEnum = 'output.result' | 'comment' | 'id' | 'project.name' | 'name' | 'type' | 'status' | 'started' | 'last_update' | 'user.name' | 'queue.name' | 'worker.name';
|
||||
export const CARDS_IN_ROW = 4;
|
||||
export const CARDS_IN_ROW = 6;
|
||||
export const RECENT_TASKS_TABLE_COL_FIELDS = {
|
||||
RESULT : 'output.result' as TaskTableColFieldsEnum,
|
||||
COMMENT : 'comment' as TaskTableColFieldsEnum,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {ApiProjectsService} from '../../business-logic/api-services/projects.service';
|
||||
import {Actions, createEffect, Effect, ofType} from '@ngrx/effects';
|
||||
import {RequestFailed} from '../core/actions/http.actions';
|
||||
import {ActiveLoader, AddMessage, DeactiveLoader} from '../core/actions/layout.actions';
|
||||
import {Actions, createEffect, ofType} from '@ngrx/effects';
|
||||
import {requestFailed} from '../core/actions/http.actions';
|
||||
import {activeLoader, addMessage, deactivateLoader} from '../core/actions/layout.actions';
|
||||
import {CreateProjectFromDashboard, getInviteInfo, GetRecentProjects, setInviteInfo, SetRecentProjects, SetRecentTasks} from './common-dashboard.actions';
|
||||
import {CARDS_IN_ROW, DASHBOARD_ACTIONS} from './common-dashboard.const';
|
||||
import {MESSAGES_SEVERITY} from '../../app.constants';
|
||||
@@ -16,7 +16,7 @@ import {MatDialog} from '@angular/material/dialog';
|
||||
import {ConfirmDialogComponent} from '../shared/ui-components/overlay/confirm-dialog/confirm-dialog.component';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {ErrorService} from '../shared/services/error.service';
|
||||
import {selectCurrentUser, selectShowOnlyUserWork} from "../core/reducers/users-reducer";
|
||||
import {selectCurrentUser, selectShowOnlyUserWork} from '../core/reducers/users-reducer';
|
||||
|
||||
@Injectable()
|
||||
export class CommonDashboardEffects {
|
||||
@@ -26,14 +26,12 @@ export class CommonDashboardEffects {
|
||||
private dialog: MatDialog) {
|
||||
}
|
||||
|
||||
@Effect()
|
||||
activeLoader = this.actions.pipe(
|
||||
activeLoader = createEffect(() => this.actions.pipe(
|
||||
ofType(DASHBOARD_ACTIONS.GET_RECENT_PROJECTS, DASHBOARD_ACTIONS.GET_RECENT_PROJECTS, DASHBOARD_ACTIONS.GET_RECENT_TASKS, DASHBOARD_ACTIONS.CREATE_PROJECT),
|
||||
map(action => new ActiveLoader(action.type))
|
||||
);
|
||||
map(action => activeLoader(action.type))
|
||||
));
|
||||
|
||||
@Effect()
|
||||
getRecentProjects = this.actions.pipe(
|
||||
getRecentProjects = createEffect(() => this.actions.pipe(
|
||||
ofType<GetRecentProjects>(DASHBOARD_ACTIONS.GET_RECENT_PROJECTS),
|
||||
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectShowOnlyUserWork)),
|
||||
mergeMap(([action, user, showOnlyUserWork]) =>
|
||||
@@ -42,14 +40,13 @@ export class CommonDashboardEffects {
|
||||
active_users: (showOnlyUserWork ? [user.id] : null),
|
||||
only_fields: ['name', 'company', 'user', 'created', 'default_output_destination']
|
||||
}).pipe(
|
||||
mergeMap(res => [new SetRecentProjects(res.projects), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new DeactiveLoader(action.type), new RequestFailed(error)])
|
||||
mergeMap(res => [new SetRecentProjects(res.projects), deactivateLoader(action.type)]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error)])
|
||||
)
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
@Effect()
|
||||
getRecentTasks = this.actions.pipe(
|
||||
getRecentTasks = createEffect(() => this.actions.pipe(
|
||||
ofType(DASHBOARD_ACTIONS.GET_RECENT_TASKS ),
|
||||
withLatestFrom(this.store.select(selectCurrentUser), this.store.select(selectShowOnlyUserWork)),
|
||||
switchMap(([action, user, showOnlyUserWork]) => this.tasksApi.tasksGetAllEx({
|
||||
@@ -63,26 +60,25 @@ export class CommonDashboardEffects {
|
||||
user: showOnlyUserWork ? [user.id] : null,
|
||||
})
|
||||
.pipe(
|
||||
mergeMap(res => [new SetRecentTasks(res.tasks as Array<IRecentTask>), new DeactiveLoader(action.type)]),
|
||||
catchError(err => [new RequestFailed(err), new DeactiveLoader(action.type)])
|
||||
mergeMap(res => [new SetRecentTasks(res.tasks as Array<IRecentTask>), deactivateLoader(action.type)]),
|
||||
catchError(err => [requestFailed(err), deactivateLoader(action.type)])
|
||||
)
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
@Effect()
|
||||
createProject = this.actions.pipe(
|
||||
createProject = createEffect(() => this.actions.pipe(
|
||||
ofType<CreateProjectFromDashboard>(DASHBOARD_ACTIONS.CREATE_PROJECT),
|
||||
mergeMap((action) => this.projectsApi.projectsCreate(action.payload.project)
|
||||
.pipe(
|
||||
mergeMap(res => [
|
||||
new GetRecentProjects({stats_for_state: ProjectsGetAllExRequest.StatsForStateEnum.Active, order_by: ['-last_update'], page: 0, page_size: CARDS_IN_ROW}),
|
||||
new DeactiveLoader(action.type),
|
||||
new AddMessage(MESSAGES_SEVERITY.SUCCESS, 'Project Created Successfully')]),
|
||||
catchError(error => [new DeactiveLoader(action.type), new RequestFailed(error),
|
||||
new AddMessage(MESSAGES_SEVERITY.ERROR, 'Project Creation Failed')])
|
||||
deactivateLoader(action.type),
|
||||
addMessage(MESSAGES_SEVERITY.SUCCESS, 'Project Created Successfully')]),
|
||||
catchError(error => [deactivateLoader(action.type), requestFailed(error),
|
||||
addMessage(MESSAGES_SEVERITY.ERROR, 'Project Creation Failed')])
|
||||
)
|
||||
)
|
||||
);
|
||||
));
|
||||
|
||||
addWorkspace = createEffect(() => this.actions.pipe(
|
||||
ofType(addWorkspace),
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
<div class="row h-100">
|
||||
<div *ngIf="(recentTasks$ | async).length >= 0" class="col-24 h-100">
|
||||
<div class="recent-header">
|
||||
<div class="recent-title">RECENT EXPERIMENTS</div>
|
||||
<ng-content select="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<sm-recent-tasks-table [tasks]="recentTasks$ | async"
|
||||
(taskSelected)="taskSelected($event)">
|
||||
</sm-recent-tasks-table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recent-header">
|
||||
<div class="recent-title">RECENT EXPERIMENTS</div>
|
||||
<ng-content select="[header-buttons]"></ng-content>
|
||||
</div>
|
||||
<div class="table-container">
|
||||
<sm-recent-tasks-table [tasks]="recentTasks"
|
||||
(taskSelected)="taskSelected($event)">
|
||||
</sm-recent-tasks-table>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { IRecentTask, selectRecentTasks } from '../../common-dashboard.reducer';
|
||||
import { IRecentTask} from '../../common-dashboard.reducer';
|
||||
import {GetRecentTasks} from '../../common-dashboard.actions';
|
||||
import { ITask } from '../../../../business-logic/model/al-task';
|
||||
import {selectCurrentUser} from '../../../core/reducers/users-reducer';
|
||||
@@ -14,10 +13,9 @@ import {filter, take} from 'rxjs/operators';
|
||||
styleUrls: ['./dashboard-experiments.component.scss']
|
||||
})
|
||||
export class DashboardExperimentsComponent implements OnInit {
|
||||
public recentTasks$: Observable<Array<IRecentTask>>;
|
||||
@Input() recentTasks: IRecentTask[];
|
||||
|
||||
constructor(private store: Store<any>, private router: Router) {
|
||||
this.recentTasks$ = this.store.select(selectRecentTasks);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -29,7 +27,7 @@ export class DashboardExperimentsComponent implements OnInit {
|
||||
public taskSelected(task: IRecentTask | ITask) {
|
||||
// TODO ADD task.id to route
|
||||
const projectId = task.project ? task.project.id : '*';
|
||||
this.router.navigateByUrl('projects/' + projectId + '/experiments/' + task.id);
|
||||
return this.router.navigateByUrl('projects/' + projectId + '/experiments/' + task.id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
<div class="row recent-header">
|
||||
<div class="col-24 d-flex justify-content-between">
|
||||
<div class="sm-card-list-layout">
|
||||
<div #header class="sm-card-list-header">
|
||||
<div class="recent-title">RECENT PROJECTS
|
||||
<button class="btn btn-link view-all" (click)="router.navigateByUrl('/projects')">VIEW ALL</button>
|
||||
</div>
|
||||
<div *smCheckPermission="true">
|
||||
<button *ngIf="(recentProjectsList$ | async).length >= CARDS_IN_ROW"
|
||||
<div>
|
||||
<button *ngIf="(recentProjectsList$ | async).length >= cardsInRow"
|
||||
class="btn btn-primary d-flex align-items-center"
|
||||
(click)="openCreateProjectDialog()">
|
||||
<i class="al-icon al-color sm blue-400 al-ico-add mr-2"></i>NEW PROJECT
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="projects">
|
||||
<sm-project-card
|
||||
*ngFor="let project of recentProjectsList$ | async | slice:0:CARDS_IN_ROW"
|
||||
*ngFor="let project of recentProjectsList$ | async"
|
||||
[project]="project" (projectCardClicked)="projectCardClicked($event)"
|
||||
[hideMenu]="true"
|
||||
></sm-project-card>
|
||||
<sm-plus-card
|
||||
*smCheckPermission="(recentProjectsList$ | async).length < CARDS_IN_ROW"
|
||||
*ngIf="(recentProjectsList$ | async).length < cardsInRow"
|
||||
[folder]="true"
|
||||
(plusCardClick)="openCreateProjectDialog()"
|
||||
></sm-plus-card>
|
||||
|
||||
@@ -6,24 +6,17 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sm-card-list-layout {
|
||||
height: 324px;
|
||||
overflow: hidden;
|
||||
grid-gap: 16px 24px;
|
||||
padding-top: 24px;
|
||||
}
|
||||
|
||||
.sm-card-list-header {
|
||||
grid-column: 1 / -1;
|
||||
padding: 0;
|
||||
}
|
||||
.recent-title {
|
||||
@include recent-title();
|
||||
}
|
||||
|
||||
.recent-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
.projects {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
overflow: hidden;
|
||||
height: 250px;
|
||||
|
||||
sm-project-card {
|
||||
margin: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {Component, OnInit, Output, EventEmitter, AfterViewInit, ViewChild, ElementRef, OnDestroy} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {MatDialog, MatDialogRef} from '@angular/material/dialog';
|
||||
import {Observable} from 'rxjs';
|
||||
import {fromEvent, Observable, Subscription} from 'rxjs';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {Project} from '../../../../business-logic/model/projects/project';
|
||||
import {selectRecentProjects} from '../../common-dashboard.reducer';
|
||||
import {GetRecentProjects} from '../../common-dashboard.actions';
|
||||
import {CARDS_IN_ROW} from '../../common-dashboard.const';
|
||||
import {ProjectDialogComponent} from '../../../shared/project-dialog/project-dialog.component';
|
||||
import {ResetSelectedProject, SetSelectedProjectId} from '../../../core/actions/projects.actions';
|
||||
import {selectCurrentUser} from '../../../core/reducers/users-reducer';
|
||||
import {filter, take} from 'rxjs/operators';
|
||||
import {filter, take, throttleTime} from 'rxjs/operators';
|
||||
import {isExample} from '../../../shared/utils/shared-utils';
|
||||
import { CARDS_IN_ROW } from '../../common-dashboard.const';
|
||||
|
||||
@Component({
|
||||
selector : 'sm-dashboard-projects',
|
||||
templateUrl: './dashboard-projects.component.html',
|
||||
styleUrls : ['./dashboard-projects.component.scss']
|
||||
})
|
||||
export class DashboardProjectsComponent implements OnInit {
|
||||
export class DashboardProjectsComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
public recentProjectsList$: Observable<Array<Project>>;
|
||||
public CARDS_IN_ROW = CARDS_IN_ROW;
|
||||
private dialog: MatDialogRef<ProjectDialogComponent>;
|
||||
readonly cardsInRow = CARDS_IN_ROW;
|
||||
|
||||
@Output() width = new EventEmitter<number>();
|
||||
private sub: Subscription;
|
||||
constructor(private store: Store<any>, public router: Router,
|
||||
private matDialog: MatDialog) {
|
||||
this.recentProjectsList$ = this.store.select(selectRecentProjects);
|
||||
}
|
||||
|
||||
@ViewChild('header') header: ElementRef<HTMLDivElement>;
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new ResetSelectedProject());
|
||||
this.store.select(selectCurrentUser)
|
||||
@@ -35,6 +39,12 @@ export class DashboardProjectsComponent implements OnInit {
|
||||
.subscribe(() => this.store.dispatch(new GetRecentProjects()));
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
window.setTimeout(() => this.width.emit(this.header.nativeElement.getBoundingClientRect().width));
|
||||
this.sub = fromEvent(window, 'resize')
|
||||
.pipe(throttleTime(50))
|
||||
.subscribe(() => this.width.emit(this.header.nativeElement.getBoundingClientRect().width));
|
||||
}
|
||||
public projectCardClicked(project: Project) {
|
||||
this.router.navigateByUrl(`projects/${project.id}`);
|
||||
this.store.dispatch(new SetSelectedProjectId(project.id, isExample(project)));
|
||||
@@ -52,4 +62,8 @@ export class DashboardProjectsComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export abstract class DashboardSearchComponentBase {
|
||||
this.store.dispatch(new InitSearch('Search for all'));
|
||||
|
||||
this.searchSubs = this.searchQuery$
|
||||
.pipe(skip(1), filter(query => !!query?.query))
|
||||
.pipe(skip(1))
|
||||
.subscribe(query => {
|
||||
this.searchTermChanged(query.query, query.regExp);
|
||||
});
|
||||
@@ -87,7 +87,7 @@ export abstract class DashboardSearchComponentBase {
|
||||
this.activeLink = activeLink;
|
||||
}
|
||||
|
||||
setFirstActiveLink(allResults, tabsIndexes) {
|
||||
setFirstActiveLink(allResults, tabsIndexes: string[]) {
|
||||
if (!(allResults[tabsIndexes.indexOf(this.activeLink)].length > 0)) {
|
||||
const firstTabIndex = allResults.findIndex(list => list.length > 0);
|
||||
if (firstTabIndex > -1) {
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<ng-template #configStep>
|
||||
<div class="steps-content">
|
||||
<div class="text" *ngIf="queue">
|
||||
Tasks have been enqueued on the {{queue?.name}} queue, which is currently not serviced by any worker. They will remain in the 'pending' state until a ClearML worker services this queue.
|
||||
Tasks have been enqueued on the <b>{{queue?.name}}</b> queue, which is currently not serviced by any worker. They will remain in the 'pending' state until a ClearML worker services this queue.
|
||||
</div>
|
||||
<div *ngFor="let step of steps" class="step-container">
|
||||
<div class="step-header">{{step.header}}</div>
|
||||
@@ -79,7 +79,7 @@
|
||||
<div class="step">3. Integrate</div>
|
||||
<div class="step sub-note">Add the following lines to your code</div>
|
||||
<div class="code">
|
||||
<div #content class="content"><span class="variable">from</span> clearml <span class="variable">import</span> Task
|
||||
<div #content class="content"><span class="variable">from</span> {{gettingStartedContext?.agentName || 'clearml'}} <span class="variable">import</span> Task
|
||||
task <span class="operation">=</span> Task.<span class="variable">init</span>(project_name<span class="operation">=</span>"my project", task_name<span class="operation">=</span>"my task")</div>
|
||||
<sm-copy-clipboard
|
||||
[hideBackground]="true"
|
||||
|
||||
@@ -89,13 +89,13 @@
|
||||
|
||||
.code {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
|
||||
margin: 1px 0 2px;
|
||||
width: 530px;
|
||||
color: $blue-100;
|
||||
background-color: $blue-900;
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
|
||||
|
||||
&:hover {
|
||||
sm-copy-clipboard {
|
||||
@@ -104,6 +104,9 @@
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
margin-right: 16px;
|
||||
white-space: pre;
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 12px;
|
||||
@@ -131,6 +134,7 @@
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import {AdminService} from '../../../../features/admin/admin.service';
|
||||
import {ConfigurationService} from '../../../shared/services/configuration.service';
|
||||
import {GetCurrentUserResponseUserObjectCompany} from '../../../../business-logic/model/users/getCurrentUserResponseUserObjectCompany';
|
||||
import {Queue} from "../../../../business-logic/model/queues/queue";
|
||||
import {GettingStartedContext} from '../../../../../environments/base';
|
||||
|
||||
type StepObject = { header?: string; title?: string; code?: string; subNote?: string };
|
||||
|
||||
@@ -25,6 +26,7 @@ export class WelcomeMessageComponent implements OnInit, OnDestroy {
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
creatingCredentials = false;
|
||||
|
||||
private workspacesSub: Subscription;
|
||||
public workspace: GetCurrentUserResponseUserObjectCompany;
|
||||
private newCredentialSub: Subscription;
|
||||
@@ -50,7 +52,7 @@ export class WelcomeMessageComponent implements OnInit, OnDestroy {
|
||||
code: 'pip install clearml-agent',
|
||||
}, {
|
||||
title: '2. Configure',
|
||||
code: 'clear-agent init'
|
||||
code: 'clearml-agent init'
|
||||
}
|
||||
];
|
||||
host: string;
|
||||
@@ -58,6 +60,8 @@ export class WelcomeMessageComponent implements OnInit, OnDestroy {
|
||||
public queue: Queue;
|
||||
steps: StepObject[];
|
||||
doNotShowAgain: boolean;
|
||||
private agentName: string;
|
||||
public gettingStartedContext: GettingStartedContext;
|
||||
|
||||
constructor(
|
||||
private store: Store<any>,
|
||||
@@ -69,10 +73,15 @@ export class WelcomeMessageComponent implements OnInit, OnDestroy {
|
||||
this.dialogRef.beforeClosed().subscribe(() => this.dialogRef.close(this.doNotShowAgain));
|
||||
this.step = data?.step || this.step;
|
||||
this.queue = data?.queue;
|
||||
this.gettingStartedContext = this.configService.getStaticEnvironment().gettingStartedContext;
|
||||
|
||||
this.steps = this.queue ? this.ORPHANED_QUEUE_STEPS : this.GETTING_STARTED_STEPS;
|
||||
if (this.queue) {
|
||||
this.steps[0].code = `clearml-agent daemon —-queue ${this.queue.name}`;
|
||||
this.steps[0].code = `clearml-agent daemon --queue ${this.queue.name}`;
|
||||
this.steps[0].header = `To assign a worker to the ${this.queue.name} queue, run:`;
|
||||
} else if (this.gettingStartedContext) {
|
||||
this.steps[0].code = this.gettingStartedContext.install;
|
||||
this.steps[1].code = this.gettingStartedContext.configure;
|
||||
}
|
||||
this.host = `${window.location.protocol}//${window.location.hostname}`;
|
||||
if (this.API_BASE_URL === '/api') {
|
||||
|
||||
@@ -2,7 +2,7 @@ import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} f
|
||||
import {isHtmlPage, isTextFileURL} from '../../shared/utils/shared-utils';
|
||||
import {IsAudioPipe} from '../../shared/pipes/is-audio.pipe';
|
||||
import {IsVideoPipe} from '../../shared/pipes/is-video.pipe';
|
||||
import {AddMessage} from '../../core/actions/layout.actions';
|
||||
import {addMessage} from '../../core/actions/layout.actions';
|
||||
import {MESSAGES_SEVERITY} from '../../../app.constants';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {ThemeEnum} from '../../experiments/shared/common-experiments.const';
|
||||
@@ -55,7 +55,7 @@ export class DebugImageSnippetComponent implements OnInit {
|
||||
}
|
||||
|
||||
copyToClipboardSuccess(success: boolean) {
|
||||
this.store.dispatch(new AddMessage(
|
||||
this.store.dispatch(addMessage(
|
||||
success ? MESSAGES_SEVERITY.SUCCESS : MESSAGES_SEVERITY.ERROR,
|
||||
success ? 'Path copied to clipboard' : 'No path to copy'
|
||||
));
|
||||
|
||||
@@ -2,13 +2,13 @@ import {Injectable} from '@angular/core';
|
||||
import {Actions, Effect, ofType} from '@ngrx/effects';
|
||||
import {catchError, mergeMap, map, switchMap, withLatestFrom} from 'rxjs/operators';
|
||||
import * as debugActions from './debug-images-actions';
|
||||
import {ActiveLoader, DeactiveLoader} from '../core/actions/layout.actions';
|
||||
import {activeLoader, deactivateLoader} from '../core/actions/layout.actions';
|
||||
import {ApiTasksService} from '../../business-logic/api-services/tasks.service';
|
||||
import {ApiEventsService} from '../../business-logic/api-services/events.service';
|
||||
import {RequestFailed} from '../core/actions/http.actions';
|
||||
import {REFRESH_EXPERIMENTS} from '../experiments/actions/common-experiments-view.actions';
|
||||
import {requestFailed} from '../core/actions/http.actions';
|
||||
import {refreshExperiments} from '../experiments/actions/common-experiments-view.actions';
|
||||
import {setRefreshing} from '../experiments-compare/actions/compare-header.actions';
|
||||
import {Store} from '@ngrx/store';
|
||||
import {Action, Store} from '@ngrx/store';
|
||||
import {selectDebugImages, selectImageViewerScrollId} from './debug-images-reducer';
|
||||
import {
|
||||
setCurrentDebugImage,
|
||||
@@ -42,7 +42,7 @@ export class DebugImagesEffects {
|
||||
@Effect()
|
||||
activeLoader = this.actions$.pipe(
|
||||
ofType(debugActions.FETCH_EXPERIMENTS),
|
||||
map(action => new ActiveLoader(action.type))
|
||||
map(action => activeLoader(action.type))
|
||||
);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class DebugImagesEffects {
|
||||
})
|
||||
.pipe(
|
||||
mergeMap((res: any) => {
|
||||
const actionsToShoot = [new DeactiveLoader(action.type), setRefreshing({payload: false})];
|
||||
const actionsToShoot = [deactivateLoader(action.type), setRefreshing({payload: false})] as Action[];
|
||||
if (res.metrics[0].iterations && res.metrics[0].iterations.length > 0) {
|
||||
actionsToShoot.push(new debugActions.SetDebugImages({res, task: action.payload.task}));
|
||||
switch (action.type) {
|
||||
@@ -94,10 +94,10 @@ export class DebugImagesEffects {
|
||||
return actionsToShoot;
|
||||
}),
|
||||
catchError(error => [
|
||||
new RequestFailed(error),
|
||||
requestFailed(error),
|
||||
setRefreshing({payload: false}),
|
||||
new DeactiveLoader(action.type),
|
||||
new DeactiveLoader(REFRESH_EXPERIMENTS)
|
||||
deactivateLoader(action.type),
|
||||
deactivateLoader(refreshExperiments.type)
|
||||
])
|
||||
)
|
||||
)
|
||||
@@ -108,8 +108,8 @@ export class DebugImagesEffects {
|
||||
ofType<debugActions.FetchExperiments>(debugActions.FETCH_EXPERIMENTS),
|
||||
switchMap((action) => this.apiTasks.tasksGetAllEx({id: action.payload, only_fields: ['id', 'name', 'status']})
|
||||
.pipe(
|
||||
mergeMap(res => [new debugActions.SetExperimentsNames(res), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new RequestFailed(error), new DeactiveLoader(action.type)])
|
||||
mergeMap(res => [new debugActions.SetExperimentsNames(res), deactivateLoader(action.type)]),
|
||||
catchError(error => [requestFailed(error), deactivateLoader(action.type)])
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -123,8 +123,8 @@ export class DebugImagesEffects {
|
||||
event_type: 'training_debug_image'
|
||||
})
|
||||
.pipe(
|
||||
mergeMap(res => [new debugActions.SetMetrics(res), new DeactiveLoader(action.type)]),
|
||||
catchError(error => [new RequestFailed(error), new DeactiveLoader(action.type)])
|
||||
mergeMap(res => [new debugActions.SetMetrics(res), deactivateLoader(action.type)]),
|
||||
catchError(error => [requestFailed(error), deactivateLoader(action.type)])
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -144,10 +144,10 @@ export class DebugImagesEffects {
|
||||
.pipe(
|
||||
mergeMap(res => [
|
||||
setDebugImageIterations({min_iteration: res.min_iteration, max_iteration: res.max_iteration}),
|
||||
setCurrentDebugImage({event: res.event}), new DeactiveLoader(action.type),
|
||||
setCurrentDebugImage({event: res.event}), deactivateLoader(action.type),
|
||||
setDebugImageViewerScrollId({scrollId: res.scroll_id}),
|
||||
]),
|
||||
catchError(error => [new RequestFailed(error), new DeactiveLoader(action.type)])
|
||||
catchError(error => [requestFailed(error), deactivateLoader(action.type)])
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -169,13 +169,13 @@ export class DebugImagesEffects {
|
||||
} else {
|
||||
return [
|
||||
setDebugImageIterations({min_iteration: res.min_iteration, max_iteration: res.max_iteration}),
|
||||
setCurrentDebugImage({event: res.event}), new DeactiveLoader(action.type),
|
||||
setCurrentDebugImage({event: res.event}), deactivateLoader(action.type),
|
||||
setDebugImageViewerScrollId({scrollId: res.scroll_id}),
|
||||
action.navigateEarlier ? setDisplayerBeginningOfTime({beginningOfTime: false}) : setDisplayerEndOfTime({endOfTime: false})
|
||||
];
|
||||
}
|
||||
}),
|
||||
catchError(error => [new RequestFailed(error), new DeactiveLoader(action.type)])
|
||||
catchError(error => [requestFailed(error), deactivateLoader(action.type)])
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<mat-expansion-panel *ngFor="let iteration of iterations; let i = index; trackBy:trackKey"
|
||||
class="images-section" togglePosition="before" [expanded]="i===0">
|
||||
class="images-section" [class.dark-theme]="isDarkTheme" togglePosition="before" [expanded]="i===0">
|
||||
<mat-expansion-panel-header class="debug-header" [collapsedHeight]="null">
|
||||
<mat-panel-title> {{iteration.iter}}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-template matExpansionPanelContent>
|
||||
<div class="d-flex justify-content flex-wrap">
|
||||
<sm-debug-image-snippet [frame]="frame"
|
||||
*ngFor="let frame of iteration.events; let i = index; trackBy:trackFrame"
|
||||
(imageError)="imageUrlError({frame: frame, experimentId: experimentId})"
|
||||
(imageClicked)="imageClicked.emit({frame: frame, snippetKey: frame.key, frames: allIterationsEvents})">
|
||||
<sm-debug-image-snippet
|
||||
*ngFor="let frame of iteration.events; let i = index; trackBy:trackFrame"
|
||||
[frame]="frame"
|
||||
(imageError)="imageUrlError({frame: frame, experimentId: experimentId})"
|
||||
(imageClicked)="imageClicked.emit({frame: frame, snippetKey: frame.key, frames: allIterationsEvents})">
|
||||
</sm-debug-image-snippet>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
background:transparent;
|
||||
}
|
||||
|
||||
|
||||
::ng-deep .mat-expansion-panel-content {
|
||||
.mat-expansion-panel-body {
|
||||
padding: 0 12px 48px 12px;
|
||||
@@ -78,7 +79,7 @@
|
||||
&:hover {
|
||||
|
||||
.mat-expansion-panel-header-title {
|
||||
color: rgba(56, 65, 97, 1)
|
||||
color: rgba(56, 65, 97, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +95,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.mat-expansion-panel {
|
||||
&.dark-theme {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ export class DebugImagesViewComponent {
|
||||
@Input() isMergeIterations;
|
||||
@Input() title;
|
||||
@Input() iterations;
|
||||
@Input() isDarkTheme = false;
|
||||
@Output() imageClicked = new EventEmitter();
|
||||
@Output() refreshClicked = new EventEmitter();
|
||||
@Output() urlError = new EventEmitter();
|
||||
@@ -22,7 +23,7 @@ export class DebugImagesViewComponent {
|
||||
public imageUrlError(data: { frame: string; experimentId: string }) {
|
||||
this.urlError.emit(data);
|
||||
}
|
||||
get allIterationsEvents (){
|
||||
get allIterationsEvents(){
|
||||
const iterationEvents = [];
|
||||
this.iterations.forEach(iteration=> iterationEvents.push(iteration.events));
|
||||
return iterationEvents;
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<div class="d-flex">
|
||||
<div class="metric-bar" [class.minimized]="minimized" *ngIf="!thereAreNoMetrics(experimentId)">
|
||||
<label>Metric:</label>
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-select #metricSelect (selectionChange)="selectMetric($event, experimentId)" panelClass="light-theme"
|
||||
<mat-form-field appearance="outline" [ngClass]="{'dark thin': isDarkTheme}">
|
||||
<mat-select #metricSelect (selectionChange)="selectMetric($event, experimentId)" [panelClass]="isDarkTheme ? 'dark black dark-theme': 'light-theme'"
|
||||
[value]="selectedMetrics[experimentId]" name="selectedMetric">
|
||||
<mat-option *ngIf="selectedMetrics[experimentId]" [value]="ALL_IMAGES">{{ALL_IMAGES}}</mat-option>
|
||||
<mat-option *ngFor="let metric of optionalMetrics[experimentId]" [value]="metric">
|
||||
@@ -26,7 +26,7 @@
|
||||
smTooltip="Older images"></div>
|
||||
|
||||
<b
|
||||
class="text-right">{{debugImages && debugImages[experimentId] && debugImages[experimentId][0][debugImages[experimentId].length - 1].iter}}</b>
|
||||
class="text-right">{{debugImages && debugImages[experimentId] && debugImages[experimentId][0][debugImages[experimentId][0].length - 1].iter}}</b>
|
||||
|
||||
<div class="al-icon al-ico-between al-color light-blue-grey"></div>
|
||||
|
||||
@@ -43,14 +43,18 @@
|
||||
smTooltip="Newest images"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="no-images icon i-no-debug-samples-with-text"
|
||||
*ngIf="shouldShowNoImagesForExperiment(experimentId)"></div>
|
||||
<div class="no-images no-output" [class.dark]="isDarkTheme" *ngIf="shouldShowNoImagesForExperiment(experimentId)">
|
||||
<svg class="mb-3 w-100" xmlns="http://www.w3.org/2000/svg" width="300" height="150" viewBox="0 0 300 150">
|
||||
<path opacity="0.1" d="M72.67,79.36a5.39,5.39,0,0,1-1.45,4.32,1.17,1.17,0,0,1-.6.34,1.13,1.13,0,0,1-1.31-.88,1.11,1.11,0,0,1,.28-1,3.19,3.19,0,0,0,.89-2.36c-.22-1.09-1.66-1.73-1.68-1.74A1.12,1.12,0,0,1,69.65,76C69.76,76,72.22,77.07,72.67,79.36ZM46.19,78.1a1.38,1.38,0,0,0-1.06,1.61L47.3,90.38a1.38,1.38,0,0,0,1.61,1.06l4-.82L50.19,77.29Zm30.87.39c-.91-4.54-5.23-7.07-5.41-7.18a1.13,1.13,0,1,0-1.16,1.94s3.63,2.13,4.34,5.68-1.8,7-1.82,7a1.12,1.12,0,0,0,.25,1.56,1.11,1.11,0,0,0,.86.2,1.12,1.12,0,0,0,.68-.43C75,87.09,78,83,77.06,78.49Zm-3.57-12a1.12,1.12,0,0,0-1,2,12.48,12.48,0,0,1,2.91,2.24,13.87,13.87,0,0,1,3.88,7.28,14.19,14.19,0,0,1-.78,8.27,13,13,0,0,1-1.83,3.22,1.11,1.11,0,0,0,1.06,1.82h0a1,1,0,0,0,.63-.36,16.47,16.47,0,0,0,3.13-13.38A16.18,16.18,0,0,0,73.49,66.53Zm-9.28,1a1.35,1.35,0,0,0-1.6-1.06h0a1.45,1.45,0,0,0-.71.4L52.2,76.93l2.7,13.24,12.82,5.52a1.36,1.36,0,0,0,1.79-.7,1.33,1.33,0,0,0,.09-.81ZM261.4,76.66l-3.77,21.62-8.28-8.4-.47,2.7a4.13,4.13,0,0,1-4.76,3.35l-25.67-4.48a4.14,4.14,0,0,1-3.35-4.76l2.36-13.51c0-.12,0-.24.08-.35a6.17,6.17,0,1,1,10-3.71,5.62,5.62,0,0,1-.5,1.55l4.49.79a6.37,6.37,0,0,1,.06-1.63,6.15,6.15,0,1,1,11.63,3.65l4.63.81a4,4,0,0,1,3.4,4.53c0,.08,0,.15-.05.23l-.48,2.7ZM225.28,68.1a3.7,3.7,0,0,0-3.52-3.87h-.19a3.79,3.79,0,1,0,3.71,3.87Zm16.29,3A3.7,3.7,0,0,0,238,67.24h-.19a3.79,3.79,0,1,0,3.72,3.86Zm53.2-20.19L281,129.18a17.25,17.25,0,0,1-20,14l-78.27-13.81a17,17,0,0,1-11.13-7.08,17.86,17.86,0,0,1-1-1.69H130.44a17.86,17.86,0,0,1-1,1.69,17,17,0,0,1-11.13,7.08L40.09,143.16a17.25,17.25,0,0,1-20-14L6.26,50.86a17.26,17.26,0,0,1,14-20h0L94.63,17.77A17.24,17.24,0,0,1,110.76,6.58h79.47a17.24,17.24,0,0,1,16.13,11.19l74.38,13.11a17.25,17.25,0,0,1,14,20ZM120.22,119.58h-9.47a17.21,17.21,0,0,1-17.19-17.19V25.1L21.81,37.75a9.55,9.55,0,0,0-7.74,11.06l13.76,78.06a9.57,9.57,0,0,0,11.06,7.71L117,120.81A9.68,9.68,0,0,0,120.22,119.58Zm69.82-7a9.54,9.54,0,0,0,9.52-9.52V24.13a9.53,9.53,0,0,0-9.5-9.55h-79a9.51,9.51,0,0,0-9.51,9.51v79a9.53,9.53,0,0,0,9.51,9.52Zm95.52-70.94a9.49,9.49,0,0,0-6.17-3.93L207.56,25.1v77.33a17.23,17.23,0,0,1-17.21,17.15h-9.49a9.66,9.66,0,0,0,3.28,1.23l78.15,13.77a9.58,9.58,0,0,0,11.07-7.75l13.78-78.06a9.44,9.44,0,0,0-1.58-7.09Zm-109,.43c1.09.64,1,2.34,1,2.34l-.14,37.87a3.33,3.33,0,0,1-3.36,3.3h0l-48.13-.16a3.37,3.37,0,0,1-3.37-3.35l.2-37.39a3.3,3.3,0,0,1,2.11-3l.18-.08h49.68s1.76-.15,1.83.53ZM160.49,53.34a3.8,3.8,0,0,0,7.59,0,3.7,3.7,0,0,0-3.67-3.76h-.12A3.79,3.79,0,0,0,160.49,53.34ZM171.6,74.93l-8.55-9.46-.19-.19a2.49,2.49,0,0,0-3.52.16v0l-4.2,4.63-9.19-10.63a3.05,3.05,0,0,0-1.59-1,2.75,2.75,0,0,0-2.66.95h0l-13.13,15v4.13l43,.14Z"/></svg>
|
||||
<h3>NO DEBUG SAMPLES</h3>
|
||||
</div>
|
||||
<sm-debug-images-view
|
||||
*ngFor="let debugImages of debugImages[experimentId]"
|
||||
[iterations]="debugImages"
|
||||
[experimentId]="experimentId"
|
||||
[title]="taskNames && taskNames[experimentId]"
|
||||
[isMergeIterations]="mergeIterations"
|
||||
[isDarkTheme]="isDarkTheme"
|
||||
(imageClicked)="imageClicked($event)"
|
||||
(urlError)="urlError($event)"
|
||||
>
|
||||
|
||||
@@ -15,12 +15,25 @@ sm-debug-images-view {
|
||||
}
|
||||
|
||||
.no-images {
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
display: block;
|
||||
color: $cloudy-blue-two;
|
||||
width: 100%;
|
||||
background-repeat: repeat-y;
|
||||
background-position: center calc(50vh - 300px);
|
||||
background-size: unset;
|
||||
font-size: 1.75rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
|
||||
&.dark {
|
||||
color: $blue-250;
|
||||
font-size: 12px;
|
||||
path {
|
||||
opacity: 1;
|
||||
fill: $dark-grey-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.metric-bar {
|
||||
@@ -56,6 +69,7 @@ sm-debug-images-view {
|
||||
.single-debug-images-container {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 640px;
|
||||
min-height: 100%;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {ChangeDetectorRef, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {IExperimentInfoState} from '../../features/experiments/reducers/experiment-info.reducer';
|
||||
@@ -35,6 +35,7 @@ import {MatSelectChange} from '@angular/material/select';
|
||||
})
|
||||
export class DebugImagesComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() isDarkTheme = false;
|
||||
private debugImagesSubscription: Subscription;
|
||||
private taskNamesSubscription: Subscription;
|
||||
private selectedExperimentSubscription: Subscription;
|
||||
@@ -91,10 +92,8 @@ export class DebugImagesComponent implements OnInit, OnDestroy {
|
||||
const debugImagesP = Object.entries(debugImages).reduce(((previousValue, currentValue: any) => {
|
||||
previousValue[currentValue[0]] = currentValue[1].metrics.map(metric => metric.iterations.map(iteration => {
|
||||
const events = iteration.events.map(event => {
|
||||
const signedUrl = this.adminService.signUrlIfNeeded(event.url);
|
||||
const parsed = new URL(signedUrl);
|
||||
parsed.searchParams.append('X-Amz-Date', event.timestamp);
|
||||
return {...event, oldSrc: event.url, url: parsed.toString(), variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : ''};
|
||||
const url = this.adminService.signUrlIfNeeded(event.url, {disableCache: event.timestamp});
|
||||
return {...event, oldSrc: event.url, url, variantAndMetric: this.selectedMetric === ALL_IMAGES ? `${event.metric}/${event.variant}` : ''};
|
||||
});
|
||||
return {...iteration, events};
|
||||
}));
|
||||
|
||||
@@ -13,6 +13,7 @@ import { debugImagesReducer } from './debug-images-reducer';
|
||||
import { DebugImagesViewComponent } from './debug-images-view/debug-images-view.component';
|
||||
import { DebugImagesComponent } from './debug-images.component';
|
||||
import {MatSliderModule} from "@angular/material/slider";
|
||||
import {ExperimentGraphsModule} from "../shared/experiment-graphs/experiment-graphs.module";
|
||||
|
||||
const declerations = [DebugImagesComponent, DebugImagesViewComponent, ImageDisplayerComponent, DebugImageSnippetComponent];
|
||||
|
||||
@@ -27,7 +28,8 @@ const declerations = [DebugImagesComponent, DebugImagesViewComponent, ImageDispl
|
||||
ScrollingModule,
|
||||
StoreModule.forFeature('debugImages', debugImagesReducer),
|
||||
EffectsModule.forFeature([DebugImagesEffects]),
|
||||
MatSliderModule
|
||||
MatSliderModule,
|
||||
ExperimentGraphsModule
|
||||
]
|
||||
})
|
||||
export class DebugImagesModule {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {treeBuilderService} from '../services/tree-builder.service';
|
||||
import {createDiffObjectDetails} from '../jsonToDiffConvertor';
|
||||
import {ExperimentParams, TreeNode, TreeNodeMetadata} from '../shared/experiments-compare-details.model';
|
||||
import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree';
|
||||
import {ActiveLoader, AddMessage, DeactiveLoader} from '../../core/actions/layout.actions';
|
||||
import {activeLoader, addMessage, deactivateLoader} from '../../core/actions/layout.actions';
|
||||
import { ChangeDetectorRef, OnDestroy, QueryList, ViewChildren, Directive } from '@angular/core';
|
||||
import {ExperimentCompareDetailsBase} from '../../../features/experiments-compare/experiments-compare-details.base';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
@@ -20,8 +20,9 @@ import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
|
||||
import {selectHideIdenticalFields, selectRefreshing} from '../reducers';
|
||||
import {refetchExperimentRequested} from '../actions/compare-header.actions';
|
||||
import {RENAME_MAP} from '../experiments-compare.constants';
|
||||
import {selectHasDataFeature} from '../../../core/reducers/users.reducer';
|
||||
|
||||
export type nextDiffDirectionEnum = 'down' | 'up';
|
||||
export type NextDiffDirectionEnum = 'down' | 'up';
|
||||
|
||||
export interface FlatNode {
|
||||
data: any;
|
||||
@@ -33,10 +34,10 @@ export interface FlatNode {
|
||||
|
||||
@Directive()
|
||||
export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase implements OnDestroy {
|
||||
public hasDataFeature$: Observable<boolean>;
|
||||
private hasDataFeature: boolean;
|
||||
|
||||
abstract buildCompareTree(experiments);
|
||||
|
||||
public RENAME_MAP = RENAME_MAP;
|
||||
public renameMap = RENAME_MAP;
|
||||
public experiments$: Observable<any[]>;
|
||||
public taskIds$: Observable<string>;
|
||||
|
||||
@@ -81,6 +82,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
public changeDetection: ChangeDetectorRef,
|
||||
public activeRoute: ActivatedRoute) {
|
||||
super();
|
||||
this.hasDataFeature$ = this.store.pipe(select(selectHasDataFeature));
|
||||
|
||||
this.taskIds$ = this.store.pipe(
|
||||
select(selectRouterParams),
|
||||
@@ -111,6 +113,9 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
this.refreshingSubscription = this.store.pipe(select(selectRefreshing))
|
||||
.pipe(filter(({refreshing}) => refreshing))
|
||||
.subscribe(({autoRefresh}) => this.store.dispatch(refetchExperimentRequested({autoRefresh})));
|
||||
|
||||
this.hideIdenticalFieldsSub.add(this.hasDataFeature$.subscribe( hasData => this.hasDataFeature = hasData));
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@@ -124,13 +129,13 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
|
||||
calculateTree(experiments) {
|
||||
this.calculatingTree = true;
|
||||
this.store.dispatch(new ActiveLoader('CALCULATING_DIFF_TREE'));
|
||||
this.store.dispatch(activeLoader('CALCULATING_DIFF_TREE'));
|
||||
this.changeDetection.detectChanges();
|
||||
|
||||
setTimeout(() => {
|
||||
const experimentTrees = this.buildCompareTree(experiments);
|
||||
const experimentTrees = this.buildCompareTree(experiments, this.hasDataFeature);
|
||||
this.tree = experimentTrees;
|
||||
this.ClearRemovedExperiment(experiments);
|
||||
this.clearRemovedExperiment(experiments);
|
||||
|
||||
const treeFlattener = new MatTreeFlattener<TreeNode<any>, FlatNode>(
|
||||
this.nodeTransformer,
|
||||
@@ -157,7 +162,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
this.selectedPath && window.setTimeout(() => this.exapndAndScrollToPath());
|
||||
});
|
||||
this.calculatingTree = false;
|
||||
this.store.dispatch(new DeactiveLoader('CALCULATING_DIFF_TREE'));
|
||||
this.store.dispatch(deactivateLoader('CALCULATING_DIFF_TREE'));
|
||||
if (!this.changeDetection['destroyed']) {
|
||||
this.changeDetection.detectChanges();
|
||||
}
|
||||
@@ -186,13 +191,13 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
|
||||
toggleNode(node) {
|
||||
Object.keys(this.experimentsDataControl).forEach(id => {
|
||||
const [dataSource, treeControl] = this.experimentsDataControl[id];
|
||||
const n = treeControl.dataNodes.filter(n => n.hasChildren).find(n => n.data.path === node.data.path);
|
||||
const [, treeControl] = this.experimentsDataControl[id];
|
||||
const n = treeControl.dataNodes.filter(n1 => n1.hasChildren).find(n2 => n2.data.path === node.data.path);
|
||||
treeControl.toggle(n);
|
||||
});
|
||||
}
|
||||
|
||||
ClearRemovedExperiment(experiments) {
|
||||
clearRemovedExperiment(experiments) {
|
||||
const expIds = experiments.map(exp => exp.id);
|
||||
Object.keys(this.experimentsDataControl).forEach(expId => {
|
||||
if (!expIds.includes(expId)) {
|
||||
@@ -235,7 +240,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
);
|
||||
}
|
||||
|
||||
goToNextDiff(direction: nextDiffDirectionEnum) {
|
||||
goToNextDiff(direction: NextDiffDirectionEnum) {
|
||||
if (direction === 'down') {
|
||||
this.selectedPathIndex = this.onlyDiffsPaths.length - 1 > this.selectedPathIndex ? this.selectedPathIndex + 1 : 0;
|
||||
} else if (this.selectedPathIndex > 0) {
|
||||
@@ -262,7 +267,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
let nodeGotExpanded = false;
|
||||
if (!isEqual(openPaths.slice(0, openPaths.length - 1), this.previousOpenPaths)) {
|
||||
Object.keys(this.experimentsDataControl).forEach(id => {
|
||||
const [dataSource, treeControl] = this.experimentsDataControl[id];
|
||||
const [, treeControl] = this.experimentsDataControl[id];
|
||||
const nodesToOpen = treeControl.dataNodes.filter(node => node.hasChildren).filter(n => {
|
||||
const currentPath = n.data.path;
|
||||
return !treeControl.isExpanded(n) && openPaths.includes(currentPath);
|
||||
@@ -274,7 +279,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
});
|
||||
}
|
||||
this.previousOpenPaths = openPaths.slice(0, openPaths.length - 1);
|
||||
const [dataSource, treeControl] = Object.values(this.experimentsDataControl)[0];
|
||||
const [dataSource,] = Object.values(this.experimentsDataControl)[0];
|
||||
const selectedNodeIndex = this.findRealIndex(dataSource);
|
||||
const scrollToInPixels = (selectedNodeIndex + 1) * 28 - this.virtualScrollRef.first.getViewportSize() / 2;
|
||||
if (nodeGotExpanded) {
|
||||
@@ -346,7 +351,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
{[section]: mergedExperiment[section]},
|
||||
this.dataTransformer,
|
||||
this.metaDataTransformer,
|
||||
{experiment: experiment}
|
||||
{experiment}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -428,7 +433,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
}
|
||||
}
|
||||
|
||||
keyClicked(data, event: MouseEvent) {
|
||||
keyClicked(data) {
|
||||
const path = data.path;
|
||||
this.selectedPathClicked(path);
|
||||
}
|
||||
@@ -467,7 +472,7 @@ export abstract class ExperimentCompareBase extends ExperimentCompareDetailsBase
|
||||
}
|
||||
|
||||
copyIdToClipboard() {
|
||||
this.store.dispatch(new AddMessage('success', 'Copied to clipboard'));
|
||||
this.store.dispatch(addMessage('success', 'Copied to clipboard'));
|
||||
}
|
||||
|
||||
public resetComponentState(experiments) {
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
class="title-key"
|
||||
[class.ellipsis]="showEllipsis"
|
||||
[style.width.px]="showEllipsis ? nativeWidth - 45 - node.level * 20 : null"
|
||||
>{{(RENAME_MAP[node.data.key] || node.data.key) | hideHashTitle}}</span>
|
||||
>{{(renameMap[node.data.key] || node.data.key) | hideHashTitle}}</span>
|
||||
<i *ngIf="node.metaData.tooltip" class="al-icon sm al-ico-description node-icon" customClass="hyper-parameters-tooltip" [smTooltip]="node.metaData.tooltip"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data, $event)">
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)">
|
||||
<div [style.padding-left.px]="2 + node.level * 20" [ngClass]="{
|
||||
'node-item-container': true,
|
||||
'identical-row': checkIfIdenticalRow(node.data),
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
Component,
|
||||
ElementRef, HostListener,
|
||||
OnInit, QueryList,
|
||||
ViewChild,
|
||||
ViewChildren
|
||||
} from '@angular/core';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
@@ -59,7 +58,6 @@ export class ExperimentCompareDetailsComponent extends ExperimentCompareBase imp
|
||||
|
||||
ngOnInit() {
|
||||
this.onInit();
|
||||
|
||||
this.routerParamsSubscription = this.taskIds$.subscribe((experimentIds: string) => this.store.dispatch(experimentListUpdated({ids: experimentIds.split(',')})));
|
||||
|
||||
this.experimentsSubscription = this.experiments$.pipe(
|
||||
@@ -94,11 +92,11 @@ export class ExperimentCompareDetailsComponent extends ExperimentCompareBase imp
|
||||
});
|
||||
}
|
||||
|
||||
buildCompareTree(experiments: Array<IExperimentDetail>): ExperimentCompareTree {
|
||||
buildCompareTree(experiments: Array<IExperimentDetail>, hasDataFeature?: boolean): ExperimentCompareTree {
|
||||
const mergedExperiment = getAllKeysEmptyObject(experiments);
|
||||
return experiments
|
||||
.reduce((acc, cur) => {
|
||||
acc[cur.id] = this.buildExperimentTree(cur, this.baseExperiment, mergedExperiment);
|
||||
acc[cur.id] = this.buildExperimentTree(cur, this.baseExperiment, mergedExperiment, hasDataFeature);
|
||||
|
||||
return acc;
|
||||
}, {} as ExperimentCompareTree);
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
></sm-parallel-coordinates-graph>
|
||||
<ng-template #no_data>
|
||||
<div class="d-flex align-items-center justify-content-center flex-column h-100 no-data">
|
||||
<div class="icon i-no-data-graph"></div>
|
||||
<div class="al-icon al-ico-no-data-graph"></div>
|
||||
<div class="no-data-title">No data to show</div>
|
||||
<div>Please select parameters & metric</div>
|
||||
</div>
|
||||
|
||||
@@ -165,9 +165,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
.i-no-data-graph {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
.al-ico-no-data-graph {
|
||||
font-size: 240px;
|
||||
width: 240px;
|
||||
height: 240px;
|
||||
color: #e5e5e5;
|
||||
}
|
||||
|
||||
sm-grouped-checked-filter-list {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import {Component, HostListener, OnDestroy, OnInit, ElementRef, ViewChild} from '@angular/core';
|
||||
import {combineLatest, Observable, Subscription} from 'rxjs';
|
||||
import {ExperimentGraph} from '../../../tasks/tasks.model';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
|
||||
import {distinctUntilChanged, distinctUntilKeyChanged, filter, map} from 'rxjs/operators';
|
||||
import {distinctUntilChanged, filter, map} from 'rxjs/operators';
|
||||
import {selectRouterParams} from '../../../core/reducers/router-reducer';
|
||||
import {get, has} from 'lodash/fp';
|
||||
import {SetExperimentSettings, SetSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
|
||||
import {selectRefreshing, selectScalarsGraphHyperParams, selectScalarsGraphMetrics, selectScalarsGraphShowIdenticalHyperParams, selectScalarsGraphTasks, selectMetricValueType, selectSelectedSettigsHyperParams, selectSelectedSettigsMetric} from '../../reducers';
|
||||
import {getExperimentsHyperParams, setShowIdenticalHyperParams, setvalueType} from '../../actions/experiments-compare-scalars-graph.actions';
|
||||
import {GroupedHyperParams, HyperParams, MetricOption, MetricValueType, SelectedMetric, VariantOption} from '../../reducers/experiments-compare-charts.reducer';
|
||||
import {GroupedHyperParams, MetricOption, MetricValueType, SelectedMetric, VariantOption} from '../../reducers/experiments-compare-charts.reducer';
|
||||
import {MatRadioChange} from '@angular/material/radio';
|
||||
import {selectPlotlyReady} from '../../../core/reducers/view-reducer';
|
||||
import {ExtFrame} from '../../../shared/experiment-graphs/single-graph/plotly-graph-base';
|
||||
|
||||
|
||||
export const _filter = (opt: VariantOption[], value: string): VariantOption[] => {
|
||||
@@ -40,7 +40,7 @@ export class ExperimentCompareHyperParamsGraphComponent implements OnInit, OnDes
|
||||
private selectRefreshing$: Observable<{ refreshing: boolean, autoRefresh: boolean }>;
|
||||
public experiments$: Observable<any[]>;
|
||||
|
||||
public graphs: { [key: string]: ExperimentGraph };
|
||||
public graphs: { [key: string]: ExtFrame };
|
||||
public selectedHyperParams: string[];
|
||||
public selectedMetric: SelectedMetric;
|
||||
public hyperParams: { [section: string]: any};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {ExperimentGraph} from '../../../tasks/tasks.model';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
|
||||
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
|
||||
@@ -14,6 +13,7 @@ import {ScalarKeyEnum} from '../../../../business-logic/model/events/scalarKeyEn
|
||||
import {toggleShowScalarOptions} from '../../actions/compare-header.actions';
|
||||
import {GroupByCharts} from '../../../experiments/reducers/common-experiment-output.reducer';
|
||||
import {GroupedList} from '../../../shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
|
||||
import {ExtFrame} from '../../../shared/experiment-graphs/single-graph/plotly-graph-base';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -44,7 +44,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
public graphList: GroupedList = {};
|
||||
public selectedGraph: string = null;
|
||||
private taskIds: Array<string>;
|
||||
public graphs: { [key: string]: ExperimentGraph };
|
||||
public graphs: { [key: string]: ExtFrame[] };
|
||||
public refreshDisabled = false;
|
||||
public showSettingsBar: boolean = false;
|
||||
public groupBy: GroupByCharts;
|
||||
@@ -138,7 +138,7 @@ export class ExperimentCompareScalarChartsComponent implements OnInit, OnDestroy
|
||||
}
|
||||
}
|
||||
|
||||
private buildNestedListWithoutChildren(merged: { [p: string]: ExperimentGraph }) {
|
||||
private buildNestedListWithoutChildren(merged: { [p: string]: ExtFrame[] }) {
|
||||
return Object.keys(merged).reduce((acc, metric) => {
|
||||
acc[metric] = {};
|
||||
return acc;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {Observable, Subscription} from 'rxjs';
|
||||
import * as metricsValuesActions from '../../actions/experiments-compare-metrics-values.actions';
|
||||
import {selectCompareMetricsValuesExperiments, selectCompareMetricsValuesSortConfig, selectRefreshing} from '../../reducers';
|
||||
import {Router} from '@angular/router';
|
||||
import {AddMessage} from '../../../core/actions/layout.actions';
|
||||
import {addMessage} from '../../../core/actions/layout.actions';
|
||||
import {TreeNode} from '../../shared/experiments-compare-details.model';
|
||||
import {createDiffObjectScalars, getAllKeysEmptyObject} from '../../jsonToDiffConvertor';
|
||||
|
||||
@@ -187,6 +187,6 @@ export class ExperimentCompareMetricValuesComponent implements OnInit, OnDestroy
|
||||
}
|
||||
|
||||
copyIdToClipboard() {
|
||||
this.store.dispatch(new AddMessage('success', 'Copied to clipboard'));
|
||||
this.store.dispatch(addMessage('success', 'Copied to clipboard'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
[class.selected-diff]="checkIfSelectedPath(node.data)"
|
||||
[class.identical-row]="!allPaths[node.data.path]">
|
||||
<i class="fas" [style.margin-left.px]="2 + node.level * 20" [ngClass]="treeControl.isExpanded(node) ? 'fa-chevron-down' : 'fa-chevron-right'"></i>
|
||||
<span class="title-key" [class.ellipsis]="showEllipsis">{{(RENAME_MAP[node.data.key] || node.data.key)}}</span>
|
||||
<span class="title-key" [class.ellipsis]="showEllipsis">{{(renameMap[node.data.key] || node.data.key)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data, $event)">
|
||||
<div *ngIf="!node.hasChildren" class="section" (click)="keyClicked(node.data)">
|
||||
<div [style.padding-left.px]="2 + node.level * 20" [ngClass]="{
|
||||
'node-item-container': true,
|
||||
'identical-row': checkIfIdenticalRow(node.data),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {ChangeDetectorRef, Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {SelectableListItem} from '../../../shared/ui-components/data/selectable-list/selectable-list.model';
|
||||
import {Observable, Subscription} from 'rxjs';
|
||||
import {ExperimentGraph} from '../../../tasks/tasks.model';
|
||||
import {select, Store} from '@ngrx/store';
|
||||
import {IExperimentInfoState} from '../../../../features/experiments/reducers/experiment-info.reducer';
|
||||
import {distinctUntilChanged, filter, map, tap} from 'rxjs/operators';
|
||||
@@ -11,6 +10,7 @@ import {isEqual} from 'lodash/fp';
|
||||
import {scrollToElement} from '../../../shared/utils/shared-utils';
|
||||
import {GetMultiPlotCharts, ResetExperimentMetrics, SetExperimentMetricsSearchTerm, SetExperimentSettings, SetSelectedExperiments} from '../../actions/experiments-compare-charts.actions';
|
||||
import {selectCompareTasksPlotCharts, selectExperimentMetricsSearchTerm, selectRefreshing, selectSelectedExperimentSettings, selectSelectedSettingsHiddenPlot} from '../../reducers';
|
||||
import {ExtFrame} from '../../../shared/experiment-graphs/single-graph/plotly-graph-base';
|
||||
|
||||
@Component({
|
||||
selector: 'sm-experiment-compare-plots',
|
||||
@@ -31,11 +31,11 @@ export class ExperimentComparePlotsComponent implements OnInit, OnDestroy {
|
||||
private routerParamsSubscription: Subscription;
|
||||
private refreshingSubscription: Subscription;
|
||||
|
||||
public graphList: Array<SelectableListItem> = [];
|
||||
public graphList: SelectableListItem[] = [];
|
||||
public selectedGraph: string = null;
|
||||
private experimentId: string;
|
||||
private taskIds: Array<string>;
|
||||
public graphs: { [key: string]: ExperimentGraph };
|
||||
public graphs: { [key: string]: ExtFrame[] };
|
||||
public refreshDisabled: boolean;
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user