Release v2.1 (#109)
Some checks failed
Build Check / build (push) Has been cancelled

Co-authored-by: shallegro <shay@allego.ai>
This commit is contained in:
shyallegro
2025-05-28 11:27:46 +03:00
committed by GitHub
parent 9a4dc4f7c7
commit 233129fbd0
812 changed files with 17538 additions and 18340 deletions

View File

@@ -30,3 +30,9 @@ jobs:
- name: Check for production build
run: test -f build/browser/index.html
- name: Run widget build
run: npm run build-widgets
- name: Check for widget build
run: test -f dist/report-widgets/browser/index.html

View File

@@ -23,11 +23,12 @@
"inlineStyleLanguage": "scss",
"stylePreprocessorOptions": {
"includePaths": [
"",
"src/app/webapp-common/shared/ui-components/styles/",
"src/app/webapp-common/styles/",
"."
]
],
"sass": {
"silenceDeprecations": ["import", "mixed-decls"]
}
},
"assets": [
"src/assets",
@@ -43,6 +44,7 @@
}
],
"styles": [
"src/app/webapp-common/assets/fonts/heebo.css",
"node_modules/ngx-markdown-editor/assets/highlight.js/agate.min.css",
{
"bundleName": "global-styles",
@@ -174,7 +176,7 @@
"stylePreprocessorOptions": {
"includePaths": [
"",
"src/app/webapp-common/shared/ui-components/styles/"
"src/app/webapp-common/styles/"
]
},
"assets": [
@@ -231,8 +233,7 @@
"stylePreprocessorOptions": {
"includePaths": [
"",
"src/app/webapp-common/styles/",
"src/app/webapp-common/shared/ui-components/styles/"
"src/app/webapp-common/styles/"
]
},
"styles": [
@@ -258,7 +259,10 @@
"taira",
"url"
],
"browser": "src/app/webapp-common/clearml-applications/report-widgets/src/main.ts"
"browser": "src/app/webapp-common/clearml-applications/report-widgets/src/main.ts",
"polyfills": [
"zone.js"
]
},
"configurations": {
"production": {
@@ -355,7 +359,7 @@
"schematicCollections": [
"@angular-eslint/schematics",
"@ngrx/schematics",
"ngxtension"
"ngxtension-plugin"
]
}
}

13642
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "clearml-webapp",
"version": "2.0.0",
"version": "2.1.0",
"license": "",
"scripts": {
"ng": "ng",
@@ -19,46 +19,49 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^18.2.12",
"@angular/cdk": "^18.2.14",
"@angular/common": "^18.2.12",
"@angular/compiler": "^18.2.12",
"@angular/core": "^18.2.12",
"@angular/forms": "^18.2.12",
"@angular/material": "^18.2.14",
"@angular/platform-browser": "^18.2.12",
"@angular/platform-browser-dynamic": "^18.2.12",
"@angular/platform-server": "^18.2.12",
"@angular/router": "^18.2.12",
"@angular/service-worker": "^18.2.12",
"@angular/youtube-player": "^18.2.14",
"@aws-sdk/client-s3": "^3.693.0",
"@aws-sdk/s3-request-presigner": "^3.693.0",
"@angular/animations": "19.1.5",
"@angular/cdk": "19.1.3",
"@angular/common": "19.1.5",
"@angular/compiler": "19.1.5",
"@angular/core": "19.1.5",
"@angular/forms": "19.1.5",
"@angular/material": "19.1.3",
"@angular/platform-browser": "19.1.5",
"@angular/platform-browser-dynamic": "19.1.5",
"@angular/platform-server": "19.1.5",
"@angular/router": "19.1.5",
"@angular/service-worker": "19.1.5",
"@angular/youtube-player": "19.1.3",
"@aws-sdk/client-s3": "^3.782.0",
"@aws-sdk/s3-request-presigner": "^3.782.0",
"@ctrl/ngx-github-buttons": "^9.0.0",
"@ctrl/tinycolor": "^4.1.0",
"@ngneat/dag": "^2.0.0",
"@ngrx/component": "^18.1.1",
"@ngrx/effects": "^18.1.1",
"@ngrx/entity": "^18.1.1",
"@ngrx/operators": "^18.1.1",
"@ngrx/router-store": "^18.1.1",
"@ngrx/store": "^18.1.1",
"@typescript-eslint/types": "^8.15.0",
"ace-builds": "^1.36.5",
"@ngrx/component": "19.1.0",
"@ngrx/effects": "19.1.0",
"@ngrx/entity": "19.1.0",
"@ngrx/operators": "19.1.0",
"@ngrx/router-store": "19.1.0",
"@ngrx/signals": "19.1.0",
"@ngrx/store": "19.1.0",
"@primeng/themes": "^19.0.10",
"@typescript-eslint/types": "^8.29.1",
"ace-builds": "^1.39.1",
"angular-resizable-element": "^7.0.2",
"angular-split": "^18.0.0",
"angular-split": "^19.0.0",
"ansi-to-html": "^0.7.2",
"bootstrap": "^5.3.3",
"chart.js": "^4.4.6",
"bootstrap": "^5.3.5",
"chart.js": "^4.4.8",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-annotation": "^3.1.0",
"chartjs-plugin-zoom": "^2.1.0",
"chartjs-plugin-zoom": "^2.2.0",
"curved-arrows": "^0.3.0",
"d3-interpolate": "^3.0.1",
"d3-selection": "^3.0.0",
"date-fns": "^4.1.0",
"diff": "^7.0.0",
"dom-to-image": "^2.6.0",
"dompurify": "^3.2.0",
"dompurify": "^3.2.5",
"export-to-csv": "^1.4.0",
"filesize": "^10.1.6",
"has-ansi": "^6.0.0",
@@ -66,54 +69,54 @@
"lodash-es": "^4.17.21",
"lucene": "^2.1.1",
"marked": "^12.0.2",
"ng2-charts": "^6.0.1",
"ng2-charts": "^8.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-color-picker": "^17.0.0",
"ngx-device-detector": "^8.0.0",
"ngx-color-picker": "^19.0.0",
"ngx-device-detector": "^9.0.0",
"ngx-markdown-editor": "^5.3.4",
"ngx-print": "^1.5.1",
"ngx-print": "^2.0.0",
"ngx-window-token": "^7.0.0",
"ngxtension": "^4.1.0",
"ngxtension": "^5.0.0",
"object-hash": "^3.0.0",
"primeicons": "^7.0.0",
"primeng": "^17.18.15",
"rxjs": "^7.8.1",
"primeng": "^19.0.10",
"rxjs": "^7.8.2",
"string-to-color": "^2.2.2",
"taira": "^3.2.2",
"tslib": "^2.8.1",
"url": "^0.11.4",
"uuid": "^11.0.3",
"uuid": "^11.1.0",
"zone.js": "~0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^18.2.12",
"@angular-devkit/core": "^18.2.12",
"@angular-devkit/schematics": "^18.2.12",
"@angular-devkit/schematics-cli": "^18.2.12",
"@angular/cli": "^18.2.12",
"@angular/compiler-cli": "^18.2.12",
"@angular/language-service": "^18.2.12",
"@ngrx/eslint-plugin": "^18.1.1",
"@ngrx/schematics": "^18.1.1",
"@ngrx/store-devtools": "^18.1.1",
"@angular-devkit/build-angular": "^19.2.12",
"@angular-devkit/core": "19.1.6",
"@angular-devkit/schematics": "19.1.6",
"@angular-devkit/schematics-cli": "19.1.6",
"@angular/cli": "19.1.6",
"@angular/compiler-cli": "19.1.5",
"@angular/language-service": "19.1.5",
"@ngrx/eslint-plugin": "19.1.0",
"@ngrx/schematics": "19.1.0",
"@ngrx/store-devtools": "19.1.0",
"@types/d3-interpolate": "^3.0.4",
"@types/d3-selection": "^3.0.11",
"@types/diff": "^6.0.0",
"@types/diff": "^7.0.2",
"@types/dom-to-image": "^2.6.7",
"@types/dompurify": "^3.0.5",
"@types/has-ansi": "^5.0.2",
"@types/jasmine": "^5.1.4",
"@types/jasmine": "^5.1.7",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.9.0",
"@types/plotly.js": "^2.35.0",
"@types/node": "^22.14.0",
"@types/plotly.js": "^2.35.4",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^10.0.0",
"angular-eslint": "^18.4.1",
"eslint": "^9.15.0",
"angular-eslint": "^19.3.0",
"eslint": "^9.24.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsdoc": "^50.5.0",
"eslint-plugin-jsdoc": "^50.6.9",
"eslint-plugin-prefer-arrow": "1.2.3",
"primeng-sass-theme": "github:primefaces/primeng-sass-theme",
"typescript": "^5.5.4",
"typescript-eslint": "^8.15.0"
"typescript": "^5.7.3",
"typescript-eslint": "^8.29.1"
}
}

View File

@@ -25,12 +25,13 @@ import {ThemeService} from '@common/shared/services/theme.service';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'sm-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [ThemeService]
selector: 'sm-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
providers: [ThemeService],
standalone: false
})
export class AppComponent {
private router = inject(Router);

View File

@@ -1,4 +1,4 @@
import {APP_INITIALIZER, inject, NgModule} from '@angular/core';
import {APP_INITIALIZER, inject, NgModule, provideAppInitializer} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {PreloadAllModules, RouteReuseStrategy, RouterModule} from '@angular/router';
@@ -17,10 +17,8 @@ import {NotifierModule} from '@common/angular-notifier';
import {LayoutModule} from './layout/layout.module';
import {ColorHashService} from '@common/shared/services/color-hash/color-hash.service';
import {SharedModule} from './shared/shared.module';
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';
import {LoginService} from '~/shared/services/login.service';
import {ExperimentSharedModule} from '~/features/experiments/shared/experiment-shared.module';
import {loadUserAndPreferences} from '~/core/app-init';
import {MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipDefaultOptions} from '@angular/material/tooltip';
@@ -30,6 +28,8 @@ import {SpinnerComponent} from '@common/shared/ui-components/overlay/spinner/spi
import {MatIconRegistry} from '@angular/material/icon';
import {provideCharts, withDefaultRegisterables} from 'ng2-charts';
import {PushPipe} from '@ngrx/component';
import { providePrimeNG } from 'primeng/config';
import {cmlPreset} from '@common/styles/prime.preset';
@NgModule({
declarations : [AppComponent],
@@ -68,13 +68,8 @@ import {PushPipe} from '@ngrx/component';
],
providers: [
UserPreferences,
{provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {floatLabel: 'always'}},
{
provide : APP_INITIALIZER,
deps: [LoginService, ConfigurationService],
useFactory: loadUserAndPreferences,
multi : true,
},
{provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, useValue: {floatLabel: 'always', appearance: 'outline'}},
provideAppInitializer(() => loadUserAndPreferences()),
ColorHashService,
{provide: HTTP_INTERCEPTORS, useClass: WebappInterceptor, multi: true},
{provide: RouteReuseStrategy, useClass: CustomReuseStrategy},
@@ -83,6 +78,11 @@ import {PushPipe} from '@ngrx/component';
useValue: {position: 'above'} as MatTooltipDefaultOptions
},
provideCharts(withDefaultRegisterables()),
providePrimeNG({
theme: {
preset: cmlPreset
}
})
],
bootstrap : [AppComponent],
exports : []

View File

@@ -34,6 +34,7 @@ export const routes: Routes = [
{path: '', pathMatch: 'full', children: [], canActivate: [projectRedirectGuardGuard]},
{
path: 'overview',
data: {search: false},
loadChildren: () => import('./webapp-common/project-info/project-info.module').then(m => m.ProjectInfoModule),
canDeactivate: [resetContextMenuGuard]
},
@@ -52,7 +53,7 @@ export const routes: Routes = [
{path: 'compare-experiments', redirectTo: 'compare-tasks'},
{
path: 'compare-tasks',
data: {entityType: EntityTypeEnum.experiment},
data: {entityType: EntityTypeEnum.experiment, search: false},
loadChildren: () =>
import('./webapp-common/experiments-compare/experiments-compare.module').then(m => m.ExperimentsCompareModule)
},

View File

@@ -1,14 +1,14 @@
import {LoginService} from '~/shared/services/login.service';
import {ConfigurationService} from '@common/shared/services/configuration.service';
import {combineLatest} from 'rxjs';
import {switchMap} from 'rxjs';
import {inject} from '@angular/core';
export const loadUserAndPreferences = (
loginService: LoginService,
confService: ConfigurationService,
): () => Promise<any> => (): Promise<any> => new Promise((resolve) => {
combineLatest([
confService.initConfigurationService(),
loginService.initCredentials()
])
.subscribe(() => loginService.loginFlow(resolve));
});
export const loadUserAndPreferences = ()=>
new Promise((resolve) => {
const loginService = inject(LoginService);
const confService = inject(ConfigurationService);
confService.initConfigurationService()
.pipe(switchMap(() => loginService.initCredentials()))
.subscribe(() => loginService.loginFlow(resolve));
});

View File

@@ -2,9 +2,11 @@ import {commonAuthReducer, initAuth, selectAuth} from '@common/core/reducers/com
import {createReducer, createSelector} from '@ngrx/store';
const extraCredentials = [];
const services = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const selectExtraCredentials = createSelector(selectAuth, state => extraCredentials);
export const selectCompanyPreSignServices = createSelector(selectAuth, state => services);
export const authReducer = createReducer(
initAuth,

View File

@@ -1,24 +1,12 @@
import {Component} from '@angular/core';
import {DashboardSearchBaseComponent} from '@common/dashboard/dashboard-search.component.base';
import {selectIsSearching} from '@common/common-search/common-search.reducer';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {debounceTime, filter} from 'rxjs/operators';
@Component({
selector: 'sm-dashboard-search',
templateUrl: './dashboard-search.component.html',
styleUrls: ['./dashboard-search.component.scss'],
selector: 'sm-dashboard-search',
templateUrl: './dashboard-search.component.html',
styleUrls: ['./dashboard-search.component.scss'],
standalone: false
})
export class DashboardSearchComponent extends DashboardSearchBaseComponent {
constructor() {
super();
this.store.select(selectIsSearching)
.pipe(
debounceTime(200),
takeUntilDestroyed(),
filter(active => !active)
)
.subscribe(() => this.router.navigate(['dashboard'], ));
}
}

View File

@@ -1,4 +1,4 @@
<div class="h-100">
<div>
<mat-tab-group
[selectedIndex]="activeIndex()"
(selectedTabChange)="activeLinkChanged.emit(activeLinksList[$event.index].name)"
@@ -11,6 +11,9 @@
}
</mat-tab-group>
<div class="page-container">
@if (getResults()?.length === 0) {
<span class="no-data">No data to show</span>
} @else if(getResults()){
<sm-virtual-grid
[cardTemplate]="
activeLink() === searchPages.projects ? ProjectTemplate :
@@ -27,6 +30,7 @@
(loadMoreClicked)="loadMoreClicked.emit()"
>
</sm-virtual-grid>
}
</div>
<ng-template #ProjectTemplate let-project>

View File

@@ -1,8 +1,22 @@
.mat-mdc-tab-group {
--mat-tab-header-label-text-size: 14px;
border-bottom: 1px solid var(--color-outline-variant);
margin: 24px 0;
height: 49px;
}
.page-container {
height: calc(100% - 67px);
height: calc(90vh - 224px);
width: calc(90vw - 96px);
margin-top: 18px;
.no-data {
display: flex;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--color-outline);
}
}

View File

@@ -7,9 +7,10 @@ import {activeLinksList, ActiveSearchLink, activeSearchLink} from '~/features/da
import {IReport} from '@common/reports/reports.consts';
@Component({
selector: 'sm-search-results-page',
templateUrl: './search-results-page.component.html',
styleUrls: ['./search-results-page.component.scss']
selector: 'sm-search-results-page',
templateUrl: './search-results-page.component.html',
styleUrls: ['./search-results-page.component.scss'],
standalone: false
})
export class SearchResultsPageComponent {
protected readonly searchPages = activeSearchLink;
@@ -71,6 +72,8 @@ export class SearchResultsPageComponent {
case activeSearchLink.models:
return 264;
case activeSearchLink.pipelines:
case activeSearchLink.reports:
case activeSearchLink.openDatasets:
return 226;
default:
return 250;

View File

@@ -35,7 +35,10 @@ import {MatTab, MatTabGroup} from '@angular/material/tabs';
MatTabGroup,
MatTab,
],
declarations:[
exports: [
DashboardSearchComponent
],
declarations: [
SearchResultsPageComponent, DashboardSearchComponent
]
})

View File

@@ -17,9 +17,10 @@ import {selectActiveSearch} from '@common/common-search/common-search.reducer';
@Component({
selector: 'sm-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss']
selector: 'sm-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
standalone: false
})
export class DashboardComponent implements OnInit, OnDestroy {
private store = inject(Store);

View File

@@ -12,22 +12,21 @@ import {MatIcon} from '@angular/material/icon';
import {ClickStopPropagationDirective} from '@common/shared/ui-components/directives/click-stop-propagation.directive';
@Component({
selector: 'sm-nested-datasets-page',
templateUrl: './nested-datasets-page.component.html',
styleUrls: [
'../../../webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.scss',
'../../../webapp-common/datasets/open-datasets/open-datasets.component.scss'
],
imports: [
ProjectsSharedModule,
AsyncPipe,
CircleCounterComponent,
TagListComponent,
MatButton,
MatIcon,
ClickStopPropagationDirective
],
standalone: true
selector: 'sm-nested-datasets-page',
templateUrl: './nested-datasets-page.component.html',
styleUrls: [
'../../../webapp-common/nested-project-view/nested-project-view-page/nested-project-view-page.component.scss',
'../../../webapp-common/datasets/open-datasets/open-datasets.component.scss'
],
imports: [
ProjectsSharedModule,
AsyncPipe,
CircleCounterComponent,
TagListComponent,
MatButton,
MatIcon,
ClickStopPropagationDirective
]
})
export class NestedDatasetsPageComponent extends CommonProjectsPageComponent {
entityTypeEnum = ProjectTypeEnum;

View File

@@ -1,7 +1,7 @@
.tab-nav {
display: grid;
grid-template-columns: 200px 1fr 200px;
border-bottom: 1px solid #efefef;
border-bottom: 1px solid var(--color-surface-container-high);
&.minimized {
grid-template-columns: 0 1fr 108px;

View File

@@ -3,10 +3,11 @@ import {Link} from '@common/shared/components/router-tab-nav-bar/router-tab-nav-
@Component({
selector: 'sm-experiment-info-navbar',
templateUrl: './experiment-info-navbar.component.html',
styleUrls: ['./experiment-info-navbar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
selector: 'sm-experiment-info-navbar',
templateUrl: './experiment-info-navbar.component.html',
styleUrls: ['./experiment-info-navbar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class ExperimentInfoNavbarComponent {
public baseInfoRoute: string[];

View File

@@ -1,10 +1,11 @@
import {Component, computed, Signal} from '@angular/core';
import {Component, computed} from '@angular/core';
import {ExperimentMenuComponent} from '@common/experiments/shared/components/experiment-menu/experiment-menu.component';
@Component({
selector: 'sm-experiment-menu-extended',
templateUrl: '../../../../webapp-common/experiments/shared/components/experiment-menu/experiment-menu.component.html',
styleUrls: ['../../../../webapp-common/experiments/shared/components/experiment-menu/experiment-menu.component.scss']
selector: 'sm-experiment-menu-extended',
templateUrl: '../../../../webapp-common/experiments/shared/components/experiment-menu/experiment-menu.component.html',
styleUrls: ['../../../../webapp-common/experiments/shared/components/experiment-menu/experiment-menu.component.scss'],
standalone: false
})
export class ExperimentMenuExtendedComponent extends ExperimentMenuComponent{
contextMenu = computed(() => this as ExperimentMenuComponent);

View File

@@ -39,15 +39,19 @@
class="graph-settings-menu"
(click)="$event.stopPropagation()"
[verticalLayout]="true"
[smoothWeight]="smoothWeight$ | ngrxPush"
[smoothType]="smoothType$ | ngrxPush"
[xAxisType]="xAxisType$ | ngrxPush"
[groupBy]="groupBy$ | ngrxPush"
[smoothWeight]="smoothWeight()"
[smoothSigma]="smoothSigma()"
[smoothType]="smoothType()"
[xAxisType]="xAxisType()"
[groupBy]="groupBy()"
[clearable]="projectId === '*' ? null : !isProjectLevel()"
[groupByOptions]="groupByOptions"
(changeWeight)="changeSmoothness($event)"
(changeSigma)="changeSigma($event)"
(changeXAxisType)="changeXAxisType($event)"
(changeGroupBy)="changeGroupBy($event)"
(changeSmoothType)="changeSmoothType($event)"
(setToProject)="setToProject()"
></sm-graph-settings-bar>
</mat-menu>

View File

@@ -1,10 +1,11 @@
import {Component} from '@angular/core';
import {BaseExperimentOutputComponent} from '../../../../webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component';
import {BaseExperimentOutputComponent} from '@common/experiments/containers/experiment-ouptut/base-experiment-output.component';
@Component({
selector: 'sm-experiment-output',
templateUrl: './experiment-output.component.html',
styleUrls: ['../../../../webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss']
styleUrls: ['../../../../webapp-common/experiments/containers/experiment-ouptut/base-experiment-output.component.scss'],
standalone: false
})
export class ExperimentOutputComponent extends BaseExperimentOutputComponent {
public overflow: boolean;

View File

@@ -11,68 +11,32 @@ import {ExperimentGraphsModule} from '@common/shared/experiment-graphs/experimen
import {ExperimentCompareSharedModule} from '@common/experiments-compare/shared/experiment-compare-shared.module';
import {AngularSplitModule} from 'angular-split';
import {DebugImagesModule} from '@common/debug-images/debug-images.module';
import {
ExperimentInfoExecutionComponent
} from '@common/experiments/containers/experiment-info-execution/experiment-info-execution.component';
import {ExperimentInfoExecutionComponent} from '@common/experiments/containers/experiment-info-execution/experiment-info-execution.component';
import {MatSidenavModule} from '@angular/material/sidenav';
import {MatListModule} from '@angular/material/list';
import {ExperimentOutputComponent} from './containers/experiment-ouptut/experiment-output.component';
import {ExperimentInfoNavbarComponent} from './containers/experiment-info-navbar/experiment-info-navbar.component';
import {
ExperimentInfoHyperParametersComponent
} from '@common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component';
import {
ExperimentInfoArtifactItemComponent
} from '@common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component';
import {
ExperimentGeneralInfoComponent
} from '@common/experiments/dumb/experiment-general-info/experiment-general-info.component';
import {
ExperimentArtifactItemViewComponent
} from '@common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component';
import {
ExperimentHyperParamsNavbarComponent
} from '@common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component';
import {
ExperimentExecutionSourceCodeComponent
} from '@common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component';
import {
ExperimentInfoEditDescriptionComponent
} from '@common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component';
import {
ExperimentOutputModelViewComponent
} from '@common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component';
import {
ExperimentInfoGeneralComponent
} from '@common/experiments/containers/experiment-info-general/experiment-info-general.component';
import {ExperimentInfoHyperParametersComponent} from '@common/experiments/containers/experiment-info-hyper-parameters/experiment-info-hyper-parameters.component';
import {ExperimentInfoArtifactItemComponent} from '@common/experiments/containers/experiment-info-artifact-item/experiment-info-artifact-item.component';
import {ExperimentArtifactItemViewComponent} from '@common/experiments/dumb/experiment-artifact-item-view/experiment-artifact-item-view.component';
import {ExperimentHyperParamsNavbarComponent} from '@common/experiments/dumb/experiment-hyper-params-navbar/experiment-hyper-params-navbar.component';
import {ExperimentExecutionSourceCodeComponent} from '@common/experiments/dumb/experiment-execution-source-code/experiment-execution-source-code.component';
import {ExperimentInfoEditDescriptionComponent} from '@common/experiments/dumb/experiment-info-edit-description/experiment-info-edit-description.component';
import {ExperimentOutputModelViewComponent} from '@common/experiments/dumb/experiment-output-model-view/experiment-output-model-view.component';
import {ExperimentInfoGeneralComponent} from '@common/experiments/containers/experiment-info-general/experiment-info-general.component';
import {BaseClickableArtifactComponent} from '@common/experiments/dumb/base-clickable-artifact.component';
import {
ExperimentModelsFormViewComponent
} from '@common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component';
import {
ExperimentInfoArtifactsComponent
} from '@common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component';
import {
ExperimentInfoHeaderComponent
} from '@common/experiments/dumb/experiment-info-header/experiment-info-header.component';
import {
ExperimentInfoTaskModelComponent
} from '@common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component';
import {
ExperimentOutputScalarsComponent
} from '@common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component';
import {
ExperimentInfoModelComponent
} from '@common/experiments/containers/experiment-info-model/experiment-info-model.component';
import {
ExperimentInfoHyperParametersFormContainerComponent
} from '@common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component';
import {ExperimentModelsFormViewComponent} from '@common/experiments/dumb/experiment-models-form-view/experiment-models-form-view.component';
import {ExperimentInfoArtifactsComponent} from '@common/experiments/containers/experiment-info-aritfacts/experiment-info-artifacts.component';
import {ExperimentInfoHeaderComponent} from '@common/experiments/dumb/experiment-info-header/experiment-info-header.component';
import {ExperimentInfoTaskModelComponent} from '@common/experiments/containers/experiment-info-task-model/experiment-info-task-model.component';
import {ExperimentOutputScalarsComponent} from '@common/experiments/containers/experiment-output-scalars/experiment-output-scalars.component';
import {ExperimentInfoModelComponent} from '@common/experiments/containers/experiment-info-model/experiment-info-model.component';
import {ExperimentInfoHyperParametersFormContainerComponent} from '@common/experiments/containers/experiment-info-hyper-parameters-form-container/experiment-info-hyper-parameters-form-container.component';
import {ExperimentOutputLogModule} from '@common/experiments/shared/experiment-output-log/experiment-output-log.module';
import {RouterModule} from '@angular/router';
import {ScrollingModule} from '@angular/cdk/scrolling';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {MatRadioModule} from '@angular/material/radio';
import {SharedModule} from '~/shared/shared.module';
import {MAT_AUTOCOMPLETE_SCROLL_STRATEGY} from '@angular/material/autocomplete';
import {scrollFactory} from '@common/shared/utils/scroll-factory';
import {Overlay} from '@angular/cdk/overlay';
@@ -81,9 +45,7 @@ import {RouterTabNavBarComponent} from '@common/shared/components/router-tab-nav
import {MatTabsModule} from '@angular/material/tabs';
import {OverlayComponent} from '@common/shared/ui-components/overlay/overlay/overlay.component';
import {RefreshButtonComponent} from '@common/shared/components/refresh-button/refresh-button.component';
import {
InfoHeaderStatusIconLabelComponent
} from '@common/shared/experiment-info-header-status-icon-label/info-header-status-icon-label.component';
import {InfoHeaderStatusIconLabelComponent} from '@common/shared/experiment-info-header-status-icon-label/info-header-status-icon-label.component';
import {NAPipe} from '@common/shared/pipes/na.pipe';
import {SortPipe} from '@common/shared/pipes/sort.pipe';
import {safeAngularUrlParameterPipe} from '@common/shared/pipes/safeAngularUrlParameter.pipe';
@@ -100,120 +62,114 @@ import {InlineEditComponent} from '@common/shared/ui-components/inputs/inline-ed
import {TagsMenuComponent} from '@common/shared/ui-components/tags/tags-menu/tags-menu.component';
import {EntityFooterComponent} from '@common/shared/entity-page/entity-footer/entity-footer.component';
import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component';
import {
ExperimentTypeIconLabelComponent
} from '@common/shared/experiment-type-icon-label/experiment-type-icon-label.component';
import {ExperimentTypeIconLabelComponent} from '@common/shared/experiment-type-icon-label/experiment-type-icon-label.component';
import {SearchComponent} from '@common/shared/ui-components/inputs/search/search.component';
import {IdBadgeComponent} from '@common/shared/components/id-badge/id-badge.component';
import {ScrollTextareaComponent} from '@common/shared/components/scroll-textarea/scroll-textarea.component';
import {TagListComponent} from '@common/shared/ui-components/tags/tag-list/tag-list.component';
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
import {LabeledRowComponent} from '@common/shared/ui-components/data/labeled-row/labeled-row.component';
import {
SelectableGroupedFilterListComponent
} from '@common/shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
import {SelectableGroupedFilterListComponent} from '@common/shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
import {EditableSectionComponent} from '@common/shared/ui-components/panel/editable-section/editable-section.component';
import {MatMenuModule} from '@angular/material/menu';
import {MatExpansionModule} from '@angular/material/expansion';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {HesitateDirective} from '@common/shared/ui-components/directives/hesitate.directive';
import {
ShowTooltipIfEllipsisDirective
} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
import {ShowTooltipIfEllipsisDirective} from '@common/shared/ui-components/indicators/tooltip/show-tooltip-if-ellipsis.directive';
import {SelectQueueModule} from '@common/experiments/shared/components/select-queue/select-queue.module';
import {PushPipe} from '@ngrx/component';
import {ExperimentHeaderComponent} from '@common/experiments/dumb/experiment-header/experiment-header.component';
import {
ExperimentOperationsLogComponent
} from '@common/experiments/dumb/experiment-operations-log/experiment-operations-log.component';
import {
ExperimentArtifactsNavbarComponent
} from '@common/experiments/dumb/experiment-artifacts-navbar/experiment-artifacts-navbar.component';
import {ExperimentOperationsLogComponent} from '@common/experiments/dumb/experiment-operations-log/experiment-operations-log.component';
import {ExperimentArtifactsNavbarComponent} from '@common/experiments/dumb/experiment-artifacts-navbar/experiment-artifacts-navbar.component';
import {CommonDeleteDialogModule} from '@common/shared/entity-page/entity-delete/common-delete-dialog.module';
import {MatIcon} from '@angular/material/icon';
import {MatButton, MatIconButton} from '@angular/material/button';
import {GraphSettingsBarComponent} from '@common/shared/experiment-graphs/graph-settings-bar/graph-settings-bar.component';
import {MatSlideToggle} from '@angular/material/slide-toggle';
import {ExperimentDetailsComponent} from '@common/experiments/dumb/experiment-details/experiment-details.component';
@NgModule({
imports: [
FormsModule,
LayoutModule,
ReactiveFormsModule,
CommonModule,
ExperimentRouterModule,
ExperimentSharedModule,
ExperimentGraphsModule,
SelectModelModule,
DebugImagesModule,
ExperimentCompareSharedModule,
MatSidenavModule,
MatListModule,
AngularSplitModule,
ScrollingModule,
RouterModule,
SharedModule,
ExperimentOutputLogModule,
MatProgressSpinnerModule,
MatRadioModule,
RouterTabNavBarComponent,
MatTabsModule,
RouterTabNavBarComponent,
OverlayComponent,
RefreshButtonComponent,
InfoHeaderStatusIconLabelComponent,
NAPipe,
SortPipe,
safeAngularUrlParameterPipe,
ReplaceViaMapPipe,
FilterOutPipe,
DurationPipe,
FilterInternalPipe,
FileSizePipe,
HideRedactedArgumentsPipe,
MenuItemComponent,
CopyClipboardComponent,
SectionHeaderComponent,
InlineEditComponent,
TagsMenuComponent,
EntityFooterComponent,
MenuComponent,
ExperimentTypeIconLabelComponent,
SearchComponent,
IdBadgeComponent,
ScrollTextareaComponent,
TagListComponent,
TooltipDirective,
LabeledRowComponent,
EditableSectionComponent,
SelectableGroupedFilterListComponent,
MatMenuModule,
MatExpansionModule,
MatInputModule,
MatSelectModule,
HesitateDirective,
ShowTooltipIfEllipsisDirective,
SelectQueueModule,
PushPipe,
ExperimentHeaderComponent,
ExperimentOperationsLogComponent,
ExperimentArtifactsNavbarComponent,
CommonDeleteDialogModule,
MatIcon,
MatButton,
MatIconButton
],
imports: [
MatTabsModule,
MatMenuModule,
MatExpansionModule,
MatInputModule,
MatSelectModule,
MatSlideToggle,
MatButton,
MatIcon,
MatIconButton,
MatListModule,
MatRadioModule,
MatSidenavModule,
MatProgressSpinnerModule,
FormsModule,
LayoutModule,
ReactiveFormsModule,
CommonModule,
ExperimentRouterModule,
ExperimentGraphsModule,
SelectModelModule,
DebugImagesModule,
ExperimentCompareSharedModule,
AngularSplitModule,
ScrollingModule,
CommonDeleteDialogModule,
RouterModule,
ExperimentOutputLogModule,
ExperimentSharedModule,
RouterTabNavBarComponent,
HesitateDirective,
NAPipe,
SortPipe,
safeAngularUrlParameterPipe,
ReplaceViaMapPipe,
FilterOutPipe,
DurationPipe,
FilterInternalPipe,
FileSizePipe,
HideRedactedArgumentsPipe,
InfoHeaderStatusIconLabelComponent,
MenuItemComponent,
CopyClipboardComponent,
SectionHeaderComponent,
InlineEditComponent,
TagsMenuComponent,
EntityFooterComponent,
MenuComponent,
ExperimentTypeIconLabelComponent,
SearchComponent,
IdBadgeComponent,
ScrollTextareaComponent,
TagListComponent,
TooltipDirective,
LabeledRowComponent,
EditableSectionComponent,
SelectableGroupedFilterListComponent,
OverlayComponent,
RefreshButtonComponent,
HesitateDirective,
ShowTooltipIfEllipsisDirective,
SelectQueueModule,
PushPipe,
ExperimentHeaderComponent,
ExperimentOperationsLogComponent,
ExperimentArtifactsNavbarComponent,
GraphSettingsBarComponent,
],
declarations: [
ExperimentsComponent,
ExperimentInfoExecutionComponent,
ExperimentOutputComponent,
ExperimentInfoExecutionComponent,
ExperimentInfoNavbarComponent,
ExperimentsComponent,
BaseClickableArtifactComponent,
ExperimentInfoHeaderComponent,
ExperimentInfoModelComponent,
ExperimentInfoTaskModelComponent,
ExperimentInfoGeneralComponent,
ExperimentGeneralInfoComponent,
ExperimentDetailsComponent,
ExperimentModelsFormViewComponent,
ExperimentOutputModelViewComponent,
ExperimentExecutionSourceCodeComponent,

View File

@@ -4,10 +4,11 @@ import {experimentOutputReducer, ExperimentOutputState, experimentOutputInitStat
import {experimentsViewReducer, ExperimentsViewState, experimentsViewInitialState} from '@common/experiments/reducers/experiments-view.reducer';
import {IExperimentInfo} from '../shared/experiment-info.model';
import {TaskStatusEnum} from '~/business-logic/model/tasks/taskStatusEnum';
import {selectSelectedModel} from '@common/models/reducers';
import {selectModelId, selectSelectedModel} from '@common/models/reducers';
import {selectCurrentUser} from '@common/core/reducers/users-reducer';
import {isReadOnly} from '@common/shared/utils/is-read-only';
import {isSharedAndNotOwner} from '@common/shared/utils/is-shared-and-not-owner';
import { selectSelectedProjectId } from '@common/core/reducers/projects.reducer';
export interface ExperimentState {
view: ExperimentsViewState;
@@ -60,6 +61,12 @@ export const selectExperimentFormValidity = createSelector(selectExperimentInfoD
return !error;
});
export const selectSelectedModelSettings = createSelector(experimentOutput, selectSelectedModel,
(output, currentModel): ExperimentSettings =>
output.settingsList && output.settingsList.find((setting) => currentModel && setting.id === currentModel.id));
export const selectOutputSettings = createSelector(experimentOutput, state => state.settingsList);
export const selectSelectedModelSettings = createSelector(experimentOutput, selectModelId, selectSelectedProjectId,
(output, modelId, projectId): Partial<ExperimentSettings> => {
if (output.settingsList && modelId) {
return output.settingsList.find((setting) => setting.id === modelId) ?? output.settingsList.find((setting) => setting.id === projectId) ?? {};
} else {
return null;
}
});

View File

@@ -58,7 +58,6 @@ import {TableComponent} from '@common/shared/ui-components/data/table/table.comp
import {MatSelectModule} from '@angular/material/select';
import {ButtonToggleComponent} from '@common/shared/ui-components/inputs/button-toggle/button-toggle.component';
import {GroupedCheckedFilterListComponent} from '@common/shared/ui-components/data/grouped-checked-filter-list/grouped-checked-filter-list.component';
import {SelectableFilterListComponent} from '@common/shared/ui-components/data/selectable-filter-list/selectable-filter-list.component';
import {TableCardComponent} from '@common/shared/ui-components/data/table-card/table-card.component';
import {ToggleArchiveComponent} from '@common/shared/ui-components/buttons/toggle-archive/toggle-archive.component';
import {RefreshButtonComponent} from '@common/shared/components/refresh-button/refresh-button.component';
@@ -80,6 +79,10 @@ import {MatDialogActions, MatDialogClose} from '@angular/material/dialog';
import {IsRowSelectedPipe} from '@common/shared/ui-components/data/table/is-rwo-selected.pipe';
import {MiniTagsListComponent} from '@common/shared/ui-components/tags/user-tag/mini-tags-list/mini-tags-list.component';
import {TableCardFilterComponent} from '@common/shared/ui-components/data/table/table-card-filter-template/table-card-filter.component';
import {SelectableGroupedFilterListComponent} from '@common/shared/ui-components/data/selectable-grouped-filter-list/selectable-grouped-filter-list.component';
import {JsonIndentPipe} from '@common/experiments/dumb/experiment-execution-parameters/json-indent.pipe';
import {MultiLineTooltipComponent} from '@common/shared/components/multi-line-tooltip/multi-line-tooltip.component';
import {MatDivider} from '@angular/material/divider';
export const experimentSyncedKeys = [
'view.projectColumnsSortOrder',
@@ -178,7 +181,6 @@ const DECLARATIONS = [
MatSelectModule,
ButtonToggleComponent,
GroupedCheckedFilterListComponent,
SelectableFilterListComponent,
TableCardComponent,
ToggleArchiveComponent,
RefreshButtonComponent,
@@ -203,6 +205,10 @@ const DECLARATIONS = [
MiniTagsListComponent,
TableCardFilterComponent,
ReactiveFormsModule,
SelectableGroupedFilterListComponent,
JsonIndentPipe,
MultiLineTooltipComponent,
MatDivider,
],
declarations : [...DECLARATIONS],
providers : [

View File

@@ -1,18 +1,12 @@
import {Model} from '../../../business-logic/model/models/model';
import {ISelectedExperiment} from './experiment-info.model';
import {isExample} from '../../../webapp-common/shared/utils/shared-utils';
import {isExample} from '@common/shared/utils/shared-utils';
import {ExperimentTagsEnum} from '~/features/experiments/shared/experiments.const';
export function areLabelsEqualss(modelLabels: Model['labels'], labels: Model['labels']) {
return true;
}
export function isDevelopment(entity): boolean {
return false;
}
export function getSystemTags(experiment: ISelectedExperiment) {
export function getSystemTags(experiment: {system_tags?: string[]}) {
const ignoredTags: string[] = [ExperimentTagsEnum.Hidden, ExperimentTagsEnum.Development];
ignoredTags.push(ExperimentTagsEnum.Pipeline, ExperimentTagsEnum.Dataset);
if (experiment?.system_tags?.includes(ExperimentTagsEnum.Dataset) || experiment?.system_tags?.includes(ExperimentTagsEnum.Pipeline)) {

View File

@@ -1,10 +1,11 @@
import {Component, signal} from '@angular/core';
import {ModelMenuComponent} from '../../../../webapp-common/models/containers/model-menu/model-menu.component';
import {ModelMenuComponent} from '@common/models/containers/model-menu/model-menu.component';
@Component({
selector: 'sm-model-menu-extended',
templateUrl: '../../../../webapp-common/models/containers/model-menu/model-menu.component.html',
styleUrls: ['../../../../webapp-common/models/containers/model-menu/model-menu.component.scss']
styleUrls: ['../../../../webapp-common/models/containers/model-menu/model-menu.component.scss'],
standalone: false
})
export class ModelMenuExtendedComponent extends ModelMenuComponent {

View File

@@ -1,9 +1,10 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'sm-not-found',
templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.scss']
selector: 'sm-not-found',
templateUrl: './not-found.component.html',
styleUrls: ['./not-found.component.scss'],
standalone: false
})
export class NotFoundComponent implements OnInit {

View File

@@ -1,13 +1,19 @@
import {Component} from '@angular/core';
import {ProjectCardMenuComponent} from '@common/shared/ui-components/panel/project-card-menu/project-card-menu.component';
import {MenuItemComponent} from '@common/shared/ui-components/panel/menu-item/menu-item.component';
import {MenuComponent} from '@common/shared/ui-components/panel/menu/menu.component';
@Component({
selector: 'sm-project-card-menu-extended',
templateUrl: '../../../../webapp-common/shared/ui-components/panel/project-card-menu/project-card-menu.component.html',
styleUrls: ['../../../../webapp-common/shared/ui-components/panel/project-card-menu/project-card-menu.component.scss']
styleUrls: ['../../../../webapp-common/shared/ui-components/panel/project-card-menu/project-card-menu.component.scss'],
imports: [
MenuItemComponent,
MenuComponent,
]
})
export class ProjectCardMenuExtendedComponent extends ProjectCardMenuComponent{
export class ProjectCardMenuExtendedComponent extends ProjectCardMenuComponent {
set contextMenu(data) {}
get contextMenu() {
return this;

View File

@@ -1,7 +1,6 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {ProjectCardMenuExtendedComponent} from '~/features/projects/containers/project-card-menu-extended/project-card-menu-extended.component';
import {ProjectCardMenuComponent} from '@common/shared/ui-components/panel/project-card-menu/project-card-menu.component';
import {PipelineCardMenuComponent} from '@common/pipelines/pipeline-card-menu/pipeline-card-menu.component';
import {ScrollingModule} from '@angular/cdk/scrolling';
@@ -36,11 +35,13 @@ import {DotsLoadMoreComponent} from '@common/shared/ui-components/indicators/dot
import {MatTab, MatTabGroup} from '@angular/material/tabs';
import {MatIcon} from '@angular/material/icon';
import {MatIconButton} from '@angular/material/button';
import {CommonSearchComponent} from '@common/common-search/containers/common-search/common-search.component';
import {PushPipe} from '@ngrx/component';
const _declarations = [
ProjectCardMenuExtendedComponent,
PipelineCardMenuComponent,
PipelinesEmptyStateComponent,
NestedCardComponent,
PipelineCardMenuComponent,
DatasetEmptyComponent,
NestedProjectViewPageComponent,
ProjectsHeaderComponent
@@ -73,14 +74,16 @@ const _declarations = [
TooltipDirective,
ButtonToggleComponent,
ShowTooltipIfEllipsisDirective,
PushPipe,
DotsLoadMoreComponent,
MatTabGroup,
MatTab,
MatIconButton,
MatIcon,
MatIconButton
CommonSearchComponent
],
declarations: [..._declarations, PipelinesEmptyStateComponent],
exports: [..._declarations, PipelinesEmptyStateComponent]
declarations: [..._declarations],
exports: [..._declarations]
})
export class ProjectsSharedModule {
}

View File

@@ -1,8 +1,12 @@
<div class="no-data-center">
<mat-icon fontSet="al" fontIcon="al-ico-model-endpoints" class="xxxl"></mat-icon>
<span class="message">MODEL ENDPOINTS WILL APPEAR HERE</span>
<span class="sub-message">
Monitor models deployed with <a href="https://github.com/allegroai/clearml-serving" target="_blank">clearml-serving</a>
or the <a href="https://github.com/allegroai/clearml" target="_blank">ClearML</a> HttpRouter class
</span>
<mat-icon fontSet="al" fontIcon="al-ico-model-endpoints" class="xxl"></mat-icon>
@if (isLoading()) {
<span class="message">NO MODEL ENDPOINTS CURRENTLY BEING SET UP</span>
} @else {
<span class="message">NO ACTIVE MODEL ENDPOINTS</span>
<span class="sub-message">Monitor models deployed with <a href="https://github.com/allegroai/clearml-serving" target="_blank">
clearml-serving</a>
or the <a href="https://github.com/allegroai/clearml" target="_blank">ClearML</a> HttpRouter class
</span>
}
</div>

View File

@@ -8,14 +8,11 @@ a[href]:hover {
justify-content: center;
gap: 24px;
align-items: center;
}
.al-icon {
color: var(--color-tint-14);
color: var(--color-empty-state);
}
.message {
font-size: 20px;
font-size: 16px;
font-weight: 500;
}

View File

@@ -1,17 +1,15 @@
import { Component } from '@angular/core';
import {RouterLink} from '@angular/router';
import {MatIcon} from '@angular/material/icon';
import {ChangeDetectionStrategy, Component, input} from '@angular/core';
import { MatIcon } from '@angular/material/icon';
@Component({
selector: 'sm-serving-empty-state',
standalone: true,
imports: [
RouterLink,
MatIcon
],
templateUrl: './serving-empty-state.component.html',
styleUrl: './serving-empty-state.component.scss'
selector: 'sm-serving-empty-state',
imports: [
MatIcon
],
templateUrl: './serving-empty-state.component.html',
styleUrl: './serving-empty-state.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ServingEmptyStateComponent {
isLoading = input<boolean>(false);
}

View File

@@ -3,8 +3,8 @@ import {Component} from '@angular/core';
@Component({
selector: 'sm-admin-footer-actions',
templateUrl: './admin-footer-actions.component.html',
styleUrls: ['./admin-footer-actions.component.scss']
styleUrls: ['./admin-footer-actions.component.scss'],
standalone: false
})
export class AdminFooterActionsComponent {
constructor() { }
}

View File

@@ -8,15 +8,14 @@ import {DialogTemplateComponent} from '@common/shared/ui-components/overlay/dial
import {AdminDialogTemplateComponent} from '@common/settings/admin/admin-dialog-template/admin-dialog-template.component';
@Component({
selector: 'sm-create-credential-dialog',
templateUrl: './create-credential-dialog.component.html',
styleUrls: ['./create-credential-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DialogTemplateComponent,
AdminDialogTemplateComponent
]
selector: 'sm-create-credential-dialog',
templateUrl: './create-credential-dialog.component.html',
styleUrls: ['./create-credential-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
DialogTemplateComponent,
AdminDialogTemplateComponent
]
})
export class CreateCredentialDialogComponent {
private store = inject(Store);

View File

@@ -7,13 +7,12 @@ import {ConfigurationService} from '@common/shared/services/configuration.servic
@Component({
selector: 'sm-usage-stats',
templateUrl: './usage-stats.component.html',
styleUrls: ['./usage-stats.component.scss'],
imports: [
MatSlideToggle
],
standalone: true
selector: 'sm-usage-stats',
templateUrl: './usage-stats.component.html',
styleUrls: ['./usage-stats.component.scss'],
imports: [
MatSlideToggle
]
})
export class UsageStatsComponent {
private store = inject(Store);

View File

@@ -1,6 +1,6 @@
<header>
<div class="title">
API credentials
API CREDENTIALS
<mat-icon
class="info sm"
fontSet="al" fontIcon="al-ico-info-circle"

View File

@@ -12,11 +12,12 @@ import {CreateCredentialDialogComponent} from '~/features/settings/containers/ad
@Component({
selector: 'sm-user-credentials',
templateUrl: './user-credentials.component.html',
styleUrls: ['./user-credentials.component.scss']
styleUrls: ['./user-credentials.component.scss'],
standalone: false
})
export class UserCredentialsComponent implements OnInit, OnDestroy {
public credentials$: Observable<{ [workspaceId: string]: CredentialKeyExt[] }>;
public credentials$: Observable<Record<string, CredentialKeyExt[]>>;
private newCredentialSub: Subscription;
creatingCredentials = false;
private user: GetCurrentUserResponseUserObject;

View File

@@ -3,7 +3,8 @@ import {Component, input} from '@angular/core';
@Component({
selector: 'sm-user-data',
templateUrl: './user-data.component.html',
styleUrls: ['./user-data.component.scss']
styleUrls: ['./user-data.component.scss'],
standalone: false
})
export class UserDataComponent {

View File

@@ -1,9 +1,7 @@
<section>
<header>
<div class="title">USER PREFERENCES</div>
</header>
<main class="main">
<sm-profile-preferences></sm-profile-preferences>
<sm-profile-key-storage></sm-profile-key-storage>
</main>
</section>
<header>
<div class="title">USER PREFERENCES</div>
</header>
<main class="main">
<sm-profile-preferences class="section"></sm-profile-preferences>
<sm-profile-key-storage></sm-profile-key-storage>
</main>

View File

@@ -1,21 +1,24 @@
@import 'variables';
@use '../layout/layout';
:host {
header {
padding: 20px 24px;
}
display: block;
height: 100%;
padding-bottom: 12px;
.main {
padding: 0 24px;
}
.section {
margin: 6px 0 24px;
padding-bottom: 12px;
display: grid;
grid-template-rows: auto minmax(0, 1fr);
height: calc(100% - 64px);
padding-left: 24px;
}
.title {
color: $white;
color: var(--color-on-primary-container);
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
height: layout.$project-header-height;
}
}

View File

@@ -3,14 +3,13 @@ import {ProfilePreferencesComponent} from '@common/settings/admin/profile-prefer
import {ProfileKeyStorageComponent} from '@common/settings/admin/profile-key-storage/profile-key-storage.component';
@Component({
selector: 'sm-webapp-configuration',
standalone: true,
imports: [
ProfilePreferencesComponent,
ProfileKeyStorageComponent,
],
templateUrl: './webapp-configuration.component.html',
styleUrl: './webapp-configuration.component.scss'
selector: 'sm-webapp-configuration',
imports: [
ProfilePreferencesComponent,
ProfileKeyStorageComponent,
],
templateUrl: './webapp-configuration.component.html',
styleUrl: './webapp-configuration.component.scss'
})
export class WebappConfigurationComponent {

View File

@@ -54,7 +54,7 @@ const routes: Routes = [
workspaceNeutral: true,
route: '/settings/storage-credentials',
staticBreadcrumb: [[settingsBreadcrumb, {
name: 'Storage Credentials',
name: 'Storage Cleanup',
type: CrumbTypeEnum.SubFeature
}]]
}

View File

@@ -1,10 +1,10 @@
<mat-drawer-container class="settings-container">
<mat-drawer mode="side" opened>
<mat-list class="pointer" style="padding-top: 24px;">
<mat-list-item routerLinkActive="active" routerLink="profile"> Profile </mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="webapp-configuration"> Configuration </mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="workspace-configuration"> Workspace </mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="storage-credentials"> Storage Credentials </mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="profile">Profile</mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="webapp-configuration">Configuration</mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="workspace-configuration">Workspace</mat-list-item>
<mat-list-item routerLinkActive="active" routerLink="storage-credentials">Storage Cleanup</mat-list-item>
</mat-list>
</mat-drawer>
<mat-drawer-content>

View File

@@ -3,7 +3,9 @@ import {Component} from '@angular/core';
@Component({
selector: 'sm-settings',
templateUrl: './settings.component.html',
styleUrls: ['../../webapp-common/settings/settings.component.scss']
styleUrls: ['../../webapp-common/settings/settings.component.scss'],
standalone: false
})
export class SettingsComponent {
}

View File

@@ -50,10 +50,10 @@ import {settingsFeatureKey, settingsReducers} from '~/features/settings/settings
@NgModule({
declarations: [
SettingsComponent,
UserDataComponent,
UserCredentialsComponent,
AdminFooterActionsComponent,
UserDataComponent,
SettingsComponent,
UserCredentialsComponent,
AdminCredentialTableComponent,
AdminFooterComponent,
ProfileNameComponent,

View File

@@ -9,7 +9,8 @@ import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector: 'sm-orchestration',
templateUrl: './orchestration.component.html',
styleUrls: ['./orchestration.component.scss']
styleUrls: ['./orchestration.component.scss'],
standalone: false
})
export class OrchestrationComponent {
private store = inject(Store);

View File

@@ -1,10 +1,11 @@
import {Component} from '@angular/core';
import {QueuesMenuComponent} from '../../../webapp-common/workers-and-queues/dumb/queues-menu/queues-menu.component';
import {QueuesMenuComponent} from '@common/workers-and-queues/dumb/queues-menu/queues-menu.component';
@Component({
selector: 'sm-queues-menu-extended',
templateUrl: '../../../webapp-common/workers-and-queues/dumb/queues-menu/queues-menu.component.html',
styleUrls: ['../../../webapp-common/workers-and-queues/dumb/queues-menu/queues-menu.component.scss']
styleUrls: ['../../../webapp-common/workers-and-queues/dumb/queues-menu/queues-menu.component.scss'],
standalone: false
})
export class QueuesMenuExtendedComponent extends QueuesMenuComponent{
set contextMenu(data) {}

View File

@@ -4,7 +4,8 @@ import {WorkersComponent} from '@common/workers-and-queues/containers/workers/wo
import {QueuesComponent} from '@common/workers-and-queues/containers/queues/queues.component';
import {WorkersAndQueuesResolver} from '~/shared/resolvers/workers-and-queues.resolver';
import {CrumbTypeEnum} from '@common/layout/breadcrumbs/breadcrumbs.component';
import {OrchestrationComponent} from "~/features/workers-and-queues/orchestration.component";
import {OrchestrationComponent} from '~/features/workers-and-queues/orchestration.component';
import {resetContextMenuGuard} from '@common/shared/guards/resetContextMenuGuard.guard';
const wQBreadcrumb = [[{
name: 'WORKERS AND QUEUES',
@@ -14,6 +15,7 @@ export const routes: Routes = [
{
path: '',
component: OrchestrationComponent,
canDeactivate: [resetContextMenuGuard],
resolve: {
queuesManager: WorkersAndQueuesResolver
},

View File

@@ -110,7 +110,6 @@ import {MatButton, MatIconButton} from '@angular/material/button';
QueuesMenuComponent,
QueuesMenuExtendedComponent
],
})
export class WorkersAndQueuesModule {
}

View File

@@ -1,11 +1,9 @@
import {Component} from '@angular/core';
@Component({
selector: 'sm-header-user-menu-actions',
templateUrl: './header-user-menu-actions.component.html',
styleUrls: ['./header-user-menu-actions.component.scss']
selector: 'sm-header-user-menu-actions',
templateUrl: './header-user-menu-actions.component.html',
styleUrls: ['./header-user-menu-actions.component.scss'],
})
export class HeaderUserMenuActionsComponent {
constructor() {}
}

View File

@@ -20,7 +20,8 @@ import {MAT_TOOLTIP_DEFAULT_OPTIONS, MatTooltipDefaultOptions} from '@angular/ma
provide: MAT_TOOLTIP_DEFAULT_OPTIONS,
useValue: {showDelay: 0, position: 'right'} as MatTooltipDefaultOptions
},
]
],
standalone: false
})
export class SideNavComponent {
public store = inject(Store);

View File

@@ -32,7 +32,7 @@ export const EXPERIMENTS_STATUS_LABELS = {
[TaskStatusEnum.Completed] : 'Completed',
[TaskStatusEnum.Published] : 'Published',
[TaskStatusEnum.Failed] : 'Failed',
[TaskStatusEnum.Stopped] : 'Completed',
[TaskStatusEnum.Stopped] : 'Aborted',
[TaskStatusEnum.Closed] : 'Closed',
[TaskTypeEnum.Testing] : 'Testing',
[TaskTypeEnum.Training] : 'Training',

View File

@@ -23,12 +23,13 @@ import {NotifierNotificationComponent} from './notifier-notification.component';
* strategy onPush, which means that we handle change detection manually in order to get the best performance. (#perfmatters)
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush, // (#perfmatters)
host: {
class: 'notifier__container'
},
selector: 'notifier-container',
templateUrl: './notifier-container.component.html'
changeDetection: ChangeDetectionStrategy.OnPush, // (#perfmatters)
host: {
class: 'notifier__container'
},
selector: 'notifier-container',
templateUrl: './notifier-container.component.html',
standalone: false
})
export class NotifierContainerComponent implements OnDestroy, OnInit {

View File

@@ -16,20 +16,21 @@ import {NotifierTimerService} from '../services/notifier-timer.service';
* mouse movements.
*/
@Component({
changeDetection: ChangeDetectionStrategy.OnPush, // (#perfmatters)
host: {
'(click)': 'onNotificationClick()',
'(mouseout)': 'onNotificationMouseout()',
'(mouseover)': 'onNotificationMouseover()',
class: 'notifier__notification'
},
providers: [
// We provide the timer to the component's local injector, so that every notification components gets its own
// instance of the timer service, thus running their timers independently from each other
NotifierTimerService
],
selector: 'notifier-notification',
templateUrl: './notifier-notification.component.html'
changeDetection: ChangeDetectionStrategy.OnPush, // (#perfmatters)
host: {
'(click)': 'onNotificationClick()',
'(mouseout)': 'onNotificationMouseout()',
'(mouseover)': 'onNotificationMouseover()',
class: 'notifier__notification'
},
providers: [
// We provide the timer to the component's local injector, so that every notification components gets its own
// instance of the timer service, thus running their timers independently from each other
NotifierTimerService
],
selector: 'notifier-notification',
templateUrl: './notifier-notification.component.html',
standalone: false
})
export class NotifierNotificationComponent implements AfterViewInit {

View File

@@ -8,14 +8,14 @@
// We do *NOT* use the partial syntax, and also explicitely write out the file type, so compiling works properly.
// Core
@import "styles/core";
@forward "styles/core";
// Themes
@import "styles/themes/theme-material";
@forward "styles/themes/theme-material";
// Types
@import "styles/types/type-default";
@import "styles/types/type-error";
@import "styles/types/type-info";
@import "styles/types/type-success";
@import "styles/types/type-warning";
@forward "styles/types/type-default";
@forward "styles/types/type-error";
@forward "styles/types/type-info";
@forward "styles/types/type-success";
@forward "styles/types/type-warning";

View File

@@ -5,6 +5,9 @@
.notifier {
&__container {
ul {
margin: 0;
}
* {
box-sizing: border-box;

View File

@@ -18,6 +18,9 @@ $notifier-shadow-color: rgba(0, 0, 0, .2) !default;
&--warning {
border-left-color: var(--color-warning);
}
&--warn {
border-left-color: var(--color-warning);
}
&--error {
border-left-color: var(--color-failed);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,301 +1,309 @@
@use "sass:string";
$icomoon-font-family: "trains" !default;
$icomoon-font-path: "fonts" !default;
$al-ico-enterprise-rocket: unquote('"\\ea19"');
$al-ico-no-data-table: unquote('"\\ea18"');
$al-ico-palette: unquote('"\\e900"');
$al-ico-log: unquote('"\\ea16"');
$al-ico-debug-samples: unquote('"\\ea15"');
$al-ico-artifacts: unquote('"\\ea14"');
$al-ico-close-circle: unquote('"\\ea13"');
$al-ico-bullhorn: unquote('"\\ea12"');
$al-ico-view-horizontal: unquote('"\\ea11"');
$al-ico-model-endpoints: unquote('"\\ea08"');
$al-ico-vm: unquote('"\\ea0c"');
$al-ico-retry: unquote('"\\ea07"');
$al-ico-link-off: unquote('"\\ea05"');
$al-ico-policy: unquote('"\\e9d5"');
$al-ico-info-group: unquote('"\\e944"');
$al-ico-advanced-filters: unquote('"\\e911"');
$al-ico-triggers-scheduled: unquote('"\\ea06"');
$al-ico-queue: unquote('"\\ea01"');
$al-ico-link-plus: unquote('"\\ea02"');
$al-ico-drag-vertical: unquote('"\\ea03"');
$al-ico-drag-horizontal: unquote('"\\ea04"');
$al-ico-admin-support: unquote('"\\ea00"');
$al-ico-scatter-view: unquote('"\\e9ff"');
$al-ico-schedulers: unquote('"\\e9fb"');
$al-ico-triggers: unquote('"\\e9fc"');
$al-ico-autoscalers: unquote('"\\e9fd"');
$al-ico-automation: unquote('"\\e9fe"');
$al-ico-charts-view: unquote('"\\e9f9"');
$al-ico-compact-view: unquote('"\\e9fa"');
$al-ico-tune: unquote('"\\e9f8"');
$al-ico-equal-outline: unquote('"\\e9f7"');
$al-ico-maximize: unquote('"\\e9f6"');
$al-ico-gpu-card: unquote('"\\e9f4"');
$al-ico-legend: unquote('"\\e9f3"');
$al-ico-model-filled: unquote('"\\e9f2"');
$al-ico-type-report: unquote('"\\e9f1"');
$al-ico-info-circle-outline: unquote('"\\e9f0"');
$al-ico-ghost: unquote('"\\e9ef"');
$al-ico-flat-view: unquote('"\\e9ee"');
$al-ico-camera: unquote('"\\e9ed"');
$al-ico-markdown: unquote('"\\e9ec"');
$al-ico-hor-expand: unquote('"\\e9ea"');
$al-ico-hor-minimize: unquote('"\\e9eb"');
$al-ico-pdf: unquote('"\\e9e9"');
$al-ico-reports: unquote('"\\e9e8"');
$al-ico-gpu: unquote('"\\e9e7"');
$al-ico-project-path: unquote('"\\e9e6"');
$al-ico-tree-view: unquote('"\\e9e5"');
$al-ico-sort-asc: unquote('"\\e9e3"');
$al-ico-sort-desc: unquote('"\\e9e4"');
$al-ico-grid-view: unquote('"\\e9e0"');
$al-ico-connect: unquote('"\\e9de"');
$al-ico-disconnect: unquote('"\\e9da"');
$al-ico-trash-all: unquote('"\\e9d9"');
$al-ico-drag: unquote('"\\e9d8"');
$al-ico-py: unquote('"\\e9d7"');
$al-ico-file: unquote('"\\e9d3"');
$al-ico-txt: unquote('"\\e9d6"');
$al-ico-pkl: unquote('"\\e9cd"');
$al-ico-image: unquote('"\\e958"');
$al-ico-zip: unquote('"\\e9ce"');
$al-ico-code-file: unquote('"\\e9a4"');
$al-ico-audio: unquote('"\\e9d0"');
$al-ico-upload: unquote('"\\e9cc"');
$al-ico-min-panel: unquote('"\\e9ca"');
$al-ico-max-panel: unquote('"\\e9cb"');
$al-ico-datasets: unquote('"\\e90b"');
$al-ico-t-logo-b: unquote('"\\e908"');
$al-ico-bars-menu: unquote('"\\e933"');
$al-ico-home: unquote('"\\e909"');
$al-ico-projects: unquote('"\\e90a"');
$al-ico-queues: unquote('"\\e90c"');
$al-ico-annotator: unquote('"\\e90d"');
$al-ico-account: unquote('"\\e998"');
$al-ico-compare: unquote('"\\e93f"');
$al-ico-archive: unquote('"\\e92c"');
$al-ico-cloud-head: unquote('"\\e9a6"');
$al-ico-how-to: unquote('"\\e997"');
$al-ico-compare-c: unquote('"\\e93a"');
$al-ico-published-title: unquote('"\\e9d2"');
$al-ico-publish: unquote('"\\e9cf"');
$al-ico-unpublish: unquote('"\\ea0f"');
$al-ico-published: unquote('"\\e9d1"');
$al-ico-info: unquote('"\\e99b"');
$al-ico-warn: unquote('"\\ea10"');
$al-ico-settings: unquote('"\\e9e2"');
$al-ico-trash: unquote('"\\ea0b"');
$al-ico-arrow-from-right: unquote('"\\e92e"');
$al-ico-arrow-from-left: unquote('"\\e92d"');
$al-ico-arrow-to-bottom: unquote('"\\e92f"');
$al-ico-arrow-to-top: unquote('"\\e930"');
$al-ico-training: unquote('"\\e90e"');
$al-ico-test: unquote('"\\ea09"');
$al-ico-testing: unquote('"\\ea0a"');
$al-ico-type-training: unquote('"\\ea33"');
$al-ico-type-testing: unquote('"\\ea32"');
$al-ico-type-data-processing: unquote('"\\ea2c"');
$al-ico-type-qc: unquote('"\\ea30"');
$al-ico-type-service: unquote('"\\ea31"');
$al-ico-type-optimizer: unquote('"\\ea2f"');
$al-ico-type-monitor: unquote('"\\ea2e"');
$al-ico-type-inference: unquote('"\\ea2d"');
$al-ico-type-application: unquote('"\\ea29"');
$al-ico-type-controller: unquote('"\\ea2a"');
$al-ico-type-custom: unquote('"\\ea2b"');
$al-ico-how-to1: unquote('"\\e90f"');
$al-ico-model: unquote('"\\e910"');
$al-ico-dialog-x: unquote('"\\e980"');
$al-ico-temp-image: unquote('"\\e912"');
$al-ico-temp-list-alt: unquote('"\\e913"');
$al-ico-dog: unquote('"\\e914"');
$al-ico-alert-purple: unquote('"\\e927"');
$al-ico-back: unquote('"\\e931"');
$al-ico-cat: unquote('"\\e936"');
$al-ico-clone: unquote('"\\e937"');
$al-ico-completed: unquote('"\\e940"');
$al-ico-data-audit: unquote('"\\e943"');
$al-ico-download-frames: unquote('"\\e982"');
$al-ico-dropdown-arrow: unquote('"\\e984"');
$al-ico-ellipse-icon: unquote('"\\e986"');
$al-ico-filter: unquote('"\\e992"');
$al-ico-fit: unquote('"\\e995"');
$al-ico-id: unquote('"\\e999"');
$al-ico-info-max: unquote('"\\e99c"');
$al-ico-info-min: unquote('"\\e99d"');
$al-ico-keypoint: unquote('"\\e99f"');
$al-ico-list-view: unquote('"\\e9a5"');
$al-ico-minus: unquote('"\\e9a7"');
$al-ico-previous: unquote('"\\e9bb"');
$al-ico-next: unquote('"\\e9af"');
$al-ico-plus: unquote('"\\e9b8"');
$al-ico-polygon: unquote('"\\e9b9"');
$al-ico-pytorch-icon: unquote('"\\e9d4"');
$al-ico-rectangle: unquote('"\\e9db"');
$al-ico-running: unquote('"\\e9df"');
$al-ico-setup: unquote('"\\ea0d"');
$al-ico-undo: unquote('"\\ea0e"');
$al-ico-zoom-1-to-1: unquote('"\\ea25"');
$al-ico-zoom-in: unquote('"\\ea26"');
$al-ico-zoom-out: unquote('"\\ea27"');
$al-ico-search: unquote('"\\e9e1"');
$al-ico-zoom-to-fit: unquote('"\\ea28"');
$al-ico-reset: unquote('"\\e9dd"');
$al-ico-import: unquote('"\\e99e"');
$al-ico-export: unquote('"\\e9b6"');
$al-ico-between: unquote('"\\e934"');
$al-ico-next-batch: unquote('"\\e9b0"');
$al-ico-prev-batch: unquote('"\\e9ba"');
$al-ico-back-to-top: unquote('"\\e932"');
$al-ico-redo: unquote('"\\e9dc"');
$al-ico-download: unquote('"\\e981"');
$al-ico-edit: unquote('"\\e983"');
$al-ico-pending: unquote('"\\e9b7"');
$al-ico-sqr-empty: unquote('"\\e925"');
$al-ico-success: unquote('"\\e9f5"');
$al-ico-alert-outline: unquote('"\\e926"');
$al-ico-github: unquote('"\\e915"');
$al-ico-description: unquote('"\\e916"');
$al-ico-line-expand: unquote('"\\e901"');
$al-ico-user-logout: unquote('"\\e902"');
$al-ico-documentation: unquote('"\\e919"');
$al-ico-frame-rule: unquote('"\\e91a"');
$al-ico-source-rule: unquote('"\\e91b"');
$al-ico-get-link-dialog: unquote('"\\e91d"');
$al-ico-iteration: unquote('"\\e91e"');
$al-ico-mapping: unquote('"\\e91f"');
$al-ico-labels: unquote('"\\e920"');
$al-ico-augmentation: unquote('"\\e921"');
$al-ico-filter-outlined: unquote('"\\e922"');
$al-ico-input-data: unquote('"\\e923"');
$al-ico-help-outlined: unquote('"\\e924"');
$al-ico-email: unquote('"\\e928"');
$al-ico-tips: unquote('"\\e929"');
$al-ico-caret-right: unquote('"\\e92a"');
$al-ico-currently-active: unquote('"\\e92b"');
$al-ico-complete: unquote('"\\e92b"');
$al-ico-access-key: unquote('"\\e935"');
$al-ico-alert: unquote('"\\e9b3"');
$al-ico-users: unquote('"\\e91c"');
$al-ico-exit-archive: unquote('"\\e938"');
$al-ico-calendar: unquote('"\\e939"');
$al-ico-time: unquote('"\\e93b"');
$al-ico-add: unquote('"\\e93c"');
$al-ico-time-colon: unquote('"\\e93d"');
$al-ico-regex: unquote('"\\e93e"');
$al-ico-filter-on-path1: unquote('"\\e941"');
$al-ico-filter-on-path2: unquote('"\\e942"');
$al-ico-filter-off: unquote('"\\e945"');
$al-ico-sort-off: unquote('"\\e946"');
$al-ico-sort-on-down-path1: unquote('"\\e947"');
$al-ico-sort-on-down-path2: unquote('"\\e948"');
$al-ico-sort-on-up-path1: unquote('"\\e949"');
$al-ico-sort-on-up-path2: unquote('"\\e94a"');
$al-ico-arrow-left: unquote('"\\e94b"');
$al-ico-arrow-right: unquote('"\\e94c"');
$al-ico-step-backward: unquote('"\\e94d"');
$al-ico-step-forward: unquote('"\\e94e"');
$al-ico-backward: unquote('"\\e94f"');
$al-ico-forward: unquote('"\\e950"');
$al-ico-copy-to-clipboard: unquote('"\\e951"');
$al-ico-card-example: unquote('"\\e952"');
$al-ico-bold: unquote('"\\e953"');
$al-ico-italic: unquote('"\\e954"');
$al-ico-heading: unquote('"\\e955"');
$al-ico-quote: unquote('"\\e956"');
$al-ico-link: unquote('"\\e957"');
$al-ico-code: unquote('"\\e959"');
$al-ico-list-bulleted: unquote('"\\e95a"');
$al-ico-list-numbered: unquote('"\\e95b"');
$al-ico-me: unquote('"\\e95c"');
$al-ico-team: unquote('"\\e95d"');
$al-ico-task-desc: unquote('"\\e95e"');
$al-ico-dots: unquote('"\\e95f"');
$al-ico-move-to: unquote('"\\e960"');
$al-ico-abort: unquote('"\\e961"');
$al-ico-extend: unquote('"\\e962"');
$al-ico-reset-exp: unquote('"\\e963"');
$al-ico-tag: unquote('"\\e964"');
$al-ico-shared-item: unquote('"\\e965"');
$al-ico-restore: unquote('"\\e966"');
$al-ico-workers: unquote('"\\e967"');
$al-ico-dots-v-menu: unquote('"\\e96a"');
$al-ico-d-menu-down: unquote('"\\e96b"');
$al-ico-d-menu-up: unquote('"\\e96c"');
$al-ico-slash: unquote('"\\e96d"');
$al-ico-info-circle: unquote('"\\e96e"');
$al-ico-annotate: unquote('"\\e96f"');
$al-ico-task-desc-outline: unquote('"\\e970"');
$al-ico-manage-queue: unquote('"\\e968"');
$al-ico-enqueue: unquote('"\\e969"');
$al-ico-dequeue: unquote('"\\e971"');
$al-ico-applications: unquote('"\\e972"');
$al-ico-ico-chevron-up: unquote('"\\e973"');
$al-ico-ico-chevron-down: unquote('"\\e974"');
$al-ico-no-data-graph: unquote('"\\e975"');
$al-ico-no-scatter-graph: unquote('"\\e976"');
$al-ico-auto-refresh-play-path1: unquote('"\\e977"');
$al-ico-auto-refresh-play-path2: unquote('"\\e978"');
$al-ico-auto-refresh-pause-path1: unquote('"\\e979"');
$al-ico-auto-refresh-pause-path2: unquote('"\\e97a"');
$al-ico-sqr-ok: unquote('"\\e97b"');
$al-ico-sqr-cancel: unquote('"\\e97c"');
$al-ico-queue-lg: unquote('"\\e97d"');
$al-ico-started-lg: unquote('"\\e97e"');
$al-ico-admin: unquote('"\\e97f"');
$al-ico-projects-outlined: unquote('"\\e985"');
$al-ico-datasets-outlined: unquote('"\\e987"');
$al-ico-hide: unquote('"\\e988"');
$al-ico-show: unquote('"\\e989"');
$al-ico-metadata: unquote('"\\e98a"');
$al-ico-filter-reset-path1: unquote('"\\e98b"');
$al-ico-filter-reset-path2: unquote('"\\e98c"');
$al-ico-version-label: unquote('"\\e98d"');
$al-ico-plugin: unquote('"\\e98e"');
$al-ico-abort-all: unquote('"\\e98f"');
$al-ico-refresh: unquote('"\\e990"');
$al-ico-rocket: unquote('"\\e991"');
$al-ico-logout: unquote('"\\e993"');
$al-ico-settings-alert-path1: unquote('"\\e994"');
$al-ico-settings-alert-path2: unquote('"\\e996"');
$al-ico-platform: unquote('"\\e99a"');
$al-ico-creditcard: unquote('"\\e9a0"');
$al-ico-star: unquote('"\\e9a1"');
$al-ico-email-check: unquote('"\\e9a2"');
$al-ico-slack: unquote('"\\e9a3"');
$al-ico-youtube: unquote('"\\e9a8"');
$al-ico-lock: unquote('"\\e9a9"');
$al-ico-lock-open: unquote('"\\e9aa"');
$al-ico-no-code: unquote('"\\e9ab"');
$al-ico-calendar-checked: unquote('"\\e9ad"');
$al-ico-no-source: unquote('"\\e9ae"');
$al-ico-arrow-up: unquote('"\\e9b1"');
$al-ico-arrow-down: unquote('"\\e9b2"');
$al-ico-error-circle: unquote('"\\e9b4"');
$al-ico-pipelines: unquote('"\\e9b5"');
$al-ico-console: unquote('"\\e9bc"');
$al-ico-link-arrow: unquote('"\\e9bf"');
$al-ico-broken-file: unquote('"\\e9c0"');
$al-ico-run: unquote('"\\e9c1"');
$al-ico-table-view: unquote('"\\e9c2"');
$al-ico-experiment-view: unquote('"\\e9c3"');
$al-ico-code-square: unquote('"\\e9c4"');
$al-ico-video: unquote('"\\e9c5"');
$al-ico-less-than: unquote('"\\e9c6"');
$al-ico-greater-than: unquote('"\\e9c7"');
$al-ico-eye-outline: unquote('"\\e9c8"');
$al-ico-csv: unquote('"\\e9c9"');
$al-ico-status-draft: unquote('"\\e903"');
$al-ico-status-published: unquote('"\\e906"');
$al-ico-status-aborted-sec: unquote('"\\e918"');
$al-ico-status-pending: unquote('"\\e904"');
$al-ico-status-skipped: unquote('"\\e9bd"');
$al-ico-status-cached: unquote('"\\e9be"');
$al-ico-status-executed: unquote('"\\e9ac"');
$al-ico-status-running: unquote('"\\e905"');
$al-ico-status-failed: unquote('"\\e907"');
$al-ico-status-aborted: unquote('"\\e917"');
$al-ico-status-completed: unquote('"\\ea17"');
$al-ico-app-gateway: string.unquote('"\\ea20"');
$al-ico-distance: string.unquote('"\\ea1f"');
$al-ico-pin: string.unquote('"\\ea1d"');
$al-ico-pin-off: string.unquote('"\\ea1c"');
$al-ico-nearest: string.unquote('"\\ea1e"');
$al-ico-no-data-code: string.unquote('"\\ea1b"');
$al-ico-no-data-markdown: string.unquote('"\\ea1a"');
$al-ico-enterprise-rocket: string.unquote('"\\ea19"');
$al-ico-no-data-table: string.unquote('"\\ea18"');
$al-ico-palette: string.unquote('"\\e900"');
$al-ico-log: string.unquote('"\\ea16"');
$al-ico-debug-samples: string.unquote('"\\ea15"');
$al-ico-artifacts: string.unquote('"\\ea14"');
$al-ico-close-circle: string.unquote('"\\ea13"');
$al-ico-bullhorn: string.unquote('"\\ea12"');
$al-ico-view-horizontal: string.unquote('"\\ea11"');
$al-ico-model-endpoints: string.unquote('"\\ea08"');
$al-ico-vm: string.unquote('"\\ea0c"');
$al-ico-retry: string.unquote('"\\ea07"');
$al-ico-link-off: string.unquote('"\\ea05"');
$al-ico-policy: string.unquote('"\\e9d5"');
$al-ico-info-group: string.unquote('"\\e944"');
$al-ico-advanced-filters: string.unquote('"\\e911"');
$al-ico-triggers-scheduled: string.unquote('"\\ea06"');
$al-ico-queue: string.unquote('"\\ea01"');
$al-ico-link-plus: string.unquote('"\\ea02"');
$al-ico-drag-vertical: string.unquote('"\\ea03"');
$al-ico-drag-horizontal: string.unquote('"\\ea04"');
$al-ico-admin-support: string.unquote('"\\ea00"');
$al-ico-scatter-view: string.unquote('"\\e9ff"');
$al-ico-schedulers: string.unquote('"\\e9fb"');
$al-ico-triggers: string.unquote('"\\e9fc"');
$al-ico-autoscalers: string.unquote('"\\e9fd"');
$al-ico-automation: string.unquote('"\\e9fe"');
$al-ico-charts-view: string.unquote('"\\e9f9"');
$al-ico-compact-view: string.unquote('"\\e9fa"');
$al-ico-tune: string.unquote('"\\e9f8"');
$al-ico-equal-outline: string.unquote('"\\e9f7"');
$al-ico-maximize: string.unquote('"\\e9f6"');
$al-ico-gpu-card: string.unquote('"\\e9f4"');
$al-ico-legend: string.unquote('"\\e9f3"');
$al-ico-model-filled: string.unquote('"\\e9f2"');
$al-ico-type-report: string.unquote('"\\e9f1"');
$al-ico-info-circle-outline: string.unquote('"\\e9f0"');
$al-ico-ghost: string.unquote('"\\e9ef"');
$al-ico-flat-view: string.unquote('"\\e9ee"');
$al-ico-camera: string.unquote('"\\e9ed"');
$al-ico-markdown: string.unquote('"\\e9ec"');
$al-ico-hor-expand: string.unquote('"\\e9ea"');
$al-ico-hor-minimize: string.unquote('"\\e9eb"');
$al-ico-pdf: string.unquote('"\\e9e9"');
$al-ico-reports: string.unquote('"\\e9e8"');
$al-ico-gpu: string.unquote('"\\e9e7"');
$al-ico-project-path: string.unquote('"\\e9e6"');
$al-ico-tree-view: string.unquote('"\\e9e5"');
$al-ico-sort-asc: string.unquote('"\\e9e3"');
$al-ico-sort-desc: string.unquote('"\\e9e4"');
$al-ico-grid-view: string.unquote('"\\e9e0"');
$al-ico-connect: string.unquote('"\\e9de"');
$al-ico-disconnect: string.unquote('"\\e9da"');
$al-ico-trash-all: string.unquote('"\\e9d9"');
$al-ico-drag: string.unquote('"\\e9d8"');
$al-ico-py: string.unquote('"\\e9d7"');
$al-ico-file: string.unquote('"\\e9d3"');
$al-ico-txt: string.unquote('"\\e9d6"');
$al-ico-pkl: string.unquote('"\\e9cd"');
$al-ico-image: string.unquote('"\\e958"');
$al-ico-zip: string.unquote('"\\e9ce"');
$al-ico-code-file: string.unquote('"\\e9a4"');
$al-ico-audio: string.unquote('"\\e9d0"');
$al-ico-upload: string.unquote('"\\e9cc"');
$al-ico-min-panel: string.unquote('"\\e9ca"');
$al-ico-max-panel: string.unquote('"\\e9cb"');
$al-ico-datasets: string.unquote('"\\e90b"');
$al-ico-t-logo-b: string.unquote('"\\e908"');
$al-ico-bars-menu: string.unquote('"\\e933"');
$al-ico-home: string.unquote('"\\e909"');
$al-ico-projects: string.unquote('"\\e90a"');
$al-ico-queues: string.unquote('"\\e90c"');
$al-ico-annotator: string.unquote('"\\e90d"');
$al-ico-account: string.unquote('"\\e998"');
$al-ico-compare: string.unquote('"\\e93f"');
$al-ico-archive: string.unquote('"\\e92c"');
$al-ico-cloud-head: string.unquote('"\\e9a6"');
$al-ico-how-to: string.unquote('"\\e997"');
$al-ico-compare-c: string.unquote('"\\e93a"');
$al-ico-published-title: string.unquote('"\\e9d2"');
$al-ico-publish: string.unquote('"\\e9cf"');
$al-ico-unpublish: string.unquote('"\\ea0f"');
$al-ico-published: string.unquote('"\\e9d1"');
$al-ico-info: string.unquote('"\\e99b"');
$al-ico-warn: string.unquote('"\\ea10"');
$al-ico-settings: string.unquote('"\\e9e2"');
$al-ico-trash: string.unquote('"\\ea0b"');
$al-ico-arrow-from-right: string.unquote('"\\e92e"');
$al-ico-arrow-from-left: string.unquote('"\\e92d"');
$al-ico-arrow-to-bottom: string.unquote('"\\e92f"');
$al-ico-arrow-to-top: string.unquote('"\\e930"');
$al-ico-training: string.unquote('"\\e90e"');
$al-ico-test: string.unquote('"\\ea09"');
$al-ico-testing: string.unquote('"\\ea0a"');
$al-ico-type-training: string.unquote('"\\ea33"');
$al-ico-type-testing: string.unquote('"\\ea32"');
$al-ico-type-data-processing: string.unquote('"\\ea2c"');
$al-ico-type-qc: string.unquote('"\\ea30"');
$al-ico-type-service: string.unquote('"\\ea31"');
$al-ico-type-optimizer: string.unquote('"\\ea2f"');
$al-ico-type-monitor: string.unquote('"\\ea2e"');
$al-ico-type-inference: string.unquote('"\\ea2d"');
$al-ico-type-application: string.unquote('"\\ea29"');
$al-ico-type-controller: string.unquote('"\\ea2a"');
$al-ico-type-custom: string.unquote('"\\ea2b"');
$al-ico-how-to1: string.unquote('"\\e90f"');
$al-ico-model: string.unquote('"\\e910"');
$al-ico-dialog-x: string.unquote('"\\e980"');
$al-ico-temp-image: string.unquote('"\\e912"');
$al-ico-temp-list-alt: string.unquote('"\\e913"');
$al-ico-dog: string.unquote('"\\e914"');
$al-ico-alert-purple: string.unquote('"\\e927"');
$al-ico-back: string.unquote('"\\e931"');
$al-ico-cat: string.unquote('"\\e936"');
$al-ico-clone: string.unquote('"\\e937"');
$al-ico-completed: string.unquote('"\\e940"');
$al-ico-data-audit: string.unquote('"\\e943"');
$al-ico-download-frames: string.unquote('"\\e982"');
$al-ico-dropdown-arrow: string.unquote('"\\e984"');
$al-ico-ellipse-icon: string.unquote('"\\e986"');
$al-ico-filter: string.unquote('"\\e992"');
$al-ico-fit: string.unquote('"\\e995"');
$al-ico-id: string.unquote('"\\e999"');
$al-ico-info-max: string.unquote('"\\e99c"');
$al-ico-info-min: string.unquote('"\\e99d"');
$al-ico-keypoint: string.unquote('"\\e99f"');
$al-ico-list-view: string.unquote('"\\e9a5"');
$al-ico-minus: string.unquote('"\\e9a7"');
$al-ico-previous: string.unquote('"\\e9bb"');
$al-ico-next: string.unquote('"\\e9af"');
$al-ico-plus: string.unquote('"\\e9b8"');
$al-ico-polygon: string.unquote('"\\e9b9"');
$al-ico-pytorch-icon: string.unquote('"\\e9d4"');
$al-ico-rectangle: string.unquote('"\\e9db"');
$al-ico-running: string.unquote('"\\e9df"');
$al-ico-setup: string.unquote('"\\ea0d"');
$al-ico-undo: string.unquote('"\\ea0e"');
$al-ico-zoom-1-to-1: string.unquote('"\\ea25"');
$al-ico-zoom-in: string.unquote('"\\ea26"');
$al-ico-zoom-out: string.unquote('"\\ea27"');
$al-ico-search: string.unquote('"\\e9e1"');
$al-ico-zoom-to-fit: string.unquote('"\\ea28"');
$al-ico-reset: string.unquote('"\\e9dd"');
$al-ico-import: string.unquote('"\\e99e"');
$al-ico-export: string.unquote('"\\e9b6"');
$al-ico-between: string.unquote('"\\e934"');
$al-ico-next-batch: string.unquote('"\\e9b0"');
$al-ico-prev-batch: string.unquote('"\\e9ba"');
$al-ico-back-to-top: string.unquote('"\\e932"');
$al-ico-redo: string.unquote('"\\e9dc"');
$al-ico-download: string.unquote('"\\e981"');
$al-ico-edit: string.unquote('"\\e983"');
$al-ico-pending: string.unquote('"\\e9b7"');
$al-ico-sqr-empty: string.unquote('"\\e925"');
$al-ico-success: string.unquote('"\\e9f5"');
$al-ico-alert-outline: string.unquote('"\\e926"');
$al-ico-github: string.unquote('"\\e915"');
$al-ico-description: string.unquote('"\\e916"');
$al-ico-line-expand: string.unquote('"\\e901"');
$al-ico-user-logout: string.unquote('"\\e902"');
$al-ico-documentation: string.unquote('"\\e919"');
$al-ico-frame-rule: string.unquote('"\\e91a"');
$al-ico-source-rule: string.unquote('"\\e91b"');
$al-ico-get-link-dialog: string.unquote('"\\e91d"');
$al-ico-iteration: string.unquote('"\\e91e"');
$al-ico-mapping: string.unquote('"\\e91f"');
$al-ico-labels: string.unquote('"\\e920"');
$al-ico-augmentation: string.unquote('"\\e921"');
$al-ico-filter-outlined: string.unquote('"\\e922"');
$al-ico-input-data: string.unquote('"\\e923"');
$al-ico-help-outlined: string.unquote('"\\e924"');
$al-ico-email: string.unquote('"\\e928"');
$al-ico-tips: string.unquote('"\\e929"');
$al-ico-caret-right: string.unquote('"\\e92a"');
$al-ico-currently-active: string.unquote('"\\e92b"');
$al-ico-complete: string.unquote('"\\e92b"');
$al-ico-access-key: string.unquote('"\\e935"');
$al-ico-alert: string.unquote('"\\e9b3"');
$al-ico-users: string.unquote('"\\e91c"');
$al-ico-exit-archive: string.unquote('"\\e938"');
$al-ico-calendar: string.unquote('"\\e939"');
$al-ico-time: string.unquote('"\\e93b"');
$al-ico-add: string.unquote('"\\e93c"');
$al-ico-time-colon: string.unquote('"\\e93d"');
$al-ico-regex: string.unquote('"\\e93e"');
$al-ico-filter-on-path1: string.unquote('"\\e941"');
$al-ico-filter-on-path2: string.unquote('"\\e942"');
$al-ico-filter-off: string.unquote('"\\e945"');
$al-ico-sort-off: string.unquote('"\\e946"');
$al-ico-sort-on-down-path1: string.unquote('"\\e947"');
$al-ico-sort-on-down-path2: string.unquote('"\\e948"');
$al-ico-sort-on-up-path1: string.unquote('"\\e949"');
$al-ico-sort-on-up-path2: string.unquote('"\\e94a"');
$al-ico-arrow-left: string.unquote('"\\e94b"');
$al-ico-arrow-right: string.unquote('"\\e94c"');
$al-ico-step-backward: string.unquote('"\\e94d"');
$al-ico-step-forward: string.unquote('"\\e94e"');
$al-ico-backward: string.unquote('"\\e94f"');
$al-ico-forward: string.unquote('"\\e950"');
$al-ico-copy-to-clipboard: string.unquote('"\\e951"');
$al-ico-card-example: string.unquote('"\\e952"');
$al-ico-bold: string.unquote('"\\e953"');
$al-ico-italic: string.unquote('"\\e954"');
$al-ico-heading: string.unquote('"\\e955"');
$al-ico-quote: string.unquote('"\\e956"');
$al-ico-link: string.unquote('"\\e957"');
$al-ico-code: string.unquote('"\\e959"');
$al-ico-list-bulleted: string.unquote('"\\e95a"');
$al-ico-list-numbered: string.unquote('"\\e95b"');
$al-ico-me: string.unquote('"\\e95c"');
$al-ico-team: string.unquote('"\\e95d"');
$al-ico-task-desc: string.unquote('"\\e95e"');
$al-ico-dots: string.unquote('"\\e95f"');
$al-ico-move-to: string.unquote('"\\e960"');
$al-ico-abort: string.unquote('"\\e961"');
$al-ico-extend: string.unquote('"\\e962"');
$al-ico-reset-exp: string.unquote('"\\e963"');
$al-ico-tag: string.unquote('"\\e964"');
$al-ico-shared-item: string.unquote('"\\e965"');
$al-ico-restore: string.unquote('"\\e966"');
$al-ico-workers: string.unquote('"\\e967"');
$al-ico-dots-v-menu: string.unquote('"\\e96a"');
$al-ico-d-menu-down: string.unquote('"\\e96b"');
$al-ico-d-menu-up: string.unquote('"\\e96c"');
$al-ico-slash: string.unquote('"\\e96d"');
$al-ico-info-circle: string.unquote('"\\e96e"');
$al-ico-annotate: string.unquote('"\\e96f"');
$al-ico-task-desc-outline: string.unquote('"\\e970"');
$al-ico-manage-queue: string.unquote('"\\e968"');
$al-ico-enqueue: string.unquote('"\\e969"');
$al-ico-dequeue: string.unquote('"\\e971"');
$al-ico-applications: string.unquote('"\\e972"');
$al-ico-ico-chevron-up: string.unquote('"\\e973"');
$al-ico-ico-chevron-down: string.unquote('"\\e974"');
$al-ico-no-data-graph: string.unquote('"\\e975"');
$al-ico-no-scatter-graph: string.unquote('"\\e976"');
$al-ico-auto-refresh-play-path1: string.unquote('"\\e977"');
$al-ico-auto-refresh-play-path2: string.unquote('"\\e978"');
$al-ico-auto-refresh-pause-path1: string.unquote('"\\e979"');
$al-ico-auto-refresh-pause-path2: string.unquote('"\\e97a"');
$al-ico-sqr-ok: string.unquote('"\\e97b"');
$al-ico-sqr-cancel: string.unquote('"\\e97c"');
$al-ico-queue-lg: string.unquote('"\\e97d"');
$al-ico-started-lg: string.unquote('"\\e97e"');
$al-ico-admin: string.unquote('"\\e97f"');
$al-ico-projects-outlined: string.unquote('"\\e985"');
$al-ico-datasets-outlined: string.unquote('"\\e987"');
$al-ico-hide: string.unquote('"\\e988"');
$al-ico-show: string.unquote('"\\e989"');
$al-ico-metadata: string.unquote('"\\e98a"');
$al-ico-filter-reset-path1: string.unquote('"\\e98b"');
$al-ico-filter-reset-path2: string.unquote('"\\e98c"');
$al-ico-version-label: string.unquote('"\\e98d"');
$al-ico-plugin: string.unquote('"\\e98e"');
$al-ico-abort-all: string.unquote('"\\e98f"');
$al-ico-refresh: string.unquote('"\\e990"');
$al-ico-rocket: string.unquote('"\\e991"');
$al-ico-logout: string.unquote('"\\e993"');
$al-ico-settings-alert-path1: string.unquote('"\\e994"');
$al-ico-settings-alert-path2: string.unquote('"\\e996"');
$al-ico-platform: string.unquote('"\\e99a"');
$al-ico-creditcard: string.unquote('"\\e9a0"');
$al-ico-star: string.unquote('"\\e9a1"');
$al-ico-email-check: string.unquote('"\\e9a2"');
$al-ico-slack: string.unquote('"\\e9a3"');
$al-ico-youtube: string.unquote('"\\e9a8"');
$al-ico-lock: string.unquote('"\\e9a9"');
$al-ico-lock-open: string.unquote('"\\e9aa"');
$al-ico-no-code: string.unquote('"\\e9ab"');
$al-ico-calendar-checked: string.unquote('"\\e9ad"');
$al-ico-no-source: string.unquote('"\\e9ae"');
$al-ico-arrow-up: string.unquote('"\\e9b1"');
$al-ico-arrow-down: string.unquote('"\\e9b2"');
$al-ico-error-circle: string.unquote('"\\e9b4"');
$al-ico-pipelines: string.unquote('"\\e9b5"');
$al-ico-console: string.unquote('"\\e9bc"');
$al-ico-link-arrow: string.unquote('"\\e9bf"');
$al-ico-broken-file: string.unquote('"\\e9c0"');
$al-ico-run: string.unquote('"\\e9c1"');
$al-ico-table-view: string.unquote('"\\e9c2"');
$al-ico-experiment-view: string.unquote('"\\e9c3"');
$al-ico-code-square: string.unquote('"\\e9c4"');
$al-ico-video: string.unquote('"\\e9c5"');
$al-ico-less-than: string.unquote('"\\e9c6"');
$al-ico-greater-than: string.unquote('"\\e9c7"');
$al-ico-eye-outline: string.unquote('"\\e9c8"');
$al-ico-csv: string.unquote('"\\e9c9"');
$al-ico-status-draft: string.unquote('"\\e903"');
$al-ico-status-published: string.unquote('"\\e906"');
$al-ico-status-aborted-sec: string.unquote('"\\e918"');
$al-ico-status-pending: string.unquote('"\\e904"');
$al-ico-status-skipped: string.unquote('"\\e9bd"');
$al-ico-status-cached: string.unquote('"\\e9be"');
$al-ico-status-executed: string.unquote('"\\e9ac"');
$al-ico-status-running: string.unquote('"\\e905"');
$al-ico-status-failed: string.unquote('"\\e907"');
$al-ico-status-aborted: string.unquote('"\\e917"');
$al-ico-status-completed: string.unquote('"\\ea17"');

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="88" height="88" viewBox="0 0 88 88">
<g>
<path fill="none" d="M0 0H88V88H0z" transform="translate(10 5) translate(-10 -5)"/>
<path fill="#2c3246" d="M30 40.37V30h3.05l3.05 3.813L39.15 30h3.05v10.37h-3.05v-5.947l-3.05 3.812-3.05-3.813v5.948zm19.063 0l-4.575-5.033h3.05V30h3.05v5.338h3.05z" transform="translate(10 5) translate(-21.187 -21.187)"/>
<path d="M26.587 89.6a2.608 2.608 0 0 1-2.606-2.609v-6.4h-5.929a2.608 2.608 0 0 1-2.609-2.606v-6.173H9.279A2.61 2.61 0 0 1 6.67 69.2v-65a2.608 2.608 0 0 1 2.609-2.6h49.082a2.608 2.608 0 0 1 2.609 2.6v6.164h6.164a2.61 2.61 0 0 1 2.609 2.609v6.4h5.926a2.608 2.608 0 0 1 2.609 2.609v65a2.61 2.61 0 0 1-2.609 2.618H26.587zm2.613-5.223h43.863V24.593h-3.32v53.384a2.61 2.61 0 0 1-2.609 2.609H29.2zm-8.537-9.009h43.862V15.584H60.97V69.2a2.61 2.61 0 0 1-2.609 2.609h-37.7zm-8.771-8.773h43.86V16.88L45.682 6.81H11.888zm7.586-8.806a2.606 2.606 0 0 1-2.606-2.606 2.608 2.608 0 0 1 2.606-2.609h5.218a2.608 2.608 0 1 1 0 5.215h-5.223zm0-9.484a2.606 2.606 0 0 1-2.611-2.605 2.606 2.606 0 0 1 2.606-2.609h24.186a2.608 2.608 0 1 1 0 5.215H19.473zm0-9.484a2.608 2.608 0 0 1-2.606-2.609 2.606 2.606 0 0 1 2.601-2.612h14.7a2.608 2.608 0 1 1 0 5.215h-14.7z" transform="translate(10 5) translate(-8.474 -6.595)" style="isolation:isolate" fill="#2c3246"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,81 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" viewBox="0 0 64 64">
<style>
#t-dots #t-clipper-path rect,
#b-dots path,
#b-dots g,
.hourglass {
animation-duration: 5s;
animation-delay: 1s;
animation-iteration-count: infinite;
}
.hourglass {
transform-origin: center;
animation-name: hourglass-rotation;
}
#t-dots #t-clipper-path rect {
animation-name: t-clipper;
}
#b-dots path {
transform: translateY(21px);
animation-name: b-dots;
}
#b-dots g {
animation-name: b-dots-g;
}
@keyframes t-clipper {
0%{}
50%{transform: translateY(22px);}
100%{transform: translateY(22px);}
}
@keyframes b-dots {
0%{}
50%{transform: translateY(0);}
100%{transform: translateY(0);}
}
@keyframes b-dots-g {
0%{}
85%{transform: translateY(0);}
100%{transform: translateY(-6px);}
}
@keyframes hourglass-rotation {
50%{transform: rotateZ(0);}
100%{transform: rotateZ(180deg);}
}
</style>
<polygon points="45 37.22 50 32.22 45 27.22 45 37.22" fill="#5a658e"/>
<polygon points="20 27.22 20 37.22 25 32.22 20 27.22" fill="#5a658e"/>
<polygon points="12 37.22 17 32.22 12 27.22 12 37.22" fill="#5a658e"/>
<g class="hourglass">
<path d="M32,33.41l8,8V52H24V41.41ZM24,12V22.59l8,8,8-8V12Z" fill="#1a1e2c"/>
<g id="t-dots">
<clipPath id="t-clipper-path" fill="white">
<rect x="22.05" y="19.95" width="19.9" height="11"/>
</clipPath>
<path fill="#5a658e" clip-path="url(#t-clipper-path)" d="M41,19v3.18s-5,8.41-9,8.41-9-8.41-9-8.41V19Z"></path>
</g>
<g id="b-dots">
<clipPath id="bottom-clip-path" fill="white">
<rect x="22.05" y="37.76" width="19.9" height="15"/>
</clipPath>
<g clip-path="url(#bottom-clip-path)">
<path fill="#5a658e" d="M24,52H40V48.49S36,40,32,40s-8,8.51-8,8.51Z"></path>
</g>
</g>
<path d="M24,12V22.59l8,8,8-8V12Zm14,9.76-6,6-6-6V14H38ZM24,41.41V52H40V41.41l-8-8ZM38,50H26V42.24l6-6,6,6Z" fill="#1a1e2c"/>
<path d="M44,52H42V40.59L33.41,32,42,23.41V12h2a1,1,0,0,0,0-2H20a1,1,0,0,0,0,2h2V23.41L30.59,32,22,40.59V52H20a1,1,0,0,0,0,2H44a1,1,0,0,0,0-2ZM24,22.59V12H40V22.59l-8,8ZM40,52H24V41.41l8-8,8,8Z" fill="#5a658e"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,14 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="64px" height="64px" viewBox="0 0 64 64">
<path d="M55,32A23,23,0,1,1,11.93,20.76l-2.5.4a1,1,0,0,1-.31-2L14,18.41a1,1,0,0,1,1.14.81l.93,5a1,1,0,0,1-.8,1.17h-.18a1,1,0,0,1-1-.81l-.5-2.7A21,21,0,1,0,32,11a1,1,0,0,1,0-2A23,23,0,0,1,55,32ZM32,50a1,1,0,0,0,1-1V47.21a1,1,0,0,0-2,0V49A1,1,0,0,0,32,50Zm0-32.21a1,1,0,0,0,1-1V15a1,1,0,0,0-2,0v1.79A1,1,0,0,0,32,17.79ZM20.54,22A1,1,0,0,0,22,22a1,1,0,0,0,0-1.41l-1.26-1.27a1,1,0,0,0-1.42,1.42ZM20,45a1,1,0,0,0,.71-.29L22,43.46a1,1,0,1,0-1.41-1.41l-1.27,1.26a1,1,0,0,0,0,1.42A1,1,0,0,0,20,45ZM42.76,22.24a1,1,0,0,0,.7-.29l1.27-1.26a1,1,0,0,0-1.42-1.42l-1.26,1.27a1,1,0,0,0,0,1.41A1,1,0,0,0,42.76,22.24ZM14,32a1,1,0,0,0,1,1h1.79a1,1,0,0,0,0-2H15A1,1,0,0,0,14,32Zm35,1a1,1,0,0,0,0-2H47.21a1,1,0,0,0,0,2Zm-7,10.46,1.26,1.27a1,1,0,0,0,1.42,0,1,1,0,0,0,0-1.42l-1.27-1.26a1,1,0,1,0-1.41,1.41Z" fill="#5a658e"/>
<g id="seconds">
<line fill="none" stroke="#5a658e" stroke-width="2" stroke-miterlimit="10" x1="32" y1="32" x2="32" y2="20" stroke-linecap="round"/>
</g>
<circle cx="32" cy="32" r="2" fill="#5a658e"/>
<circle cx="32" cy="32" r="1" fill="#1a1e2c"/>
<defs>
<animateTransform type="rotate" fill="remove" restart="always" calcMode="linear" accumulate="none" additive="sum" xlink:href="#seconds" repeatCount="indefinite" dur="15s" to="360 32 32" from="0 32 32" attributeName="transform" attributeType="xml">
</animateTransform>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 898 KiB

After

Width:  |  Height:  |  Size: 820 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 898 KiB

After

Width:  |  Height:  |  Size: 817 KiB

View File

@@ -1,7 +1,7 @@
@if (activated) {
@if (signIsNeeded$() === false) {
@if (noPermissions$() === false) {
<a class="webapp-link" [href]="webappLink" target="_blank" [class.dark-theme]="isDarkTheme">
<a class="webapp-link" [href]="" (click)="$event.preventDefault(); navigateToSource()" [class.dark-theme]="isDarkTheme">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="m2.67,14.66c-.74,0-1.33-.6-1.33-1.33h0V2.67c0-.74.6-1.33,1.33-1.33h5.33v1.33H2.67v10.67h10.67v-5.33h1.34v5.33c0,.74-.6,1.33-1.34,1.33H2.67Zm4.86-7.14l4.86-4.86h-1.72v-1.33h4v4h-1.33v-1.72l-4.86,4.86-.95-.94Z" fill="#8492c2"/>
</svg>
@@ -22,6 +22,7 @@
[identifier]="'report-widget'"
[width]="400"
[xAxisType]="xaxis"
[moveLegendToTitle]="checkIfLegendToTitle(plotData)"
[isCompare]="$any(plotData)?.plotParsed?.data?.[0]?.type !== 'parcoords'"
[noMargins]="true"
[hideMaximize]="hideMaximize"
@@ -85,7 +86,7 @@
<ng-template #csvButtonTemplate>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<path
d="M15,8V2l6,6h-6ZM21,9v11c0,1.1-.9,2-2,2H5c-1.1,0-2-.9-2-2V4c0-1.1.9-2,2-2h9v7h7ZM8.83,12.28c-.18-.09-.37-.16-.57-.21s-.42-.07-.64-.07c-.79,0-1.39.26-1.8.78s-.62,1.27-.62,2.25.21,1.73.62,2.25,1.01.78,1.8.78c.22,0,.43-.02.63-.07s.39-.12.57-.21v-1.28c-.2.18-.39.32-.58.4s-.38.13-.58.13c-.42,0-.74-.17-.96-.5s-.32-.83-.32-1.49.11-1.16.32-1.49.53-.5.96-.5c.2,0,.39.04.58.13s.38.22.58.4v-1.28ZM13.76,16.3c0-.43-.11-.79-.33-1.07s-.57-.51-1.04-.68l-.5-.19c-.34-.13-.57-.24-.68-.35s-.16-.24-.16-.4c0-.21.07-.37.22-.48s.36-.17.64-.17c.25,0,.5.05.76.14s.51.22.75.4v-1.12c-.26-.12-.53-.21-.8-.27s-.54-.09-.8-.09c-.59,0-1.06.15-1.39.45s-.51.72-.51,1.25c0,.41.1.74.31.98s.6.48,1.18.7l.57.21c.2.08.35.18.45.31s.16.28.16.46c0,.23-.08.41-.23.54s-.38.19-.66.19-.57-.05-.85-.16-.59-.27-.89-.49v1.19c.27.14.55.24.84.31s.58.1.87.1c.7,0,1.23-.15,1.58-.44s.52-.73.52-1.32ZM18.83,12.11h-1.16l-1.03,4.87-1.02-4.87h-1.16l1.39,5.83h1.6l1.39-5.83Z"
fill="#8492c2"/>

View File

@@ -9,7 +9,7 @@ import {
} from '@angular/core';
import {Store} from '@ngrx/store';
import {MatDialog} from '@angular/material/dialog';
import {debounceTime, filter, map, switchMap, take} from 'rxjs/operators';
import {filter, map, switchMap, take} from 'rxjs/operators';
import {Environment} from '../environments/base';
import {getParcoords, getPlot, getSample, getScalar, getSingleValues, reportsPlotlyReady} from './app.actions';
import {appFeature} from './app.reducer';
@@ -17,7 +17,7 @@ import {ExtFrame} from '@common/shared/single-graph/plotly-graph-base';
import {DebugSample} from '@common/shared/debug-sample/debug-sample.reducer';
import {getSignedUrl, setS3Credentials} from '@common/core/actions/common-auth.actions';
import {ConfigurationService} from '@common/shared/services/configuration.service';
import {_mergeVariants, allowedMergedTypes, convertMultiPlots, createMultiSingleValuesChart, mergeMultiMetricsGroupedVariant, prepareGraph, prepareMultiPlots, tryParseJson} from '@common/tasks/tasks.utils';
import {_mergeVariants, allowedMergedTypes, checkIfLegendToTitle, convertMultiPlots, createMultiSingleValuesChart, mergeMultiMetricsGroupedVariant, prepareGraph, prepareMultiPlots, tryParseJson} from '@common/tasks/tasks.utils';
import {selectSignedUrl} from '@common/core/reducers/common-auth-reducer';
import {loadExternalLibrary} from '@common/shared/utils/load-external-library';
import {ImageViewerComponent} from '@common/shared/debug-sample/image-viewer/image-viewer.component';
@@ -40,8 +40,6 @@ import {
} from '@common/shared/single-value-summary-table/single-value-summary-table.component';
import {MetricVariantResult} from '~/business-logic/model/projects/metricVariantResult';
import {SingleGraphStateModule} from '@common/shared/single-graph/single-graph-state.module';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {BreakpointObserver, BreakpointState} from '@angular/cdk/layout';
import {DOCUMENT} from '@angular/common';
@@ -52,27 +50,26 @@ export interface SelectedMetricVariant extends MetricVariantResult {
}
@Component({
selector: 'sm-widget-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
SingleGraphStateModule,
SingleGraphModule,
DebugSampleModule,
ParallelCoordinatesGraphComponent,
SingleValueSummaryTableComponent
]
selector: 'sm-widget-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
SingleGraphStateModule,
SingleGraphModule,
DebugSampleModule,
ParallelCoordinatesGraphComponent,
SingleValueSummaryTableComponent
]
})
export class AppComponent implements OnInit {
private store = inject(Store);
private configService = inject(ConfigurationService);
private dialog = inject(MatDialog);
private cdr = inject(ChangeDetectorRef);
private breakpointObserver = inject(BreakpointObserver);
private renderer = inject(Renderer2);
private readonly document = inject(DOCUMENT);
protected readonly checkIfLegendToTitle = checkIfLegendToTitle;
protected title = 'report-widgets';
public plotData: ExtFrame;
@@ -87,12 +84,12 @@ export class AppComponent implements OnInit {
public hideMaximize: 'show' | 'hide' | 'disabled' = 'show';
protected signIsNeeded$ = this.store.selectSignal(appFeature.selectSignIsNeeded);
protected noPermissions$ = this.store.selectSignal(appFeature.selectNoPermissions);
public isDarkTheme: boolean;
private tasksData = this.store.selectSignal(appFeature.selectTaskData);
public isDarkTheme = false;
public externalTool = false;
public parcoordsData: { experiments: ExtraTask[]; params: string[]; metrics: SelectedMetricVariant[] };
@ViewChild(SingleGraphComponent) 'singleGraph': SingleGraphComponent;
public singleValueData: EventsGetTaskSingleValueMetricsResponseValues[];
public webappLink: string;
public readonly xaxis: ScalarKeyEnum;
@HostListener('window:resize')
@@ -106,7 +103,6 @@ export class AppComponent implements OnInit {
});
this.searchParams = new URLSearchParams(window.location.search);
this.type = this.searchParams.get('type') as WidgetTypes;
this.webappLink = this.buildSourceLink(this.searchParams, '*', null);
this.singleGraphHeight = window.innerHeight;
this.otherSearchParams = this.getOtherSearchParams();
this.xaxis = this.searchParams.get('xaxis') as ScalarKeyEnum;
@@ -136,7 +132,7 @@ export class AppComponent implements OnInit {
if (!(window.top as any).holdIframe) {
this.activate();
}
} catch (e) {
} catch {
this.externalTool = true;
this.hideMaximize = 'hide';
console.log('no-access-to-parent-window');
@@ -180,7 +176,7 @@ export class AppComponent implements OnInit {
if (window.top.document.body.parentElement.className) {
this.isDarkTheme = window.top.document.body.parentElement.classList.contains('dark-mode');
}
} catch (e) {
} catch {
this.isDarkTheme = this.searchParams.get('light') ? this.searchParams.get('light') === 'false' : !this.isDarkTheme;
}
this.renderer.addClass(this.document.documentElement, `${this.isDarkTheme ? 'dark' : 'light'}-mode`);
@@ -219,15 +215,15 @@ export class AppComponent implements OnInit {
take(1))
.subscribe((metricsPlots) => {
this.plotLoaded = true;
if (this.searchParams.getAll('objects').length < 2) {
if (this.tasksData().sourceTasks.length < 2) {
const merged = this.mergeVariants(metricsPlots as ReportsApiMultiplotsResponse);
this.plotData = {...Object.values(merged)[0], ...Object.values(merged)[0].plotParsed};
} else {
const {merged} = prepareMultiPlots(metricsPlots as ReportsApiMultiplotsResponse);
const newGraphs = convertMultiPlots(merged);
const originalObject = this.searchParams.get('objects');
const series = this.searchParams.get('series');
this.plotData = series ? Object.values(newGraphs)[0].find(a => (a.data[0] as any).seriesName === series) :
const series = this.searchParams.getAll('series');
this.plotData = series?.length > 0 ? Object.values(newGraphs)[0].find(a => series.includes((a.data[0] as any).seriesName)) :
Object.values(newGraphs)[0]?.find(a => originalObject === (a.task ?? a.data[0].task)) ??
Object.values(newGraphs)[0]?.[0];
}
@@ -436,16 +432,23 @@ export class AppComponent implements OnInit {
return {metric: lastMetric.metric, variant: lastMetric.variant};
}
private buildSourceLink(searchParams: URLSearchParams, project: string, tasks: string[]): string {
protected navigateToSource() {
const a = document.createElement('a');
a.href = this.buildSourceLink(this.searchParams, '*');
a.target = '_blank';
a.click();
}
private buildSourceLink(searchParams: URLSearchParams, project: string): string {
const isModels = searchParams.has('models') || this.searchParams.get('objectType') === 'model';
const objects = searchParams.getAll('objects');
const variants = searchParams.getAll('variants');
const metricsPath = searchParams.getAll('metrics');
let entityIds = objects.length > 0 ? objects : searchParams.getAll(isModels ? 'models' : 'tasks');
if (entityIds.length === 0 && tasks?.length > 0) {
entityIds = tasks;
if (entityIds.length === 0 && this.tasksData().sourceTasks.length > 0) {
entityIds = this.tasksData().sourceTasks.slice(0,100);
}
const isCompare = entityIds.length > 1;
const isCompare =this.tasksData().sourceTasks.length > 1;
let url = `${window.location.origin.replace('4201', '4200')}/projects/${project ?? '*'}/`;
if (isCompare) {
url += `${isModels ? 'compare-models;ids=' : 'compare-tasks;ids='}${entityIds.filter(id => !!id).join(',')}/${
@@ -469,11 +472,11 @@ export class AppComponent implements OnInit {
switch (type) {
case 'single':
case 'scalar':
return 'info-output/metrics/scalar';
return 'output/metrics/scalar';
case 'plot':
return 'info-output/metrics/plots';
return 'output/metrics/plots';
case 'sample':
return 'info-output/debugImages';
return 'output/debugImages';
}
}
return '';

View File

@@ -61,12 +61,13 @@ export class AppEffects {
{headers: this.getHeaders(action.company)}
)),
mergeMap((res) => [
setPlotData({data: res.data.plots as unknown as ReportsApiMultiplotsResponse}),
setTaskData({
sourceProject: (res.data.tasks[0]?.project as any).id,
sourceTasks: res.data.tasks.map(t => t.id),
appId: (res.data.tasks[0] as any)?.application?.app_id?.id
})]),
}),
setPlotData({data: res.data.plots as unknown as ReportsApiMultiplotsResponse})
]),
catchError(error => [requestFailed(error), ...(error.status === 403 ? [setNoPermissions()] : [])])
));

View File

@@ -1,16 +1,18 @@
@import "customizations/colors";
@import "customizations/form-fields";
@import "customizations/dialogs";
@import "customizations/scrollbar";
@import "customizations/tooltip";
@import "customizations/slide-toggle";
@forward "customizations/colors";
@forward "customizations/form-fields";
@forward "customizations/dialogs";
@forward "customizations/scrollbar";
@forward "customizations/tooltip";
@forward "customizations/slide-toggle";
@use "customizations/colors";
@use "customizations/dialogs";
body {
color: var(--color-on-surface-variant);
}
@mixin components($theme) {
@include generate-colors($theme);
@include form-fields($theme);
@include dialogs($theme);
@include colors.generate-colors($theme);
@include dialogs.dialogs($theme);
}

View File

@@ -1,22 +1,24 @@
@use '../../../../../../../../node_modules/@angular/material/index' as mat;
@use "../../../../../styles/variables";
//@import "../assets/fonts/trains-icons.scss";
//@import "reboot";
//@import "minimal-bootstrap";
//@import "layout";
//@import "utilities";
@import "themes";
@import "./customizations";
@import "../../../../../styles/icons"; //needs a clean from sass vars
//@use "../assets/fonts/trains-icons.scss";
//@use "reboot";
//@use "minimal-bootstrap";
//@use "layout";
//@use "utilities";
@use "themes";
@use "./customizations";
@forward "../../../../../styles/icons"; //needs a clean from sass vars
:root {
@include mat.all-component-themes($theme);
@include components($theme);
@include mat.all-component-themes(themes.$theme);
@include mat.system-level-colors(themes.$theme);
@include customizations.components(themes.$theme);
&.dark-mode {
@include mat.all-component-colors($dark-theme);
@include components($dark-theme);
@include mat.all-component-colors(themes.$dark-theme);
@include mat.system-level-colors(themes.$dark-theme);
@include customizations.components(themes.$dark-theme);
}
}
@@ -26,7 +28,6 @@ $purple: #4d66ff;
$white-primary-text: rgba(white, 0.87);
$dark-primary-text: rgba(black, 0.87);
@import "../../../../../styles/variables";
* {
margin: 0;
@@ -38,6 +39,10 @@ html, body {
height: 100%;
overflow: hidden;
--mat-select-trigger-text-size: 14px;
&.dark-mode {
background-color: var(--color-surface-container-lowest);
}
}
body {

View File

@@ -0,0 +1 @@
../../../../../assets/

View File

@@ -25,7 +25,6 @@ export interface Environment {
fileBaseUrl: string;
displayedServerUrls?: {apiServer?: string; filesServer?: string};
alternativeFilesBaseUrl?: string;
demo: boolean;
headerPrefix: string;
version: string;
userKey: string;
@@ -76,7 +75,6 @@ export const BASE_ENV: Environment = {
accountAdministration: true,
production: true,
cookieName: 'allegro_token',
demo: false,
updateCheck: false,
autoLogin: false,
apiBaseUrl: null,

View File

@@ -6,6 +6,18 @@
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<style>
@media (prefers-color-scheme: dark) {
html:not(.light-mode) {
background-color: var(--color-shadow);
}
}
@media (prefers-color-scheme: light) {
html:not(.dark-mode) {
background-color: white;
}
}
</style>
</head>
<body>
<sm-widget-root style="width: 100%; height: 100%"></sm-widget-root>

View File

@@ -1,10 +1,15 @@
<span class="search-container" [class.open]="isSearching$ | ngrxPush">
<div class="search-icon-container">
<button [disabled]="isSearching() && !searchElem().searchBarInput().nativeElement.value " mat-icon-button (click)="isSearching()? clearSearch():openSearch()" data-id="searchIcon" class="me-2 search-icon" [class.open]="isSearching()" >
<mat-icon fontSet="al" [fontIcon]="searchElem().searchBarInput().nativeElement.value ?'al-ico-dialog-x':'al-ico-search'" data-id="search Icon"></mat-icon>
</button>
</div>
<span class="search-container" [class.open]="isSearching()">
<sm-search
#searchComponent
class="search-header"
class="outline contained-icon"
[class.regex-error]="regexError"
[value]="(searchQuery$ | ngrxPush)?.original"
[placeholder]="searchPlaceholder$ | ngrxPush"
[placeholder]="searchPlaceholder()"
[hideIcons]="true"
[minimumChars]="minChars"
(focusout)="onSearchFocusOut()"
@@ -13,29 +18,16 @@
@if (regexError) {
<mat-icon class="error regexp" fontSet="al" fontIcon="al-ico-error-circle" inline [smTooltip]="regexError" [matTooltipPosition]="'below'"></mat-icon>
}
<button mat-icon-button
class="regexp sm"
[class.active]="regExp"
smClickStopPropagation
[smTooltip]="'Regex'" [matTooltipPosition]="'below'"
(click)="toggleRegExp(); searchComponent.searchBarInput.nativeElement.focus();"
@if (showRegex$ | ngrxPush) {
<button mat-icon-button
class="regexp sm"
[class.active]="regExp"
smClickStopPropagation
[smTooltip]="'Regex'" [matTooltipPosition]="'below'"
(click)="toggleRegExp(); searchComponent.searchBarInput().nativeElement.focus();"
>
<mat-icon fontSet="al" fontIcon="al-ico-regex" data-id="enableRegexButton"></mat-icon>
</button>
}
</sm-search>
</span>
@if (searchActive) {
@if ((isSearching$ | ngrxPush)) {
<button mat-icon-button (click)="clearSearch()" smClickStopPropagation>
<mat-icon
fontSet="al"
fontIcon="al-ico-dialog-x"
data-id="closeSearchButton">
</mat-icon>
</button>
} @else {
<button mat-icon-button (click)="openSearch()" data-id="searchIcon">
<mat-icon fontSet="al" fontIcon="al-ico-search" data-id="search Icon"></mat-icon>
</button>
}
}

View File

@@ -1,14 +1,28 @@
@import "variables";
:host {
position: absolute;
height: 32px;
display: flex;
align-items: center;
right: 110px;
// background-color: var(--color-surface-container-low);
z-index: 1;
padding-left: 20px;
.search-icon-container {
width: 284px;
position: absolute;
.search-icon {
transition: left 0.35s ease-in-out;
left: 0;
z-index: 1;
&.open {
left: 247px;
}
.al-ico-search {
color: var(--color-primary);
}
}
}
.search-container {
width: 0;
@@ -24,11 +38,11 @@
.regexp {
position: absolute;
right: 8px;
right: 36px;
&.active {
color: var(--color-on-secondary);
background-color: var(--color-secondary);
color: var(--color-on-primary);
background-color: var(--color-primary);
}
&.error {
@@ -37,7 +51,7 @@
}
.al-ico-error-circle {
right: 48px;
right: 60px;
}
.al-ico-dialog-x {

View File

@@ -1,23 +1,23 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit, ViewChild} from '@angular/core';
import {ChangeDetectionStrategy, Component, inject, input, OnDestroy, viewChild} from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
import {Store} from '@ngrx/store';
import {setSearching, setSearchQuery} from '../../common-search.actions';
import {SearchState, selectIsSearching, selectPlaceholder, selectSearchQuery} from '../../common-search.reducer';
import {Observable} from 'rxjs';
import {debounceTime, distinctUntilChanged, filter, tap} from 'rxjs/operators';
import {resetSearch, setSearching, setSearchQuery} from '../../common-search.actions';
import {selectIsSearching, selectPlaceholder, selectSearchQuery} from '../../common-search.reducer';
import {Observable, timer} from 'rxjs';
import {debounce, distinctUntilChanged, filter, tap} from 'rxjs/operators';
import {SearchComponent} from '@common/shared/ui-components/inputs/search/search.component';
import {PushPipe} from '@ngrx/component';
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
import {ClickStopPropagationDirective} from '@common/shared/ui-components/directives/click-stop-propagation.directive';
import {MatIconButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {takeUntilDestroyed, toObservable} from '@angular/core/rxjs-interop';
@Component({
selector: 'sm-common-search',
templateUrl: './common-search.component.html',
styleUrls: ['./common-search.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
SearchComponent,
PushPipe,
@@ -27,22 +27,31 @@ import {MatIcon} from '@angular/material/icon';
MatIcon
]
})
export class CommonSearchComponent implements OnInit {
public searchQuery$: Observable<SearchState['searchQuery']>;
public isSearching$: Observable<boolean>;
public searchPlaceholder$: Observable<string>;
public searchActive: boolean;
export class CommonSearchComponent implements OnDestroy {
private store = inject(Store);
private router = inject(Router);
private route = inject(ActivatedRoute);
public searchActive = input(false);
@ViewChild(SearchComponent) searchElem: SearchComponent;
public regExp = false;
private closeTimer: number;
minChars = 3;
public regexError: boolean;
protected minChars = 3;
protected regexError: boolean;
protected searchQuery$ = this.store.select(selectSearchQuery).pipe(tap(searchQuery => this.regExp = searchQuery?.regExp));
protected isSearching = this.store.selectSignal(selectIsSearching);
protected searchPlaceholder = this.store.selectSignal(selectPlaceholder);
protected searchElem = viewChild(SearchComponent);
private waitForRegex = false
protected showRegex$: Observable<boolean>;
constructor() {
this.showRegex$ = toObservable(this.isSearching).pipe(debounce((isSearching) => timer(isSearching ? 350 : 0)));
constructor(private store: Store, private router: Router, private route: ActivatedRoute, private cdr: ChangeDetectorRef) {
this.router.events
.pipe(
takeUntilDestroyed(),
filter(event => event instanceof NavigationEnd),
distinctUntilChanged((pe: NavigationEnd, ce: NavigationEnd) => {
const pURL = new URLSearchParams(pe.url.split('?')?.[1]);
@@ -60,12 +69,12 @@ export class CommonSearchComponent implements OnInit {
if (query) {
this.openSearch();
} else {
if (document.activeElement !== this.searchElem.searchBarInput.nativeElement) {
this.store.dispatch(setSearching({payload: false}))
if (document.activeElement !== this.searchElem()?.searchBarInput().nativeElement) {
this.store.dispatch(setSearching({payload: false}));
}
}
})
if( this.route.snapshot.queryParams.q) {
});
if (this.route.snapshot.queryParams.q) {
const query = this.route.snapshot.queryParams?.q ?? '';
const qregex = this.route.snapshot.queryParams?.qreg ?? false;
this.store.dispatch(setSearchQuery({
@@ -73,19 +82,12 @@ export class CommonSearchComponent implements OnInit {
regExp: qregex,
original: query
}));
setTimeout( () => this.openSearch());
setTimeout(() => this.openSearch());
}
}
ngOnInit() {
this.searchQuery$ = this.store.select(selectSearchQuery).pipe(tap(searchQuery => this.regExp = searchQuery?.regExp));
this.isSearching$ = this.store.select(selectIsSearching);
this.searchPlaceholder$ = this.store.select(selectPlaceholder).pipe(debounceTime(0));
this.router.events.pipe(filter(event => event instanceof NavigationEnd))
.subscribe(() => {
this.setSearchActive();
});
setTimeout(this.setSearchActive.bind(this));
ngOnDestroy(): void {
this.store.dispatch(resetSearch());
}
@@ -101,9 +103,10 @@ export class CommonSearchComponent implements OnInit {
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {
q: query || undefined
q: query || undefined,
qreg: this.regExp ? this.regExp : undefined
}
})
});
} catch (e) {
this.regexError = e.message?.replace(/:.+:/, ':');
@@ -112,44 +115,37 @@ export class CommonSearchComponent implements OnInit {
openSearch() {
window.clearTimeout(this.closeTimer);
this.searchElem.searchBarInput.nativeElement.focus();
this.searchElem().searchBarInput().nativeElement.focus();
this.store.dispatch(setSearching({payload: true}));
}
onSearchFocusOut() {
if (!this.searchElem.searchBarInput.nativeElement.value) {
window.clearTimeout(this.closeTimer);
if (!this.waitForRegex && !this.searchElem().searchBarInput().nativeElement.value) {
this.closeTimer = window.setTimeout(() => this.store.dispatch(setSearching({payload: false})), 200);
}
}
private setSearchActive() {
let route = this.route.snapshot;
let showSearch = false;
while (route.firstChild) {
route = route.firstChild;
if (route.data.search !== undefined) {
showSearch = route.data.search;
}
}
this.searchActive = showSearch;
this.cdr.detectChanges();
}
clearSearch() {
this.searchElem.clear();
this.searchElem().clear();
this.store.dispatch(setSearching({payload: false}));
document.body.focus();
setTimeout( () =>this.searchElem.searchBarInput.nativeElement.blur(), 100);
setTimeout(() => this.searchElem().searchBarInput().nativeElement.blur(), 100);
}
toggleRegExp() {
this.regExp = !this.regExp;
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {
qreg: this.regExp ? this.regExp : undefined}
})
if (this.searchElem().searchBarInput().nativeElement.value) {
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {qreg: this.regExp ? this.regExp : undefined}
});
}
this.waitForRegex = true;
window.setTimeout(() => this.waitForRegex = false, 200);
this.searchElem().searchBarInput().nativeElement.focus();
}
}

View File

@@ -75,7 +75,7 @@ export const systemThemeChanged = createAction(
export const setForcedTheme = createAction(
VIEW_PREFIX + '[set forced theme]',
props<{ theme: 'light' | 'dark'; default: 'light' | 'dark' }>()
props<{ theme: 'light' | 'dark'; default?: 'light' | 'dark' }>()
);
export const setThemeColors = createAction(

View File

@@ -58,6 +58,11 @@ export const setSelectedProject = createAction(
props<{ project: Project }>()
);
export const setSelectedProjectSimple = createAction(
PROJECTS_PREFIX + 'SET_SELECTED_PROJECT_SIMPLE',
props<{ project: Project }>()
);
export const setProjectAncestors = createAction(
PROJECTS_PREFIX + 'SET_PROJECT_ANCESTORS',
props<{ projects: Project[] }>()

View File

@@ -28,7 +28,7 @@ import {isGoogleCloudUrl, SignResponse} from '@common/settings/admin/base-admin-
import {isFileserverUrl} from '~/shared/utils/url';
import {selectRouterQueryParams} from '@common/core/reducers/router-reducer';
import {concatLatestFrom} from '@ngrx/operators';
import {selectExtraCredentials} from '~/core/reducers/auth.reducers';
import {selectCompanyPreSignServices, selectExtraCredentials} from '~/core/reducers/auth.reducers';
import {ErrorService} from '@common/shared/services/error.service';
@Injectable()
@@ -183,13 +183,15 @@ export class CommonAuthEffects {
mergeMap(([action, prevSigns]) =>
forkJoin(action.sign.map(req =>
of(action).pipe(
concatLatestFrom(() =>
this.store.select(selectSignedUrl(req.url))
),
switchMap(([, signedUrl]) => (!signedUrl?.expires || signedUrl.expires < (new Date()).getTime() ||
(isFileserverUrl(req.url) && req.config?.disableCache) ||
(req.config?.error && isGoogleCloudUrl(req.url) && !signedUrl.signed.includes('X-Amz-Signature'))
) ? this.adminService.signUrlIfNeeded(req.url, req.config, prevSigns[req.url]) : of({type: 'none'})),
concatLatestFrom(() => [
this.store.select(selectSignedUrl(req.url)),
this.store.select(selectCompanyPreSignServices)
]),
switchMap(([, signedUrl, preSignService]) => (!signedUrl?.expires || signedUrl.expires < (new Date()).getTime() ||
(isFileserverUrl(req.url) && req.config?.disableCache) ||
(req.config?.error && isGoogleCloudUrl(req.url) && !signedUrl.signed.includes('X-Amz-Signature')) ||
preSignService.length > 0
) ? this.adminService.signUrlIfNeeded(req.url, req.config, prevSigns[req.url]) : of({type: 'none'})),
map(res => ({res, orgUrl: req.url}))
)
)).pipe(
@@ -250,9 +252,9 @@ export class CommonAuthEffects {
.pipe(
concatLatestFrom(() => this.store.select(selectS3BucketCredentialsBucketCredentials)),
switchMap(([data, bucketCredentials]) => {
const actions = [...this.signAfterPopup];
this.signAfterPopup = [];
let actions = [];
if (data) {
actions = [...this.signAfterPopup];
if (!data.success) {
const emptyCredentials = bucketCredentials.find((cred => cred?.Bucket === data.Bucket)) === undefined;
const dontAskAgainForBucketName = emptyCredentials ? '' : data.Bucket + data.Endpoint;
@@ -260,6 +262,7 @@ export class CommonAuthEffects {
}
return [authActions.saveS3Credentials({newCredential: data}), ...actions];
}
this.signAfterPopup = [];
return actions;
}),
finalize(() => action?.credentials?.Bucket && delete this.openPopup[action.credentials.Bucket])

View File

@@ -49,11 +49,11 @@ export interface RootProjects {
projectTags: string[];
companyTags: string[];
systemTags: string[];
tagsColors: { [tag: string]: TagColor };
tagsColors: Record<string, TagColor>;
tagsFilterByProject: boolean;
graphVariant: { [project: string]: ISmCol[] };
graphVariant: Record<string, ISmCol[]>;
graphData: ScatterPlotPoint[];
hiddenStates: { [state: string]: boolean };
hiddenStates: Record<string, boolean>;
lastUpdate: string;
users: User[];
allUsers: User[];
@@ -61,9 +61,9 @@ export interface RootProjects {
showHidden: boolean;
hideExamples: boolean;
blockUserScript: boolean;
mainPageTagsFilter: { [Feature: string]: { tags: string[]; filterMatchMode: string } };
mainPageTagsFilter: Record<string, { tags: string[]; filterMatchMode: string }>;
mainPageTagsFilterMatchMode: string;
defaultNestedModeForFeature: { [feature: string]: boolean };
defaultNestedModeForFeature: Record<string, boolean>;
selectedSubFeature: IBreadcrumbsLink;
tablesFilterProjectsOptions: Partial<ProjectsGetAllResponseSingle>[];
projectsOptionsScrollId: string;
@@ -116,7 +116,7 @@ export const selectProjectTags = createSelector(projects, state => state.project
export const selectMainPageTagsFilter = createSelector(projects, selectProjectType,(state, projectType) => projectType? state.mainPageTagsFilter[projectType]?.tags : []);
export const selectMainPageTagsFilterMatchMode = createSelector(projects, selectProjectType, (state, projectType) => projectType? state.mainPageTagsFilter[projectType]?.filterMatchMode : null);
export const selectCompanyTags = createSelector(projects, state => state.companyTags);
// eslint-disable-next-line @typescript-eslint/naming-convention
export const selectProjectSystemTags = createSelector(projects, state => getSystemTags({system_tags: state.systemTags} as ITableExperiment));
export const selectTagsColors = createSelector(projects, state => state?.tagsColors);
export const selectLastUpdate = createSelector(projects, state => state.lastUpdate);
@@ -138,7 +138,7 @@ export const selectSelectedProjectUsers = createSelector(selectSelectedProjectId
(projectId, projectUsers, allUsers) => projectId === '*' ? allUsers : projectUsers);
export const selectTablesFilterProjectsOptions = createSelector(projects, state => state.tablesFilterProjectsOptions);
export const selectMyProjects = createSelector(selectTablesFilterProjectsOptions,
projects => projects?.filter(project => project?.company?.id)
projects => projects? projects?.filter(project => project?.company?.id): null
);
export const selectReadOnlyProjects = createSelector(selectTablesFilterProjectsOptions,
@@ -162,6 +162,10 @@ export const projectsReducer = createReducer(
selectedProject: action.project,
extraUsers: []
})),
on(projectsActions.setSelectedProjectSimple, (state, action): RootProjects => ({
...state,
selectedProject: action.project
})),
on(projectsActions.setProjectAncestors, (state, action): RootProjects => ({
...state,
projectAncestors: action.projects
@@ -256,7 +260,7 @@ export const projectsReducer = createReducer(
on(projectsActions.setTablesFilterProjectsOptions, (state, action): RootProjects => ({
...state,
tablesFilterProjectsOptions: action.loadMore ? uniqBy((state.tablesFilterProjectsOptions || []).concat(action.projects), 'id') : uniqBy(action.projects, 'id'),
projectsOptionsScrollId: action.scrollId
projectsOptionsScrollId: action.scrollId
})),
on(projectsActions.getTablesFilterProjectsOptions, (state, action): RootProjects => ({...state, ...(!action.loadMore && {tablesFilterProjectsOptions: null, projectsOptionsScrollId: null})})),
);

View File

@@ -157,7 +157,11 @@ export const viewReducers = [
on(layoutActions.visibilityChanged, (state, action): ViewState => ({...state, applicationVisible: action.visible})),
on(layoutActions.userThemeChanged, (state, action): ViewState => ({...state, theme: action.theme})),
on(layoutActions.systemThemeChanged, (state, action): ViewState => ({...state, systemTheme: action.theme})),
on(layoutActions.setForcedTheme, (state, action): ViewState => ({...state, forcedTheme: action.theme, defaultTheme: action.theme})),
on(layoutActions.setForcedTheme, (state, action): ViewState => ({
...state,
forcedTheme: action.theme,
...(action.default && {defaultTheme: action.theme})
})),
on(layoutActions.setThemeColors, (state, action): ViewState => ({...state, themeColors: action.colors})),
on(layoutActions.setScaleFactor, (state, action): ViewState => ({...state, scaleFactor: action.scale})),
on(layoutActions.firstLogin, (state, action): ViewState => ({
@@ -203,7 +207,7 @@ export const viewReducers = [
neverShowChangesAgain: action.version
})),
on(setBreadcrumbs, (state, action): ViewState => ({
...state, breadcrumbs: action.breadcrumbs, ...(action.workspaceNeutral !== undefined && {workspaceNeutral: action.workspaceNeutral})
...state, breadcrumbs: action.breadcrumbs, workspaceNeutral: action.workspaceNeutral !== undefined ? action.workspaceNeutral: false
})),
on(headerActions.reset, (state): ViewState => ({
...state, headerMenuActiveFeature: initViewState.headerMenuActiveFeature, headerMenu: initViewState.headerMenu

View File

@@ -68,7 +68,7 @@ export const searchExperiments = createAction(
export const setExperimentsResults = createAction(
SEARCH_PREFIX + 'SET_EXPERIMENTS',
props<{ experiments: Task[]; scrollId: string }>()
props<{ tasks: Task[]; scrollId: string }>()
);
export const searchModels = createAction(

View File

@@ -117,7 +117,7 @@ export class DashboardSearchEffects {
this.store.select(selectActiveSearch),
this.store.select(selectSearchTerm)
]),
mergeMap(([, active, term]) => {
switchMap(([, active, term]) => {
const actionsToFire = [];
if (!active) {
// actionsToFire.push(searchClear());
@@ -181,7 +181,7 @@ export class DashboardSearchEffects {
order_by: orderBy,
/* eslint-enable @typescript-eslint/naming-convention */
}).pipe(
mergeMap(res => [setProjectsResults({
switchMap(res => [setProjectsResults({
projects: res.projects,
scrollId: res.scroll_id
}), deactivateLoader(action.type)]),
@@ -315,7 +315,7 @@ export class DashboardSearchEffects {
/* eslint-enable @typescript-eslint/naming-convention */
}).pipe(
mergeMap(res => [setExperimentsResults({
experiments: res.tasks,
tasks: res.tasks,
scrollId: res.scroll_id
}), deactivateLoader(action.type)]),
catchError(error => [deactivateLoader(action.type), requestFailed(error)])))

View File

@@ -19,7 +19,7 @@ import {setFilterByUser} from '@common/core/actions/users.actions';
export interface DashboardSearchState {
projects: Project[];
experiments: Task[];
tasks: Task[];
models: Model[];
pipelines: Project[];
reports: IReport[];
@@ -40,7 +40,7 @@ export const searchInitialState: DashboardSearchState = {
pipelines: [],
openDatasets: [],
users: [],
experiments: [],
tasks: [],
models: [],
reports: [],
resultsCount: null,
@@ -78,7 +78,7 @@ export const dashboardSearchReducers = [
})),
on(setExperimentsResults, (state, action): DashboardSearchState => ({
...state,
experiments: action.scrollId === state.scrollIds?.[activeSearchLink.experiments] ? state.experiments.concat(action.experiments) : action.experiments,
tasks: action.scrollId === state.scrollIds?.[activeSearchLink.experiments] ? state.tasks.concat(action.tasks) : action.tasks,
scrollIds: {...state.scrollIds, [activeSearchLink.experiments]: action.scrollId}
})),
on(setModelsResults, (state, action): DashboardSearchState => ({
@@ -98,6 +98,8 @@ export const dashboardSearchReducers = [
[activeSearchLink.experiments]: [],
[activeSearchLink.pipelines]: [],
[activeSearchLink.projects]: [],
[activeSearchLink.openDatasets]: [],
[activeSearchLink.reports]: [],
})),
on(searchClear, (state): DashboardSearchState => ({...state, ...searchInitialState})),
] as ReducerTypes<DashboardSearchState, ActionCreator[]>[];
@@ -109,7 +111,7 @@ export const dashboardSearchReducer = createReducer(
export const selectSearch = createFeatureSelector<DashboardSearchState>('search');
export const selectProjectsResults = createSelector(selectSearch, (state: DashboardSearchState): Array<Project> => state.projects);
export const selectExperimentsResults = createSelector(selectSearch, (state: DashboardSearchState): Array<Task> => state.experiments);
export const selectExperimentsResults = createSelector(selectSearch, (state: DashboardSearchState): Array<Task> => state.tasks);
export const selectModelsResults = createSelector(selectSearch, (state: DashboardSearchState): Array<Model> => state.models);
export const selectReportsResults = createSelector(selectSearch, (state: DashboardSearchState): Array<IReport> => state.reports);
export const selectPipelinesResults = createSelector(selectSearch, (state: DashboardSearchState): Array<Project> => state.pipelines);

View File

@@ -0,0 +1,31 @@
<sm-dialog-template>
<div class="content">
<div class="search-container">
<div></div>
<sm-search
#searchComponent
class="outline"
[class.regex-error]="regexError"
[value]="(searchQuery$ | ngrxPush)?.query"
[hideIcons]="true"
[minimumChars]="3"
(valueChanged)="search($event)"
>
@if (regexError) {
<mat-icon class="error regexp" fontSet="al" fontIcon="al-ico-error-circle" inline [smTooltip]="regexError" [matTooltipPosition]="'below'"></mat-icon>
}
<button mat-icon-button
class="regexp sm"
[class.active]="regExp"
smClickStopPropagation
[smTooltip]="'Regex'" [matTooltipPosition]="'below'"
(click)="toggleRegExp(); searchComponent.searchBarInput().nativeElement.focus();"
>
<mat-icon fontSet="al" fontIcon="al-ico-regex" data-id="enableRegexButton"></mat-icon>
</button>
</sm-search>
<sm-show-only-user-work></sm-show-only-user-work>
</div>
<sm-dashboard-search (itemSelected)="closeDialog()"></sm-dashboard-search>
</div>
</sm-dialog-template>

View File

@@ -0,0 +1,29 @@
:host {
.content{
padding-top: 24px;
}
.search-container{
display: grid;
grid-auto-columns: minmax(0, 1fr);
grid-auto-flow: column;
sm-show-only-user-work{
margin-left: 12px;
}
}
.regexp {
position: absolute;
right: 8px;
&.active {
color: var(--color-on-primary);
background-color: var(--color-primary);
}
&.error {
width: 20px;
right: 36px;
}
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { GlobalSearchDialogComponent } from './global-search-dialog.component';
describe('GlobalSearchDialogComponent', () => {
let component: GlobalSearchDialogComponent;
let fixture: ComponentFixture<GlobalSearchDialogComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GlobalSearchDialogComponent]
})
.compileComponents();
fixture = TestBed.createComponent(GlobalSearchDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,132 @@
import {ChangeDetectorRef, Component, inject, OnDestroy} from '@angular/core';
import {SearchComponent} from '@common/shared/ui-components/inputs/search/search.component';
import {DashboardSearchModule} from '~/features/dashboard-search/dashboard-search.module';
import {DialogTemplateComponent} from '@common/shared/ui-components/overlay/dialog-template/dialog-template.component';
import {Store} from '@ngrx/store';
import {searchSetTerm} from '@common/dashboard-search/dashboard-search.actions';
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
import {ClickStopPropagationDirective} from '@common/shared/ui-components/directives/click-stop-propagation.directive';
import {MatIcon} from '@angular/material/icon';
import {MatIconButton} from '@angular/material/button';
import {PushPipe} from '@ngrx/component';
import {TooltipDirective} from '@common/shared/ui-components/indicators/tooltip/tooltip.directive';
import {distinctUntilChanged, filter} from 'rxjs/operators';
import {selectSearchTerm} from '@common/dashboard-search/dashboard-search.reducer';
import {Observable} from 'rxjs';
import {ShowOnlyUserWorkComponent} from '@common/shared/components/show-only-user-work/show-only-user-work.component';
import {MatDialogRef} from '@angular/material/dialog';
@Component({
selector: 'sm-global-search-dialog',
imports: [
SearchComponent,
DashboardSearchModule,
DialogTemplateComponent,
ClickStopPropagationDirective,
MatIcon,
MatIconButton,
PushPipe,
TooltipDirective,
ShowOnlyUserWorkComponent
],
templateUrl: './global-search-dialog.component.html',
styleUrl: './global-search-dialog.component.scss'
})
export class GlobalSearchDialogComponent implements OnDestroy {
private store = inject(Store);
private router = inject(Router);
private route = inject(ActivatedRoute);
public regExp = false;
public regexError: boolean;
protected dialogRef = inject<MatDialogRef<GlobalSearchDialogComponent>>(MatDialogRef<GlobalSearchDialogComponent>);
public searchQuery$: Observable<{ query: string; regExp?: boolean; original?: string }>;
private itemSelected = false;
constructor() {
this.searchQuery$ = this.store.select(selectSearchTerm);
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd),
distinctUntilChanged((pe: NavigationEnd, ce: NavigationEnd) => {
const pURL = new URLSearchParams(pe.url.split('?')?.[1]);
const cURL = new URLSearchParams(ce.url.split('?')?.[1]);
return pURL.get('gq') === cURL.get('gq') && pURL.get('gqreg') === cURL.get('gqreg');
})
).subscribe(() => {
const query = this.route.snapshot.queryParams?.gq ?? '';
const qregex = this.route.snapshot.queryParams?.gqreg === 'true';
this.regExp = qregex;
this.store.dispatch(searchSetTerm({
query: qregex ? query : query.trim(),
regExp: qregex,
}));
});
if (this.route.snapshot.queryParams.gq) {
const query = this.route.snapshot.queryParams?.gq ?? '';
const qregex = this.route.snapshot.queryParams?.gqreg ?? false;
this.store.dispatch(searchSetTerm({
query: qregex ? query : query.trim(),
regExp: qregex,
}));
}
}
ngOnDestroy(): void {
if (!this.itemSelected) {
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {
gq: undefined,
gqreg: undefined,
tab: undefined
}
});
}
}
public search(query: string) {
try {
if (this.regExp) {
new RegExp(query);
}
this.regexError = null;
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {
gq: query || undefined
}
});
} catch (e) {
this.regexError = e.message?.replace(/:.+:/, ':');
}
}
toggleRegExp() {
this.regExp = !this.regExp;
if(!this.regExp) {
this.regexError = null;
}
this.router.navigate([], {
relativeTo: this.route,
replaceUrl: true,
queryParamsHandling: 'merge',
queryParams: {
gqreg: this.regExp ? this.regExp : undefined
}
});
}
closeDialog() {
this.itemSelected = true;
this.dialogRef.close();
}
}

View File

@@ -1,4 +1,4 @@
@import "mixins/common";
@use "mixins/common";
:host {
.recent-header {
@@ -15,6 +15,6 @@
}
.recent-title {
@include recent-title();
@include common.recent-title();
}
}

View File

@@ -4,10 +4,11 @@ import {IRecentTask} from '../../common-dashboard.reducer';
import {ITask} from '~/business-logic/model/al-task';
@Component({
selector: 'sm-dashboard-experiments',
templateUrl: './dashboard-experiments.component.html',
styleUrls: ['./dashboard-experiments.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
selector: 'sm-dashboard-experiments',
templateUrl: './dashboard-experiments.component.html',
styleUrls: ['./dashboard-experiments.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class DashboardExperimentsComponent {
private router = inject(Router);

View File

@@ -1,4 +1,4 @@
@import "mixins/common";
@use "mixins/common";
.view-all {
font-size: 12px;
@@ -16,8 +16,8 @@
.sm-card-list-header {
grid-column: 1 / -1;
padding: 0;
height: unset;
height: 38px;
}
.recent-title {
@include recent-title();
@include common.recent-title();
}

View File

@@ -14,9 +14,10 @@ import {CARDS_IN_ROW} from '../../common-dashboard.const';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
@Component({
selector : 'sm-dashboard-projects',
templateUrl: './dashboard-projects.component.html',
styleUrls : ['./dashboard-projects.component.scss']
selector: 'sm-dashboard-projects',
templateUrl: './dashboard-projects.component.html',
styleUrls: ['./dashboard-projects.component.scss'],
standalone: false
})
export class DashboardProjectsComponent {
private store = inject(Store);

Some files were not shown because too many files have changed in this diff Show More