Compare commits

...
6 Commits
Author SHA1 Message Date
allan 994e7fc973 Merge pull request 'fix: patch npm audit vulnerabilities in sas and client dependencies' (#280) from issue-276 into additional-validations-regex
Build / Build-and-ng-test (pull_request) Successful in 5m40s
Build / Build-and-test-development (pull_request) Successful in 15m30s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m21s
Reviewed-on: #280
2026-07-23 12:23:38 +00:00
YuryShkoda 56b7854db5 chore: bumped @sasjs/core
Build / Build-and-ng-test (pull_request) Successful in 5m6s
Lighthouse Checks / lighthouse (pull_request) Successful in 22m4s
Build / Build-and-test-development (pull_request) Successful in 15m24s
2026-07-23 14:11:59 +03:00
YuryShkoda cac9244f92 fix(deps): retarget Angular upgrade to 20, not 21 (CI install was broken)
Build / Build-and-ng-test (pull_request) Successful in 8m13s
Build / Build-and-test-development (pull_request) Successful in 25m4s
Lighthouse Checks / lighthouse (pull_request) Successful in 52m4s
Retargets @angular/* to 20.3.26/cdk 20.2.14, reverts main.ts's
21-only bootstrap option, and widens Clarity's vendored peerDependencies
metadata to declare Angular 20 support (verified compatible; only 21 actually breaks it).
2026-07-22 17:08:40 +03:00
YuryShkoda 022981390e chore(deps): upgrade Angular 19 -> 21 to resolve audit CVEs
Build / Build-and-ng-test (pull_request) Failing after 52s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m6s
Bumps @angular/* to 21 (passing through 20 as a transient step, since
vendored Clarity has no Angular-20-compatible release) and
@typescript-eslint to 8.65.0 for TS 5.9 compat. Applies the required
ng update migrations, including *ngIf/*ngFor/*ngSwitch -> control-flow
syntax across 37 templates.
2026-07-22 14:56:20 +03:00
YuryShkoda 05fe4744d5 fix: regenerate client lockfile to resolve Angular peer-dependency drift breaking npm ci
Build / Build-and-ng-test (pull_request) Successful in 5m19s
Build / Build-and-test-development (pull_request) Successful in 14m55s
Lighthouse Checks / lighthouse (pull_request) Successful in 21m47s
2026-07-22 11:25:26 +03:00
YuryShkoda e22edf7ed3 fix: patch npm audit vulnerabilities in sas and client dependencies
Build / Build-and-ng-test (pull_request) Failing after 1m18s
Build / Build-and-test-development (pull_request) Has been skipped
Lighthouse Checks / lighthouse (pull_request) Failing after 1m28s
2026-07-22 10:52:52 +03:00
42 changed files with 11731 additions and 11335 deletions
+26
View File
@@ -157,5 +157,31 @@
}
}
}
},
"schematics": {
"@schematics/angular:component": {
"type": "component"
},
"@schematics/angular:directive": {
"type": "directive"
},
"@schematics/angular:service": {
"type": "service"
},
"@schematics/angular:guard": {
"typeSeparator": "."
},
"@schematics/angular:interceptor": {
"typeSeparator": "."
},
"@schematics/angular:module": {
"typeSeparator": "."
},
"@schematics/angular:pipe": {
"typeSeparator": "."
},
"@schematics/angular:resolver": {
"typeSeparator": "."
}
}
}
Binary file not shown.
+5847 -5791
View File
File diff suppressed because it is too large Load Diff
+21 -22
View File
@@ -41,22 +41,22 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^19.2.20",
"@angular/cdk": "^19.2.19",
"@angular/common": "^19.2.20",
"@angular/compiler": "^19.2.20",
"@angular/core": "^19.2.20",
"@angular/forms": "^19.2.20",
"@angular/platform-browser": "^19.2.20",
"@angular/platform-browser-dynamic": "^19.2.20",
"@angular/router": "^19.2.20",
"@angular/animations": "^20.3.26",
"@angular/cdk": "^20.2.14",
"@angular/common": "^20.3.26",
"@angular/compiler": "^20.3.26",
"@angular/core": "^20.3.26",
"@angular/forms": "^20.3.26",
"@angular/platform-browser": "^20.3.26",
"@angular/platform-browser-dynamic": "^20.3.26",
"@angular/router": "^20.3.26",
"@cds/core": "^6.15.1",
"@clr/angular": "file:libraries/clr-angular-17.9.0.tgz",
"@clr/icons": "^13.0.2",
"@clr/ui": "file:libraries/clr-ui-17.9.0.tgz",
"@handsontable/angular-wrapper": "^18.0.0",
"@sasjs/adapter": "^4.17.0",
"@sasjs/utils": "^3.5.3",
"@sasjs/adapter": "^4.17.3",
"@sasjs/utils": "^3.5.9",
"@sheet/crypto": "file:libraries/sheet-crypto.tgz",
"@types/d3-graphviz": "^2.6.7",
"@types/text-encoding": "0.0.35",
@@ -77,7 +77,6 @@
"moment": "^2.30.1",
"ngx-clipboard": "^16.0.0",
"ngx-json-viewer": "file:libraries/ngx-json-viewer-3.2.1.tgz",
"nodejs": "0.0.0",
"os-browserify": "0.3.0",
"rxjs": "^7.8.0",
"save-svg-as-png": "^1.4.17",
@@ -91,16 +90,16 @@
"zone.js": "~0.15.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^19.2.24",
"@angular-devkit/build-angular": "^20.3.32",
"@angular-eslint/builder": "19.8.1",
"@angular-eslint/eslint-plugin": "19.8.1",
"@angular-eslint/eslint-plugin-template": "19.8.1",
"@angular-eslint/schematics": "19.8.1",
"@angular-eslint/template-parser": "19.8.1",
"@angular/cli": "^19.2.24",
"@angular/compiler-cli": "^19.2.20",
"@angular/cli": "^20.3.32",
"@angular/compiler-cli": "^20.3.26",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@compodoc/compodoc": "^1.2.1",
"@compodoc/compodoc": "^2.0.0",
"@cypress/webpack-preprocessor": "^5.17.1",
"@lhci/cli": "^0.15.1",
"@types/core-js": "^2.5.5",
@@ -110,10 +109,10 @@
"@types/lodash-es": "^4.17.3",
"@types/marked": "^4.3.0",
"@types/node": "12.20.50",
"@typescript-eslint/eslint-plugin": "8.31.1",
"@typescript-eslint/parser": "8.31.1",
"@typescript-eslint/eslint-plugin": "8.65.0",
"@typescript-eslint/parser": "8.65.0",
"core-js": "^2.5.4",
"cypress": "^15.14.2",
"cypress": "^15.19.0",
"cypress-file-upload": "^5.0.8",
"cypress-plugin-tab": "^1.0.5",
"cypress-real-events": "^1.8.1",
@@ -135,8 +134,8 @@
"rimraf": "3.0.2",
"ts-loader": "^9.2.8",
"ts-node": "^3.3.0",
"typescript": "~5.8.3",
"wait-on": "^6.0.1",
"typescript": "~5.9.3",
"wait-on": "^9.0.10",
"watch": "^1.0.2"
},
"overrides": {
@@ -144,4 +143,4 @@
"uuid": "11.1.1",
"lighthouse": "13.4.0"
}
}
}
+281 -283
View File
@@ -1,292 +1,287 @@
<div class="main-container">
<ng-container *ngIf="!router.url.includes('licensing')">
<div
*ngIf="
freeTierBanner && (!licenseExpiringDays || licenseExpiringDays < 0)
"
class="alert alert-app-level alert-warning"
id="demo-banner"
role="alert"
>
<ng-container *ngIf="licenceProblem.value === null">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div>
<div class="alert-text">
Data Controller (FREE Tier) - to upgrade contact
<contact-link classes="color-white" />
</div>
</div>
</div>
<a routerLink="/licensing/update" class="update-key"
>Update Licence Key</a
>
</ng-container>
<ng-container *ngIf="licenceProblem.value !== null">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div>
<div class="alert-text">
Data Controller (FREE Tier) - Problem with licence
</div>
</div>
</div>
<a
(click)="licenceProblemDetails(licenceProblem.value)"
class="update-key cursor-pointer"
>More details</a
>
</ng-container>
</div>
<div
*ngIf="licenseExpiringDays && !freeTierBanner"
class="alert alert-app-level alert-danger"
id="demo-banner"
role="alert"
>
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div>
<div class="alert-text">
This license key will expire in {{ licenseExpiringDays }}
{{ licenseExpiringDays === 1 ? 'day' : 'days' }}. Please contact
<contact-link classes="color-white" />
or your reseller to arrange additional licence for site id
{{ syssite.getValue() }}.
</div>
</div>
</div>
<a
*ngIf="!freeTierBanner"
routerLink="/licensing/update"
class="update-key"
>Update Licence Key</a
@if (!router.url.includes('licensing')) {
@if (freeTierBanner && (!licenseExpiringDays || licenseExpiringDays < 0)) {
<div
class="alert alert-app-level alert-warning"
id="demo-banner"
role="alert"
>
</div>
<div
*ngIf="appOverCapacity"
class="alert alert-app-level alert-danger"
id="demo-banner"
role="alert"
>
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
@if (licenceProblem.value === null) {
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon
class="alert-icon"
shape="warning-standard"
></cds-icon>
</div>
<div class="alert-text">
Data Controller (FREE Tier) - to upgrade contact
<contact-link classes="color-white" />
</div>
</div>
</div>
<div class="alert-text">
The registered number of users exceeds the limit specified for your
license. Please contact
<contact-link classes="color-white" />
or your reseller to arrange additional licence for site id
{{ syssite.getValue() }}.
</div>
</div>
</div>
<a
*ngIf="!licenseExpiringDays && !freeTierBanner"
routerLink="/licensing/update"
class="update-key"
>Update Licence Key</a
>
</div>
</ng-container>
<header class="app-header" *ngIf="!embed">
<!-- <button
*ngIf="
isMainRoute('view') ||
(isMainRoute('home') && !router.url.includes('licensing'))
"
class="header-hamburger-trigger"
(click)="toggleSidebar()"
type="button"
>
<span></span>
</button> -->
<div
*ngIf="
isMainRoute('view') ||
(isMainRoute('home') && !router.url.includes('licensing'))
"
(click)="toggleSidebar()"
type="button"
aria-label="Toggle sidebar"
class="cursor-pointer select-none ml-10 d-flex clr-justify-content-center clr-align-items-center"
>
<clr-icon size="24" shape="tree-view" aria-hidden="true"></clr-icon>
</div>
<div class="logo d-flex clr-align-items-center">
<a
*ngIf="!router.url.includes('deploy')"
href="#"
[routerLink]="['/']"
class="nav-link"
>
<img
class="without-text d-block d-md-none"
src="images/dc-logo.svg"
alt="datacontroller logo without text"
/>
<img
class="with-text d-none d-md-block"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
</a>
<a *ngIf="router.url.includes('deploy')">
<span class="clr-icon header-logo ml-10"></span>
</a>
</div>
<ng-container
*ngIf="
!router.url.includes('deploy') && !router.url.includes('licensing')
"
>
<div class="header-nav d-flex d-sm-none">
<clr-dropdown>
<button
class="nav-icon color-white-i"
clrDropdownTrigger
aria-label="toggle settings menu"
<a routerLink="/licensing/update" class="update-key"
>Update Licence Key</a
>
Menu
<!-- <clr-icon size="20" shape="bars"></clr-icon> -->
</button>
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
<a [routerLink]="['/home']" clrDropdownItem>LOAD</a>
<a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
</clr-dropdown-menu>
</clr-dropdown>
}
@if (licenceProblem.value !== null) {
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon
class="alert-icon"
shape="warning-standard"
></cds-icon>
</div>
<div class="alert-text">
Data Controller (FREE Tier) - Problem with licence
</div>
</div>
</div>
<a
(click)="licenceProblemDetails(licenceProblem.value)"
class="update-key cursor-pointer"
>More details</a
>
}
</div>
<div class="header-nav d-none d-sm-flex">
<a
[routerLink]="['/view']"
class="nav-link nav-text"
routerLinkActive="active"
>VIEW</a
>
<a
[routerLink]="['/home']"
class="nav-link nav-text"
[class.active]="
router.url.includes('editor') ||
router.url.includes('edit-record') ||
router.url.includes('home')
"
>LOAD</a
>
<a
[routerLink]="['/review/submitted']"
[class.active]="
router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history')
"
class="nav-link nav-text cursor-pointer"
>REVIEW</a
>
}
@if (licenseExpiringDays && !freeTierBanner) {
<div
class="alert alert-app-level alert-danger"
id="demo-banner"
role="alert"
>
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div>
<div class="alert-text">
This license key will expire in {{ licenseExpiringDays }}
{{ licenseExpiringDays === 1 ? 'day' : 'days' }}. Please contact
<contact-link classes="color-white" />
or your reseller to arrange additional licence for site id
{{ syssite.getValue() }}.
</div>
</div>
</div>
@if (!freeTierBanner) {
<a routerLink="/licensing/update" class="update-key"
>Update Licence Key</a
>
}
</div>
</ng-container>
}
@if (appOverCapacity) {
<div
class="alert alert-app-level alert-danger"
id="demo-banner"
role="alert"
>
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<cds-icon class="alert-icon" shape="warning-standard"></cds-icon>
</div>
<div class="alert-text">
The registered number of users exceeds the limit specified for
your license. Please contact
<contact-link classes="color-white" />
or your reseller to arrange additional licence for site id
{{ syssite.getValue() }}.
</div>
</div>
</div>
@if (!licenseExpiringDays && !freeTierBanner) {
<a routerLink="/licensing/update" class="update-key"
>Update Licence Key</a
>
}
</div>
}
}
<app-header-actions></app-header-actions>
</header>
<nav
*ngIf="
!embed &&
(router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history'))
"
class="subnav"
>
<ul class="nav">
<li class="nav-item">
<a
[routerLink]="['/review/submitted']"
class="nav-link nav-text"
routerLinkActive="active"
>SUBMIT</a
@if (!embed) {
<header class="app-header">
<!-- <button
*ngIf="
isMainRoute('view') ||
(isMainRoute('home') && !router.url.includes('licensing'))
"
class="header-hamburger-trigger"
(click)="toggleSidebar()"
type="button"
>
<span></span>
</button> -->
@if (
isMainRoute('view') ||
(isMainRoute('home') && !router.url.includes('licensing'))
) {
<div
(click)="toggleSidebar()"
type="button"
aria-label="Toggle sidebar"
class="cursor-pointer select-none ml-10 d-flex clr-justify-content-center clr-align-items-center"
>
</li>
<li class="nav-item">
<a
[routerLink]="['/review/approve']"
class="nav-link nav-text"
[class.active]="router.url.includes('approve')"
routerLinkActive="active"
>APPROVE</a
>
</li>
<li class="nav-item">
<a
[routerLink]="['/review/history']"
class="nav-link nav-text"
routerLinkActive="active"
>HISTORY</a
>
</li>
</ul>
</nav>
<clr-icon size="24" shape="tree-view" aria-hidden="true"></clr-icon>
</div>
}
<div class="logo d-flex clr-align-items-center">
@if (!router.url.includes('deploy')) {
<a href="#" [routerLink]="['/']" class="nav-link">
<img
class="without-text d-block d-md-none"
src="images/dc-logo.svg"
alt="datacontroller logo without text"
/>
<img
class="with-text d-none d-md-block"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
</a>
}
@if (router.url.includes('deploy')) {
<a>
<span class="clr-icon header-logo ml-10"></span>
</a>
}
</div>
@if (
!router.url.includes('deploy') && !router.url.includes('licensing')
) {
<div class="header-nav d-flex d-sm-none">
<clr-dropdown>
<button
class="nav-icon color-white-i"
clrDropdownTrigger
aria-label="toggle settings menu"
>
Menu
<!-- <clr-icon size="20" shape="bars"></clr-icon> -->
</button>
<clr-dropdown-menu *clrIfOpen clrPosition="bottom-left">
<a [routerLink]="['/view']" clrDropdownItem>VIEW</a>
<a [routerLink]="['/home']" clrDropdownItem>LOAD</a>
<a [routerLink]="['/review/submitted']" clrDropdownItem>REVIEW</a>
</clr-dropdown-menu>
</clr-dropdown>
</div>
<div class="header-nav d-none d-sm-flex">
<a
[routerLink]="['/view']"
class="nav-link nav-text"
routerLinkActive="active"
>VIEW</a
>
<a
[routerLink]="['/home']"
class="nav-link nav-text"
[class.active]="
router.url.includes('editor') ||
router.url.includes('edit-record') ||
router.url.includes('home')
"
>LOAD</a
>
<a
[routerLink]="['/review/submitted']"
[class.active]="
router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history')
"
class="nav-link nav-text cursor-pointer"
>REVIEW</a
>
</div>
}
<app-header-actions></app-header-actions>
</header>
}
@if (
!embed &&
(router.url.includes('submitted') ||
router.url.includes('approve') ||
router.url.includes('history'))
) {
<nav class="subnav">
<ul class="nav">
<li class="nav-item">
<a
[routerLink]="['/review/submitted']"
class="nav-link nav-text"
routerLinkActive="active"
>SUBMIT</a
>
</li>
<li class="nav-item">
<a
[routerLink]="['/review/approve']"
class="nav-link nav-text"
[class.active]="router.url.includes('approve')"
routerLinkActive="active"
>APPROVE</a
>
</li>
<li class="nav-item">
<a
[routerLink]="['/review/history']"
class="nav-link nav-text"
routerLinkActive="active"
>HISTORY</a
>
</li>
</ul>
</nav>
}
<app-alerts *ngIf="!errTop"></app-alerts>
@if (!errTop) {
<app-alerts></app-alerts>
}
<app-requests-modal [(opened)]="requestsModal"></app-requests-modal>
<app-excel-password-modal></app-excel-password-modal>
<!-- <app-terms *ngIf="showRegistration"></app-terms> -->
<!-- VA embed, back to Editor button -->
<div
*ngIf="embed === 'va' && vaEditorLibds && !isMainRoute('/editor')"
class="va-back-bar"
>
<button
type="button"
class="btn btn-sm btn-primary"
(click)="backToEditor()"
>
<clr-icon
aria-hidden="true"
shape="caret"
dir="left"
size="16"
></clr-icon>
Back to Edit table
</button>
</div>
@if (embed === 'va' && vaEditorLibds && !isMainRoute('/editor')) {
<div class="va-back-bar">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="backToEditor()"
>
<clr-icon
aria-hidden="true"
shape="caret"
dir="left"
size="16"
></clr-icon>
Back to Edit table
</button>
</div>
}
<router-outlet *ngIf="startupDataLoaded"></router-outlet>
@if (startupDataLoaded) {
<router-outlet></router-outlet>
}
<app-login></app-login>
<app-alerts *ngIf="errTop"></app-alerts>
<app-info-modal
*ngFor="let abort of sasjsAborts"
[data]="abort"
[forceReload]="!startupDataLoaded && sasjsAborts.length === 1"
(onConfirmModalClick)="closeAbortModal(abort.id!)"
>
</app-info-modal>
@if (errTop) {
<app-alerts></app-alerts>
}
@for (abort of sasjsAborts; track abort) {
<app-info-modal
[data]="abort"
[forceReload]="!startupDataLoaded && sasjsAborts.length === 1"
(onConfirmModalClick)="closeAbortModal(abort.id!)"
>
</app-info-modal>
}
<clr-modal
appDragNdrop
@@ -309,17 +304,20 @@
</div>
<!-- App Loading Page -->
<div *ngIf="!startupDataLoaded" class="app-loading">
<img
class="loading-logo"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
<div *ngIf="appActive === null" class="slider">
<div class="line"></div>
<div class="subline inc"></div>
<div class="subline dec"></div>
@if (!startupDataLoaded) {
<div class="app-loading">
<img
class="loading-logo"
src="images/datacontroller.svg"
alt="datacontroller logo"
/>
@if (appActive === null) {
<div class="slider">
<div class="line"></div>
<div class="subline inc"></div>
<div class="subline dec"></div>
</div>
}
</div>
</div>
}
<!-- /App Loading Page -->
+92 -91
View File
@@ -1,100 +1,101 @@
<main class="content-area position-relative">
<div class="clr-row">
<!-- T&C section -->
<div *ngIf="step === 0" id="TCS" class="card">
<div class="card-header">Terms and Conditions</div>
<div class="card-block">
<div class="card-text">
<p class="mt-0">
The Demo version of Data Controller is free for EVALUATION purposes
only. Before proceeding with configuration, please confirm that you
have read, understood, and agreed to the
<a
href="https://docs.datacontroller.io/evaluation-licence-agreement"
target="_blank"
>Data Controller for SAS&#169; Evaluation Agreement</a
>.
</p>
@if (step === 0) {
<div id="TCS" class="card">
<div class="card-header">Terms and Conditions</div>
<div class="card-block">
<div class="card-text">
<p class="mt-0">
The Demo version of Data Controller is free for EVALUATION
purposes only. Before proceeding with configuration, please
confirm that you have read, understood, and agreed to the
<a
href="https://docs.datacontroller.io/evaluation-licence-agreement"
target="_blank"
>Data Controller for SAS&#169; Evaluation Agreement</a
>.
</p>
</div>
<hr class="light" />
<clr-checkbox-wrapper>
<input clrCheckbox type="checkbox" (change)="termsAgreeChange()" />
<label
>I have read and agree to the terms of the
<a
href="https://docs.datacontroller.io/evaluation-licence-agreement"
target="_blank"
>Data Controller for SAS&#169; Evaluation Agreement</a
></label
>
</clr-checkbox-wrapper>
<!-- <hr />
<div class="clr-checkbox-wrapper">
<input
[(ngModel)]="autodeploy"
type="checkbox"
id="checkbox2"
class="clr-checkbox"
checked
/>
<label for="checkbox2"
>Autodeploy
{{ !jsonFile ? '(json file is not available)' : '' }}</label
>
</div> -->
</div>
<hr class="light" />
<clr-checkbox-wrapper>
<input clrCheckbox type="checkbox" (change)="termsAgreeChange()" />
<label
>I have read and agree to the terms of the
<a
href="https://docs.datacontroller.io/evaluation-licence-agreement"
target="_blank"
>Data Controller for SAS&#169; Evaluation Agreement</a
></label
>
</clr-checkbox-wrapper>
<!-- <hr />
<div class="clr-checkbox-wrapper">
<input
[(ngModel)]="autodeploy"
type="checkbox"
id="checkbox2"
class="clr-checkbox"
checked
/>
<label for="checkbox2"
>Autodeploy
{{ !jsonFile ? '(json file is not available)' : '' }}</label
>
</div> -->
</div>
</div>
}
</div>
<!-- T&C section end -->
<ng-container *ngIf="step > 0" [ngSwitch]="true">
<ng-container *ngSwitchCase="sasJsConfig.serverType === ServerType.SasViya">
<div *ngIf="autodeploy" class="autodeploy-section card">
<app-automatic-deploy
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
[appLoc]="appLoc"
[dcPath]="dcPath"
[selectedAdminGroup]="selectedAdminGroup"
(onNavigateToHome)="onNavigateToHome()"
></app-automatic-deploy>
</div>
<div *ngIf="!autodeploy" id="mainbody" class="card">
<app-manual-deploy
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-manual-deploy>
</div>
</ng-container>
<ng-container *ngSwitchCase="sasJsConfig.serverType === ServerType.Sasjs">
<div class="autodeploy-section card">
<app-sasjs-configurator
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-sasjs-configurator>
</div>
</ng-container>
<ng-container *ngSwitchCase="sasJsConfig.serverType === ServerType.Sas9">
<div class="autodeploy-section card">
<app-sasjs-configurator
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-sasjs-configurator>
</div>
</ng-container>
</ng-container>
@if (step > 0) {
@switch (true) {
@case (sasJsConfig.serverType === ServerType.SasViya) {
@if (autodeploy) {
<div class="autodeploy-section card">
<app-automatic-deploy
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
[appLoc]="appLoc"
[dcPath]="dcPath"
[selectedAdminGroup]="selectedAdminGroup"
(onNavigateToHome)="onNavigateToHome()"
></app-automatic-deploy>
</div>
}
@if (!autodeploy) {
<div id="mainbody" class="card">
<app-manual-deploy
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-manual-deploy>
</div>
}
}
@case (sasJsConfig.serverType === ServerType.Sasjs) {
<div class="autodeploy-section card">
<app-sasjs-configurator
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-sasjs-configurator>
</div>
}
@case (sasJsConfig.serverType === ServerType.Sas9) {
<div class="autodeploy-section card">
<app-sasjs-configurator
[sasJs]="sasJs"
[sasJsConfig]="sasJsConfig"
[dcAdapterSettings]="dcAdapterSettings"
(onNavigateToHome)="onNavigateToHome()"
></app-sasjs-configurator>
</div>
}
}
}
</main>
@@ -1,91 +1,80 @@
<div *ngIf="autodeploying" class="auto-deploy">
<div class="spinner-box">
<ng-container *ngIf="!autodeployDone">
<span class="spinner spinner-md"> Loading... </span>
<p>Deploying...</p>
</ng-container>
<ng-container *ngIf="autodeployDone">
<p class="m-0 align-self-start">Done</p>
<hr class="w-100" />
<div
*ngIf="autoDeployStatus.deployServicePack !== null"
class="deploy-status-row"
>
<clr-icon
*ngIf="autoDeployStatus.deployServicePack === true"
class="deploy-success"
shape="success-standard"
></clr-icon>
<clr-icon
*ngIf="!autoDeployStatus.deployServicePack === false"
class="deploy-error"
shape="times-circle"
></clr-icon>
<p>Deploy SAS Jobs</p>
</div>
<div
*ngIf="autoDeployStatus.runMakeData !== null"
class="deploy-status-row"
>
<clr-icon
*ngIf="autoDeployStatus.runMakeData"
class="deploy-success"
shape="success-standard"
></clr-icon>
<clr-icon
*ngIf="autoDeployStatus.runMakeData === false"
class="deploy-error"
shape="times-circle"
></clr-icon>
<p>Create database</p>
</div>
<hr class="w-100" />
<div class="buttons">
<button (click)="navigateToHome()" class="btn btn-primary mt-15 mr-0">
<clr-icon
*ngIf="
@if (autodeploying) {
<div class="auto-deploy">
<div class="spinner-box">
@if (!autodeployDone) {
<span class="spinner spinner-md"> Loading... </span>
<p>Deploying...</p>
}
@if (autodeployDone) {
<p class="m-0 align-self-start">Done</p>
<hr class="w-100" />
@if (autoDeployStatus.deployServicePack !== null) {
<div class="deploy-status-row">
@if (autoDeployStatus.deployServicePack === true) {
<clr-icon
class="deploy-success"
shape="success-standard"
></clr-icon>
}
@if (!autoDeployStatus.deployServicePack === false) {
<clr-icon class="deploy-error" shape="times-circle"></clr-icon>
}
<p>Deploy SAS Jobs</p>
</div>
}
@if (autoDeployStatus.runMakeData !== null) {
<div class="deploy-status-row">
@if (autoDeployStatus.runMakeData) {
<clr-icon
class="deploy-success"
shape="success-standard"
></clr-icon>
}
@if (autoDeployStatus.runMakeData === false) {
<clr-icon class="deploy-error" shape="times-circle"></clr-icon>
}
<p>Create database</p>
</div>
}
<hr class="w-100" />
<div class="buttons">
<button (click)="navigateToHome()" class="btn btn-primary mt-15 mr-0">
@if (
autoDeployStatus.deployServicePack === false ||
autoDeployStatus.runMakeData === false
) {
<clr-icon class="deploy-error" shape="times-circle"></clr-icon>
}
LAUNCH
</button>
<button
(click)="
downloadFile(makeDataResponse, 'create-database-log', 'txt')
"
class="deploy-error"
shape="times-circle"
></clr-icon>
LAUNCH
</button>
<button
(click)="downloadFile(makeDataResponse, 'create-database-log', 'txt')"
class="btn btn-primary-outline mt-15 mr-0"
>
Download log
</button>
</div>
<hr class="w-100" />
<div class="buttons">
<button
(click)="autodeploying = false; autodeployDone = false"
class="btn btn-primary-outline mt-15 mr-0 align-self-end"
>
Close
</button>
<button
(click)="openSasRequestsModal()"
class="btn btn-primary-outline mt-15 mr-0 align-self-end"
>
SAS Requests
</button>
</div>
</ng-container>
class="btn btn-primary-outline mt-15 mr-0"
>
Download log
</button>
</div>
<hr class="w-100" />
<div class="buttons">
<button
(click)="autodeploying = false; autodeployDone = false"
class="btn btn-primary-outline mt-15 mr-0 align-self-end"
>
Close
</button>
<button
(click)="openSasRequestsModal()"
class="btn btn-primary-outline mt-15 mr-0 align-self-end"
>
SAS Requests
</button>
</div>
}
</div>
</div>
</div>
}
<h4 class="text-center my-15">Viya Deploy</h4>
<hr />
@@ -106,21 +95,18 @@
<label for="dcloc" class="mt-20 clr-control-label">SAS Admin group</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper small-mt">
<select
*ngIf="!adminGroupsLoading"
clrSelect
name="options"
[(ngModel)]="selectedAdminGroup"
>
<option *ngFor="let adminGroup of adminGroups" [value]="adminGroup.id">
{{ adminGroup.name }}
</option>
</select>
<clr-spinner
clrInline
class="spinner-sm"
*ngIf="adminGroupsLoading"
></clr-spinner>
@if (!adminGroupsLoading) {
<select clrSelect name="options" [(ngModel)]="selectedAdminGroup">
@for (adminGroup of adminGroups; track adminGroup) {
<option [value]="adminGroup.id">
{{ adminGroup.name }}
</option>
}
</select>
}
@if (adminGroupsLoading) {
<clr-spinner clrInline class="spinner-sm"></clr-spinner>
}
</div>
</div>
@@ -129,36 +115,34 @@
>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper small-mt">
<select
*ngIf="!computeContextsLoading"
clrSelect
name="options"
(ngModelChange)="onComputeContextChange($event)"
[(ngModel)]="selectedComputeContext"
>
<option
*ngFor="let computeContext of computeContexts"
[value]="computeContext.id"
@if (!computeContextsLoading) {
<select
clrSelect
name="options"
(ngModelChange)="onComputeContextChange($event)"
[(ngModel)]="selectedComputeContext"
>
{{ computeContext.name }}
</option>
</select>
<clr-spinner
clrInline
class="spinner-sm"
*ngIf="computeContextsLoading"
></clr-spinner>
@for (computeContext of computeContexts; track computeContext) {
<option [value]="computeContext.id">
{{ computeContext.name }}
</option>
}
</select>
}
@if (computeContextsLoading) {
<clr-spinner clrInline class="spinner-sm"></clr-spinner>
}
</div>
</div>
<ng-container *ngIf="runningAsUser">
@if (runningAsUser) {
<label for="dcloc" class="mt-20 clr-control-label">Running as user:</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<p class="mt-0">{{ runningAsUser }}</p>
</div>
</div>
</ng-container>
}
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
@@ -169,7 +153,7 @@
(click)="recreateDatabaseClicked($event)"
type="checkbox"
checked
/>
/>
<label>Recreate database</label>
</clr-checkbox-wrapper> -->
@@ -185,18 +169,18 @@
<!-- Keeping this for a reference in case future VIYA changes and starts allowing separate backend and frontend) -->
<!-- <button
(click)="executeJson()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
[disabled]="!jsonFile"
(click)="executeJson()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
[disabled]="!jsonFile"
>
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
Deploy {{ !jsonFile ? '(json file is not available)' : '' }}
</button> -->
<!-- <button
(click)="uploadJsonAuto.click()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
(click)="uploadJsonAuto.click()"
class="btn-autodeploy btn btn-primary d-inline-block mr-10"
>
Upload different file to deploy
Upload different file to deploy
</button>
<input
#uploadJsonAuto
@@ -204,7 +188,7 @@
hidden
(click)="clearUploadInput($event)"
(change)="onJsonFileChange($event)"
/> -->
/> -->
<clr-modal [(clrModalOpen)]="recreateDatabaseModal" [clrModalClosable]="false">
<h3 class="modal-title">Warning</h3>
@@ -10,29 +10,29 @@
</div>
<div class="card-block">
<!-- <div class="card-title">
Client Details
</div> -->
Client Details
</div> -->
<div *ngIf="needsLogin" id="loginForm" class="d-none">
<p class="mb-10">Please log in first</p>
<label for="username" class="clr-control-label">Username</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<input type="text" id="username" class="clr-input" />
@if (needsLogin) {
<div id="loginForm" class="d-none">
<p class="mb-10">Please log in first</p>
<label for="username" class="clr-control-label">Username</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<input type="text" id="username" class="clr-input" />
</div>
</div>
</div>
<label for="password" class="clr-control-label">Password</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<input type="password" id="password" class="clr-input" />
<label for="password" class="clr-control-label">Password</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
<input type="password" id="password" class="clr-input" />
</div>
</div>
<button class="btn btn-primary d-none" id="loginBtn">Log in</button>
</div>
}
<button class="btn btn-primary d-none" id="loginBtn">Log in</button>
</div>
<ng-container *ngIf="!needsLogin">
@if (!needsLogin) {
<form>
<div class="clr-form-control">
<label for="select-full" class="clr-control-label">Admin group</label>
@@ -45,17 +45,15 @@
id="adminGroupsSelect"
class="clr-select"
>
<option
*ngFor="let adminGroup of adminGroups"
[value]="adminGroup.id"
>
{{ adminGroup.name }}
</option>
@for (adminGroup of adminGroups; track adminGroup) {
<option [value]="adminGroup.id">
{{ adminGroup.name }}
</option>
}
</select>
</div>
</div>
</div>
<div class="clr-form-control">
<div [class.hidden]="contextsLoading">
<label for="select-full" class="clr-control-label">Context</label>
@@ -67,15 +65,14 @@
name="selectedContext"
class="clr-select"
>
<option
*ngFor="let context of allContexts"
[value]="context.name"
>
{{ context.name }}
<span *ngIf="(context.attributes | json) != '{}'"
>( {{ context.attributes.sysUserId }} )</span
>
</option>
@for (context of allContexts; track context) {
<option [value]="context.name">
{{ context.name }}
@if ((context.attributes | json) != '{}') {
<span>( {{ context.attributes.sysUserId }} )</span>
}
</option>
}
</select>
</div>
</div>
@@ -88,14 +85,12 @@
<clr-icon shape="play"></clr-icon>
</button>
</div>
<div [class.hidden]="!contextsLoading" class="d-flex">
<span class="spinner spinner-inline mr-10">
Loading contexts...
</span>
<span> Loading contexts... </span>
</div>
<label for="dcloc" class="mt-20 clr-control-label">DC Loc</label>
<div class="mb-10 clr-control-container">
<div class="clr-input-wrapper">
@@ -110,7 +105,6 @@
/>
</div>
</div>
<p class="mt-10">
Select JSON file to upload (json build file preloaded):
</p>
@@ -121,10 +115,9 @@
(change)="onJsonFileChange($event)"
/>
<!-- <button *ngIf="downloadFileBtn" (click)="downloadSasPrecodeFile()" style="width: 40px; min-width: 0;" class="btn btn-sm btn-icon">
<clr-icon shape="download"></clr-icon>
</button> -->
<clr-icon shape="download"></clr-icon>
</button> -->
</div>
<div class="mt-20 d-flex align-items-center">
<button
(click)="executeJson()"
@@ -135,13 +128,13 @@
>
SUBMIT JSON
</button>
<span *ngIf="isJsonSubmitted">JSON Submitted Successfully</span>
@if (isJsonSubmitted) {
<span>JSON Submitted Successfully</span>
}
<!-- <span *ngIf="executingScript" class="spinner spinner-inline ml-3">
Loading...
</span> -->
Loading...
</span> -->
</div>
<p class="mt-10">Select SAS file to upload:</p>
<div class="d-flex flex-column">
<input
@@ -149,15 +142,15 @@
(click)="clearUploadInput($event)"
(change)="onSasFileChange($event); downloadFileBtn = true"
/>
<button
*ngIf="downloadFileBtn"
(click)="downloadSasPrecodeFile()"
class="btn btn-sm btn-icon min-w-0 w-40"
>
<clr-icon shape="download"></clr-icon>
</button>
@if (downloadFileBtn) {
<button
(click)="downloadSasPrecodeFile()"
class="btn btn-sm btn-icon min-w-0 w-40"
>
<clr-icon shape="download"></clr-icon>
</button>
}
</div>
<div class="mt-20 d-flex align-items-center">
<button
(click)="executeSAS()"
@@ -168,17 +161,14 @@
>
SUBMIT
</button>
<!-- <span *ngIf="executingScript" class="spinner spinner-inline ml-3">
Loading...
</span> -->
Loading...
</span> -->
</div>
</div>
<ng-container *ngIf="jobLog.length > 0">
@if (jobLog.length > 0) {
<p class="mb-0 mt-10">File execute completed</p>
<hr />
<div>
<button
(click)="downloadFile(jobLog, 'execute-script-log', 'txt')"
@@ -187,8 +177,7 @@
Download log
</button>
</div>
</ng-container>
}
<button
[clrLoading]="createDatabaseLoading"
(click)="createDatabase()"
@@ -196,52 +185,45 @@
>
Create Database
</button>
<ng-container *ngIf="makeDataResponse.length > 0">
@if (makeDataResponse.length > 0) {
<p class="mb-0 mt-10">Create Database Completed</p>
<hr />
<div *ngIf="makeDataResponse.length > 0" class="log-wrapper">
{{ makeDataResponse }}
</div>
@if (makeDataResponse.length > 0) {
<div class="log-wrapper">
{{ makeDataResponse }}
</div>
}
<button (click)="navigateToHome()" class="btn btn-primary mt-15">
Let's get started
</button>
<button
(click)="downloadFile(makeDataResponse, 'create-database-log', 'txt')"
class="btn btn-primary mt-15"
>
Download log
</button>
<button (click)="validateDeploy()" class="btn btn-primary mt-15">
Validate
</button>
<div
*ngIf="validationState !== 'none' || isValidating"
class="validation-bar"
>
<ng-container *ngIf="isValidating">
<span class="spinner spinner-inline mr-10">
Validating deploy...
</span>
<span> Validating deploy... </span>
</ng-container>
<ng-container *ngIf="!isValidating && validationState === 'error'">
<clr-icon shape="exclamation-circle" class="is-error"></clr-icon>
<span> Validation failed </span>
</ng-container>
<ng-container *ngIf="!isValidating && validationState === 'success'">
<clr-icon shape="check-circle" class="is-success"></clr-icon>
<span> Validation succeeded </span>
</ng-container>
</div>
</ng-container>
@if (validationState !== 'none' || isValidating) {
<div class="validation-bar">
@if (isValidating) {
<span class="spinner spinner-inline mr-10">
Validating deploy...
</span>
<span> Validating deploy... </span>
}
@if (!isValidating && validationState === 'error') {
<clr-icon shape="exclamation-circle" class="is-error"></clr-icon>
<span> Validation failed </span>
}
@if (!isValidating && validationState === 'success') {
<clr-icon shape="check-circle" class="is-success"></clr-icon>
<span> Validation succeeded </span>
}
</div>
}
}
</form>
</ng-container>
}
</div>
@@ -1,6 +1,8 @@
<div *ngIf="loading" class="thinProgress progresStatic progress loop">
<progress></progress>
</div>
@if (loading) {
<div class="thinProgress progresStatic progress loop">
<progress></progress>
</div>
}
<h4 class="text-center my-15">Sasjs Deploy</h4>
<hr class="light" />
@@ -27,13 +29,13 @@
</div>
<!-- <button
(click)="directoryBrowse.click()"
class="mt-15 text-center"
class="btn btn-sm btn-outline"
>
Browse
</button>
<input #directoryBrowse hidden (change)="dirChange($event)" type="file" id="ctrl" webkitdirectory directory multiple/> -->
(click)="directoryBrowse.click()"
class="mt-15 text-center"
class="btn btn-sm btn-outline"
>
Browse
</button>
<input #directoryBrowse hidden (change)="dirChange($event)" type="file" id="ctrl" webkitdirectory directory multiple/> -->
</div>
<p class="m-0 mt-10">
@@ -45,12 +47,11 @@
<label class="mt-20 clr-control-label">Data Controller Admin group</label>
<clr-select-container class="mb-10 mt-0 w-50vw">
<select [(ngModel)]="dcAdminGroup" clrSelect>
<option
*ngFor="let adminGroup of dcAdminGroupList"
[value]="adminGroup.GROUPNAME"
>
{{ adminGroup.GROUPNAME }} - {{ adminGroup.GROUPDESC }}
</option>
@for (adminGroup of dcAdminGroupList; track adminGroup) {
<option [value]="adminGroup.GROUPNAME">
{{ adminGroup.GROUPNAME }} - {{ adminGroup.GROUPDESC }}
</option>
}
</select>
</clr-select-container>
@@ -1,7 +1,9 @@
<div>
<p class="m-0" *ngFor="let state of processedStates; let i = index">
{{ state }}
</p>
@for (state of processedStates; track state; let i = $index) {
<p class="m-0">
{{ state }}
</p>
}
</div>
<div class="text-center">
<span class="spinner spinner-sm vertical-align-middle">Loading...</span>
File diff suppressed because it is too large Load Diff
+153 -130
View File
@@ -1,144 +1,167 @@
<app-sidebar class="sidebar-height">
<clr-tree>
<clr-tree-node *ngIf="groups" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Groups"
name="input"
[(ngModel)]="groupSearch"
(keyup)="groupListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="groupSearch = ''; groupListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let group of groups">
<clr-tree-node
(click)="groupOnClick(group)"
*ngIf="!group['hidden']"
[class.active]="group.GROUPURI === groupUri"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="users"></clr-icon>
{{ group.GROUPNAME }}
</p>
@if (groups) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Groups"
name="input"
[(ngModel)]="groupSearch"
(keyup)="groupListOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
(click)="groupSearch = ''; groupListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (group of groups; track group) {
@if (!group['hidden']) {
<clr-tree-node
(click)="groupOnClick(group)"
[class.active]="group.GROUPURI === groupUri"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="users"></clr-icon>
{{ group.GROUPNAME }}
</p>
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
<div class="content-area">
<div *ngIf="loading" class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
<div *ngIf="groupMembers && !loading">
<div *ngIf="serverType !== ServerType.SasViya" class="clr-row">
<div class="clr-col-8">
<table class="table group-info">
<tbody>
<tr>
<td class="left">
<p class="group-info-text">
<b>{{ groupName }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<i>{{ groupDesc || 'no description' }}</i>
</td>
</tr>
</tbody>
</table>
</div>
@if (loading) {
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
<div class="clr-row">
<div class="clr-col-8">
<div class="card group-data">
<div *ngIf="serverType !== ServerType.SasViya">
<h3>MEMBERS ({{ groupMemberCount }})</h3>
<h5 *ngIf="groupMemberCount == 0">No Members Present</h5>
<div class="table-container">
<table *ngIf="groupMemberCount != 0" class="table member-table">
<thead>
<tr>
<ng-container *ngIf="serverType === ServerType.Sas9">
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
</ng-container>
<ng-container *ngIf="serverType === ServerType.Sasjs">
<td class="width-25"><b>ID</b></td>
<td class="width-25"><b>DISPLAY NAME</b></td>
<td class="width-25"><b>USER NAME</b></td>
</ng-container>
</tr>
</thead>
<tbody>
<tr
[routerLink]="
'/view/usernav/users/' + (member.URIMEM || member.ID)
"
*ngFor="let member of groupMembers"
>
<ng-container *ngIf="serverType === ServerType.Sas9">
<td class="">{{ member.MEMBERNAME }}</td>
<td class="">{{ member.EMAIL }}</td>
<td class="">{{ member.MEMBERCREATED }}</td>
<td class="">{{ member.MEMBERUPDATED }}</td>
</ng-container>
<ng-container *ngIf="serverType === ServerType.Sasjs">
<td class="">{{ member.ID }}</td>
<td class="">{{ member.DISPLAYNAME }}</td>
<td class="">{{ member.USERNAME }}</td>
</ng-container>
</tr>
</tbody>
</table>
</div>
}
@if (groupMembers && !loading) {
<div>
@if (serverType !== ServerType.SasViya) {
<div class="clr-row">
<div class="clr-col-8">
<table class="table group-info">
<tbody>
<tr>
<td class="left">
<p class="group-info-text">
<b>{{ groupName }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<i>{{ groupDesc || 'no description' }}</i>
</td>
</tr>
</tbody>
</table>
</div>
<div *ngIf="serverType === ServerType.SasViya">
<h3>{{ groupName }}</h3>
<h5 *ngIf="groupMemberCount == 0">No Members Present</h5>
<div class="table-container">
<table *ngIf="groupMemberCount != 0" class="table member-table">
<thead>
<tr>
<td>
<b>MEMBERS ({{ groupMemberCount }})</b>
</td>
</tr>
</thead>
<tbody>
<tr
[routerLink]="'/view/usernav/users/' + member.MEMBERID"
*ngFor="let member of groupMembers"
>
<td class="">{{ member.MEMBERNAME }}</td>
</tr>
</tbody>
</table>
</div>
</div>
}
<div class="clr-row">
<div class="clr-col-8">
<div class="card group-data">
@if (serverType !== ServerType.SasViya) {
<div>
<h3>MEMBERS ({{ groupMemberCount }})</h3>
@if (groupMemberCount == 0) {
<h5>No Members Present</h5>
}
<div class="table-container">
@if (groupMemberCount != 0) {
<table class="table member-table">
<thead>
<tr>
@if (serverType === ServerType.Sas9) {
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
}
@if (serverType === ServerType.Sasjs) {
<td class="width-25"><b>ID</b></td>
<td class="width-25"><b>DISPLAY NAME</b></td>
<td class="width-25"><b>USER NAME</b></td>
}
</tr>
</thead>
<tbody>
@for (member of groupMembers; track member) {
<tr
[routerLink]="
'/view/usernav/users/' +
(member.URIMEM || member.ID)
"
>
@if (serverType === ServerType.Sas9) {
<td class="">{{ member.MEMBERNAME }}</td>
<td class="">{{ member.EMAIL }}</td>
<td class="">{{ member.MEMBERCREATED }}</td>
<td class="">{{ member.MEMBERUPDATED }}</td>
}
@if (serverType === ServerType.Sasjs) {
<td class="">{{ member.ID }}</td>
<td class="">{{ member.DISPLAYNAME }}</td>
<td class="">{{ member.USERNAME }}</td>
}
</tr>
}
</tbody>
</table>
}
</div>
</div>
}
@if (serverType === ServerType.SasViya) {
<div>
<h3>{{ groupName }}</h3>
@if (groupMemberCount == 0) {
<h5>No Members Present</h5>
}
<div class="table-container">
@if (groupMemberCount != 0) {
<table class="table member-table">
<thead>
<tr>
<td>
<b>MEMBERS ({{ groupMemberCount }})</b>
</td>
</tr>
</thead>
<tbody>
@for (member of groupMembers; track member) {
<tr
[routerLink]="
'/view/usernav/users/' + member.MEMBERID
"
>
<td class="">{{ member.MEMBERNAME }}</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
}
<br />
</div>
<br />
</div>
</div>
</div>
</div>
}
</div>
+143 -144
View File
@@ -1,159 +1,158 @@
<app-sidebar>
<clr-tree>
<clr-tree-node *ngIf="treeNodeLibraries?.length! > 0" class="search-node">
<div class="tree-search-wrapper">
<input
clrInput
appStealFocus
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
aria-label="Clear libraries search"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let library of treeNodeLibraries">
<clr-tree-node
(click)="treeNodeClicked($event, library); lib = library.LIBRARYREF"
*ngIf="!library['hidden']"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<p
(click)="
lib = library.LIBRARYREF;
libraryOnClick(library.LIBRARYREF, library)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="rack-server" aria-hidden="true"></clr-icon>
{{ library.LIBRARYREF }}
</p>
<clr-tree-node *ngIf="library['tables']" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables')"
autocomplete="off"
/>
@if (treeNodeLibraries?.length! > 0) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
clrInput
appStealFocus
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search" aria-hidden="true"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
(click)="
searchTreeInput.value = '';
library['searchString'] = '';
treeOnFilter(library, 'tables')
"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
aria-label="Clear tables search"
aria-label="Clear libraries search"
></clr-icon>
</div>
</clr-tree-node>
<clr-tree-node
*ngFor="let libTable of library['tables']; let index = index"
>
<clr-tooltip
*ngVar="
index + 1 >
licenceState.value.tables_in_library_limit as tableLocked
"
>
<button
clrTooltipTrigger
(click)="!tableLocked ? onTableClick(libTable, library) : ''"
class="clr-treenode-link"
[class.dc-locked-control]="tableLocked"
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
>
<ng-container [ngSwitch]="libTable.includes('-FC')">
<clr-icon
*ngSwitchCase="true"
shape="bolt"
aria-hidden="true"
></clr-icon>
<clr-icon
*ngSwitchCase="false"
shape="table"
aria-hidden="true"
></clr-icon>
</ng-container>
{{ libTable.replace('-FC', '') }}
</button>
<ng-container *ngIf="tableLocked">
<clr-tooltip-content
clrPosition="bottom-right"
clrSize="lg"
*clrIfOpen
>
<span>
To unlock all tables, contact support&#64;datacontroller.io
</span>
</clr-tooltip-content>
</ng-container>
</clr-tooltip>
</clr-tree-node>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (library of treeNodeLibraries; track library) {
@if (!library['hidden']) {
<clr-tree-node
(click)="treeNodeClicked($event, library); lib = library.LIBRARYREF"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<p
(click)="
lib = library.LIBRARYREF;
libraryOnClick(library.LIBRARYREF, library)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="rack-server" aria-hidden="true"></clr-icon>
{{ library.LIBRARYREF }}
</p>
@if (library['tables']) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables')"
autocomplete="off"
/>
@if (searchTreeInput.value.length < 1) {
<clr-icon shape="search" aria-hidden="true"></clr-icon>
}
@if (searchTreeInput.value.length > 0) {
<clr-icon
(click)="
searchTreeInput.value = '';
library['searchString'] = '';
treeOnFilter(library, 'tables')
"
shape="times"
aria-label="Clear tables search"
></clr-icon>
}
</div>
</clr-tree-node>
}
@for (
libTable of library['tables'];
track libTable;
let index = $index
) {
<clr-tree-node>
<clr-tooltip
*ngVar="
index + 1 >
licenceState.value.tables_in_library_limit as tableLocked
"
>
<button
clrTooltipTrigger
(click)="!tableLocked ? onTableClick(libTable, library) : ''"
class="clr-treenode-link"
[class.dc-locked-control]="tableLocked"
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
>
@switch (libTable.includes('-FC')) {
@case (true) {
<clr-icon shape="bolt" aria-hidden="true"></clr-icon>
}
@case (false) {
<clr-icon shape="table" aria-hidden="true"></clr-icon>
}
}
{{ libTable.replace('-FC', '') }}
</button>
@if (tableLocked) {
<clr-tooltip-content
clrPosition="bottom-right"
clrSize="lg"
*clrIfOpen
>
<span>
To unlock all tables, contact
support&#64;datacontroller.io
</span>
</clr-tooltip-content>
}
</clr-tooltip>
</clr-tree-node>
}
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
<main class="content-area">
<div class="card-block">
<div *ngIf="loading" class="spinner-wrapper-fullpage">
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
@if (loading) {
<div class="spinner-wrapper-fullpage">
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
</div>
</div>
}
<div *ngIf="!loading" class="no-table-selected">
<img
src="images/select-table.png"
class="select-table-icon"
alt="select table icon"
/>
<p
*ngIf="treeNodeLibraries?.length! > 0"
class="text-center color-gray mt-10"
cds-text="section"
>
Please select a table
</p>
<p
*ngIf="treeNodeLibraries?.length! < 1"
class="text-center color-gray mt-10"
cds-text="section"
>
No Editable Tables Configured
</p>
</div>
@if (!loading) {
<div class="no-table-selected">
<img
src="images/select-table.png"
class="select-table-icon"
alt="select table icon"
/>
@if (treeNodeLibraries?.length! > 0) {
<p class="text-center color-gray mt-10" cds-text="section">
Please select a table
</p>
}
@if (treeNodeLibraries?.length! < 1) {
<p class="text-center color-gray mt-10" cds-text="section">
No Editable Tables Configured
</p>
}
</div>
}
</div>
</main>
+251 -242
View File
@@ -1,168 +1,176 @@
<app-sidebar (scrolledToBottom)="loadMoreLibraries()">
<clr-tree>
<clr-tree-node *ngIf="libraryList" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let library of libraryList">
<clr-tree-node
(click)="treeNodeClicked($event, library, libraryList)"
*ngIf="!library['hidden'] && library['inForeground']"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<p
(click)="lib = library.LIBRARYID; libraryOnClick(lib || '', library)"
class="m-0 cursor-pointer"
>
<clr-icon shape="rack-server"></clr-icon>
{{ library.LIBRARYNAME }}
</p>
<clr-tree-node *ngIf="library['tables']" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables.TABLENAME')"
autocomplete="off"
/>
@if (libraryList) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
(click)="
searchTreeInput.value = '';
library['searchString'] = '';
treeOnFilter(library, 'tables.TABLENAME')
"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
}
</div>
</clr-tree-node>
}
@for (library of libraryList; track library) {
@if (!library['hidden'] && library['inForeground']) {
<clr-tree-node
(click)="treeNodeClicked($event, libTable, library['tables'])"
*ngFor="let libTable of library['tables']"
[(clrExpanded)]="libTable['expanded']"
[clrLoading]="libTable['loadingColumns'] && !libTable.columns"
[class.clr-expanded]="libTable['expanded']"
(click)="treeNodeClicked($event, library, libraryList)"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<p
(click)="tableOnClick(libTable.TABLEURI, libTable, library)"
[id]="libTable.TABLEURI"
(click)="
lib = library.LIBRARYID; libraryOnClick(lib || '', library)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="table"></clr-icon>
{{ libTable.TABLENAME }}
<clr-icon shape="rack-server"></clr-icon>
{{ library.LIBRARYNAME }}
</p>
<clr-tree-node *ngIf="libTable['columns']" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Columns"
name="input"
[(ngModel)]="libTable['searchString']"
(keyup)="treeOnFilter(libTable, 'columns.COLNAME')"
autocomplete="off"
/>
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
(click)="
searchTreeInput.value = '';
libTable['searchString'] = '';
treeOnFilter(libTable, 'columns.COLNAME')
"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<clr-tree-node *ngFor="let libColumn of libTable['columns']">
<button
(click)="columnOnClick(libColumn, library, libTable)"
class="clr-treenode-link"
[class.column-active]="libColumnActive(libColumn.COLURI)"
@if (library['tables']) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables.TABLENAME')"
autocomplete="off"
/>
@if (searchTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchTreeInput.value.length > 0) {
<clr-icon
(click)="
searchTreeInput.value = '';
library['searchString'] = '';
treeOnFilter(library, 'tables.TABLENAME')
"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
}
@for (libTable of library['tables']; track libTable) {
<clr-tree-node
(click)="treeNodeClicked($event, libTable, library['tables'])"
[(clrExpanded)]="libTable['expanded']"
[clrLoading]="libTable['loadingColumns'] && !libTable.columns"
[class.clr-expanded]="libTable['expanded']"
>
<clr-icon shape="objects"></clr-icon>
{{ libColumn.COLNAME }}
</button>
</clr-tree-node>
<p
(click)="tableOnClick(libTable.TABLEURI, libTable, library)"
[id]="libTable.TABLEURI"
class="m-0 cursor-pointer"
>
<clr-icon shape="table"></clr-icon>
{{ libTable.TABLENAME }}
</p>
@if (libTable['columns']) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Columns"
name="input"
[(ngModel)]="libTable['searchString']"
(keyup)="treeOnFilter(libTable, 'columns.COLNAME')"
autocomplete="off"
/>
@if (searchTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchTreeInput.value.length > 0) {
<clr-icon
(click)="
searchTreeInput.value = '';
libTable['searchString'] = '';
treeOnFilter(libTable, 'columns.COLNAME')
"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
}
@for (libColumn of libTable['columns']; track libColumn) {
<clr-tree-node>
<button
(click)="columnOnClick(libColumn, library, libTable)"
class="clr-treenode-link"
[class.column-active]="libColumnActive(libColumn.COLURI)"
>
<clr-icon shape="objects"></clr-icon>
{{ libColumn.COLNAME }}
</button>
</clr-tree-node>
}
</clr-tree-node>
}
</clr-tree-node>
</clr-tree-node>
</ng-container>
}
}
</clr-tree>
<div *ngIf="librariesPaging" class="w-100 text-center">
<span class="spinner spinner-sm"> Loading... </span>
</div>
@if (librariesPaging) {
<div class="w-100 text-center">
<span class="spinner spinner-sm"> Loading... </span>
</div>
}
</app-sidebar>
<div class="content-area">
<div class="card">
<div *ngIf="!column && !table" class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a column or table
</p>
</div>
<ng-container *ngIf="column || table">
<div
*ngIf="!graphContainer"
class="card-header d-flex flex-column justify-content-center"
>
<h3
*ngIf="!currentLineagePathColumn && !currentLineagePathLibTable"
class="text-center pb-10"
>
{{
currentLineagePathColumn
? currentLineagePathLibTable + '.' + currentLineagePathColumn
: currentLineagePathLibTable
}}
</h3>
@if (!column && !table) {
<div class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a column or table
</p>
</div>
}
@if (column || table) {
@if (!graphContainer) {
<div class="card-header d-flex flex-column justify-content-center">
@if (!currentLineagePathColumn && !currentLineagePathLibTable) {
<h3 class="text-center pb-10">
{{
currentLineagePathColumn
? currentLineagePathLibTable + '.' + currentLineagePathColumn
: currentLineagePathLibTable
}}
</h3>
}
</div>
}
<div class="card-block">
<section
class="form-block sw position-relative d-flex align-items-center"
@@ -170,114 +178,115 @@
<div
class="linage-title-wrapper d-flex align-items-center font-weight-bold position-absolute"
>
<span *ngIf="lineageTableName.length > 0">
{{ lineageTableName.split('.')[0] }}.<a
[routerLink]="'/view/data/' + lineageTableName"
>{{ lineageTableName.split('.')[1] }}</a
>{{ lineageColumnName.length > 0 ? '.' + lineageColumnName : '' }}
</span>
@if (lineageTableName.length > 0) {
<span>
{{ lineageTableName.split('.')[0] }}.<a
[routerLink]="'/view/data/' + lineageTableName"
>{{ lineageTableName.split('.')[1] }}</a
>{{
lineageColumnName.length > 0 ? '.' + lineageColumnName : ''
}}
</span>
}
</div>
<div
*ngIf="graphContainer"
class="clr-col-md-12 text-center d-flex justify-content-end"
>
<button
(click)="limitDotDepth = true"
type="button"
class="btn btn-outline mr-5"
>
Limit depth
</button>
<!-- <button class="btn btn-outline" (click)='showSvg()'> Open in New Tab </button> -->
<div class="btn-group direction d-block">
<div
class="radio btn"
(click)="
forwardLineage = false;
router.url.includes('column')
? onGenerateClick()
: onGenerateGraphTableClick()
"
>
<input
type="radio"
name="btn-group-demo-radios"
[checked]="!forwardLineage"
/>
<label>Backward</label>
</div>
<div
class="radio btn"
(click)="
forwardLineage = true;
router.url.includes('column')
? onGenerateClick()
: onGenerateGraphTableClick()
"
>
<input
type="radio"
name="btn-group-demo-radios"
[checked]="forwardLineage"
/>
<label>Forward</label>
</div>
</div>
<clr-dropdown class="mr-10">
@if (graphContainer) {
<div class="clr-col-md-12 text-center d-flex justify-content-end">
<button
class="btn btn-info-outline"
clrDropdownTrigger
[disabled]="!column && !table"
(click)="limitDotDepth = true"
type="button"
class="btn btn-outline mr-5"
>
Download
<clr-icon shape="caret down"></clr-icon>
Limit depth
</button>
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div (click)="downloadSVG()" clrDropdownItem>SVG</div>
<div (click)="downloadPNG()" clrDropdownItem>PNG</div>
<div (click)="downloadDot()" clrDropdownItem>Dot</div>
<div *ngIf="flatdata" (click)="downloadCSV()" clrDropdownItem>
CSV
<!-- <button class="btn btn-outline" (click)='showSvg()'> Open in New Tab </button> -->
<div class="btn-group direction d-block">
<div
class="radio btn"
(click)="
forwardLineage = false;
router.url.includes('column')
? onGenerateClick()
: onGenerateGraphTableClick()
"
>
<input
type="radio"
name="btn-group-demo-radios"
[checked]="!forwardLineage"
/>
<label>Backward</label>
</div>
<div
class="radio btn"
(click)="
forwardLineage = true;
router.url.includes('column')
? onGenerateClick()
: onGenerateGraphTableClick()
"
>
<input
type="radio"
name="btn-group-demo-radios"
[checked]="forwardLineage"
/>
<label>Forward</label>
</div>
</clr-dropdown-menu>
</clr-dropdown>
<clr-checkbox-wrapper
*ngIf="column"
class="d-flex align-items-center"
>
<input
type="checkbox"
(change)="generateGraph()"
clrCheckbox
name="refreshCache"
[(ngModel)]="refreshCache"
/>
<label>Refresh Cache</label>
</clr-checkbox-wrapper>
</div>
</section>
<div *ngIf="graphContainer" [class.mt-2]="tableFlag">
<div class="text-center">
<span *ngIf="graphLoading" class="spinner"> Loading... </span>
<div *ngIf="!graphLoading" class="position-relative">
<div class="graph-render-spinner">
<span *ngIf="graphRendering" class="spinner spinner-sm"></span>
</div>
<div id="graph"></div>
<clr-dropdown class="mr-10">
<button
class="btn btn-info-outline"
clrDropdownTrigger
[disabled]="!column && !table"
>
Download
<clr-icon shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu clrPosition="bottom-left" *clrIfOpen>
<div (click)="downloadSVG()" clrDropdownItem>SVG</div>
<div (click)="downloadPNG()" clrDropdownItem>PNG</div>
<div (click)="downloadDot()" clrDropdownItem>Dot</div>
@if (flatdata) {
<div (click)="downloadCSV()" clrDropdownItem>CSV</div>
}
</clr-dropdown-menu>
</clr-dropdown>
@if (column) {
<clr-checkbox-wrapper class="d-flex align-items-center">
<input
type="checkbox"
(change)="generateGraph()"
clrCheckbox
name="refreshCache"
[(ngModel)]="refreshCache"
/>
<label>Refresh Cache</label>
</clr-checkbox-wrapper>
}
</div>
}
</section>
@if (graphContainer) {
<div [class.mt-2]="tableFlag">
<div class="text-center">
@if (graphLoading) {
<span class="spinner"> Loading... </span>
}
@if (!graphLoading) {
<div class="position-relative">
<div class="graph-render-spinner">
@if (graphRendering) {
<span class="spinner spinner-sm"></span>
}
</div>
<div id="graph"></div>
</div>
}
</div>
</div>
</div>
}
</div>
</ng-container>
}
</div>
</div>
+218 -188
View File
@@ -8,58 +8,60 @@
[(ngModel)]="repository"
(change)="updateSelectedRepository()"
>
<option
*ngFor="let repository of repositories"
value="{{ repository }}"
>
{{ repository }}
</option>
@for (repository of repositories; track repository) {
<option value="{{ repository }}">
{{ repository }}
</option>
}
</select>
</clr-select-container>
</div>
<clr-tree>
<clr-tree-node *ngIf="metaDataList" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="search SAS Types"
name="input"
[(ngModel)]="metaDataSearch"
(keyup)="metaListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="metaDataSearch = ''; metaListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let metaData of metaDataList">
<clr-tree-node
(click)="treeNodeClicked($event, metaData, metaDataList)"
*ngIf="!metaData['hidden']"
[(clrExpanded)]="metaData['expanded']"
[clrLoading]="metaData['loadingTables'] && !metaData.tables"
>
<p
(click)="
metaDataId = metaData.ID; metaDataOnClick(metaDataId, metaData)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="block"></clr-icon>
{{ metaData.ID }}
</p>
@if (metaDataList) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="search SAS Types"
name="input"
[(ngModel)]="metaDataSearch"
(keyup)="metaListOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
(click)="metaDataSearch = ''; metaListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (metaData of metaDataList; track metaData) {
@if (!metaData['hidden']) {
<clr-tree-node
(click)="treeNodeClicked($event, metaData, metaDataList)"
[(clrExpanded)]="metaData['expanded']"
[clrLoading]="metaData['loadingTables'] && !metaData.tables"
>
<p
(click)="
metaDataId = metaData.ID; metaDataOnClick(metaDataId, metaData)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="block"></clr-icon>
{{ metaData.ID }}
</p>
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
@@ -67,54 +69,121 @@
<div class="card background-transparent-i">
<h3 class="color-gray">{{ assoTypeSelected }}</h3>
<div *ngIf="!loading && !metaObjectList" class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a type
</p>
</div>
<div class="loadingSpinner" *ngIf="loading">
<span class="spinner"> Loading... </span>
<div *ngIf="loading">
<h4 *ngIf="metatypesLoading">Loading metadata types</h4>
<h4 *ngIf="!metatypesLoading">Loading metadata objects</h4>
@if (!loading && !metaObjectList) {
<div class="no-table-selected">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a type
</p>
</div>
</div>
<div *ngIf="showData" class="clr-row clr-flex-grow-1">
<div class="clr-col-6">
<div *ngIf="metaObjectList && !objectRoute" class="search-input">
<input
clrInput
#searchObjTreeInput
placeholder="search"
name="input"
[(ngModel)]="metaObjectSearch"
(keyup)="metaObjectOnFilter()"
autocomplete="off"
/>
</div>
<br />
<div *ngIf="!objectView" class="objects-col">
<clr-accordion>
<ng-container *ngFor="let metaObject of metaObjectShowList">
<clr-accordion-panel
(clrAccordionPanelOpenChange)="
$event ? panelChange($event, metaObject) : ''
"
*ngIf="!metaObject['hidden']"
>
<clr-accordion-title
><clr-icon shape="rack-server"></clr-icon>
{{ metaObject.NAME }}
<p class="float-right">{{ metaObject.ID }}</p>
</clr-accordion-title>
<clr-accordion-content *clrIfExpanded>
<clr-tree *ngIf="showAcc" [clrLazy]="true">
}
@if (loading) {
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
@if (loading) {
<div>
@if (metatypesLoading) {
<h4>Loading metadata types</h4>
}
@if (!metatypesLoading) {
<h4>Loading metadata objects</h4>
}
</div>
}
</div>
}
@if (showData) {
<div class="clr-row clr-flex-grow-1">
<div class="clr-col-6">
@if (metaObjectList && !objectRoute) {
<div class="search-input">
<input
clrInput
#searchObjTreeInput
placeholder="search"
name="input"
[(ngModel)]="metaObjectSearch"
(keyup)="metaObjectOnFilter()"
autocomplete="off"
/>
</div>
}
<br />
@if (!objectView) {
<div class="objects-col">
<clr-accordion>
@for (metaObject of metaObjectShowList; track metaObject) {
@if (!metaObject['hidden']) {
<clr-accordion-panel
(clrAccordionPanelOpenChange)="
$event ? panelChange($event, metaObject) : ''
"
>
<clr-accordion-title
><clr-icon shape="rack-server"></clr-icon>
{{ metaObject.NAME }}
<p class="float-right">{{ metaObject.ID }}</p>
</clr-accordion-title>
<clr-accordion-content *clrIfExpanded>
@if (showAcc) {
<clr-tree [clrLazy]="true">
<clr-tree-node
*clrRecursiveFor="
let entry of root$ | async;
getChildren: getChildren
"
[clrExpandable]="true"
>
<div
[class.object-header]="!entry.count"
class="full-width"
>
<div>
@if (!entry.count) {
<clr-icon shape="rack-server"></clr-icon>
}
@if (entry.count) {
<clr-icon shape="block"></clr-icon>
}
{{ entry.display }}
</div>
@if (!entry.count) {
<p class="float-right object-uri">
{{ entry.URI }}
</p>
}
</div>
</clr-tree-node>
</clr-tree>
}
</clr-accordion-content>
</clr-accordion-panel>
}
}
</clr-accordion>
</div>
}
@if (objectView) {
<div class="objects-col">
@for (metaObject of metaObjectShowList; track metaObject) {
<div class="cols-head">
<clr-icon shape="rack-server"></clr-icon>
<div class="object-text">
<p class="m-0 word-break mr-20">
{{ metaObject.NAME }}
</p>
<p class="float-right ml-3">
{{ metaObject.ID }}
</p>
</div>
</div>
@if (showAcc) {
<clr-tree [clrLazy]="true">
<clr-tree-node
*clrRecursiveFor="
let entry of root$ | async;
@@ -127,109 +196,70 @@
class="full-width"
>
<div>
<clr-icon
*ngIf="!entry.count"
shape="rack-server"
></clr-icon>
<clr-icon
*ngIf="entry.count"
shape="block"
></clr-icon>
@if (!entry.count) {
<clr-icon shape="rack-server"></clr-icon>
}
@if (entry.count) {
<clr-icon shape="block"></clr-icon>
}
{{ entry.display }}
</div>
<p class="float-right object-uri" *ngIf="!entry.count">
{{ entry.URI }}
</p>
@if (!entry.count) {
<p class="float-right object-uri">
{{ entry.URI }}
</p>
}
</div>
</clr-tree-node>
</clr-tree>
</clr-accordion-content>
</clr-accordion-panel>
</ng-container>
</clr-accordion>
</div>
<div *ngIf="objectView" class="objects-col">
<ng-container *ngFor="let metaObject of metaObjectShowList">
<div class="cols-head">
<clr-icon shape="rack-server"></clr-icon>
<div class="object-text">
<p class="m-0 word-break mr-20">
{{ metaObject.NAME }}
</p>
<p class="float-right ml-3">
{{ metaObject.ID }}
</p>
</div>
}
}
</div>
<clr-tree *ngIf="showAcc" [clrLazy]="true">
<clr-tree-node
*clrRecursiveFor="
let entry of root$ | async;
getChildren: getChildren
"
[clrExpandable]="true"
}
</div>
<div class="clr-col-6 text-center">
<h3>{{ assoObjectSelected }}</h3>
@if (showTable) {
<clr-datagrid class="datagrid-custom-footer">
<clr-dg-column>
TYPE
<clr-dg-string-filter
[clrDgStringFilter]="typeFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>
NAME
<clr-dg-string-filter
[clrDgStringFilter]="nameFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>
VALUE
<clr-dg-string-filter
[clrDgStringFilter]="valueFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-row
*clrDgItems="let metaObjectAttribute of metaObjectAttributes"
>
<div [class.object-header]="!entry.count" class="full-width">
<div>
<clr-icon
*ngIf="!entry.count"
shape="rack-server"
></clr-icon>
<clr-icon *ngIf="entry.count" shape="block"></clr-icon>
{{ entry.display }}
</div>
<p class="float-right object-uri" *ngIf="!entry.count">
{{ entry.URI }}
</p>
</div>
</clr-tree-node>
</clr-tree>
</ng-container>
<clr-dg-cell>{{ metaObjectAttribute.TYPE }}</clr-dg-cell>
<clr-dg-cell>{{ metaObjectAttribute.NAME }}</clr-dg-cell>
<clr-dg-cell>{{ metaObjectAttribute.VALUE }}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10, 20, 50, 100]"
>Attributes per page</clr-dg-page-size
>
{{ pagination.firstItem + 1 }} -
{{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} Attributes
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
}
</div>
</div>
<div class="clr-col-6 text-center">
<h3>{{ assoObjectSelected }}</h3>
<clr-datagrid class="datagrid-custom-footer" *ngIf="showTable">
<clr-dg-column>
TYPE
<clr-dg-string-filter
[clrDgStringFilter]="typeFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>
NAME
<clr-dg-string-filter
[clrDgStringFilter]="nameFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>
VALUE
<clr-dg-string-filter
[clrDgStringFilter]="valueFilter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-row
*clrDgItems="let metaObjectAttribute of metaObjectAttributes"
>
<clr-dg-cell>{{ metaObjectAttribute.TYPE }}</clr-dg-cell>
<clr-dg-cell>{{ metaObjectAttribute.NAME }}</clr-dg-cell>
<clr-dg-cell>{{ metaObjectAttribute.VALUE }}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10, 20, 50, 100]"
>Attributes per page</clr-dg-page-size
>
{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} Attributes
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</div>
}
</div>
</div>
@@ -1,117 +1,118 @@
<app-sidebar>
<div *ngIf="datasetsLoading" class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
<div *ngIf="!parsedDatasets.length" class="text-center mb-10">
<button
(click)="fileUploadInput.click()"
id="browse-file"
class="btn btn-primary btn-sm"
[disabled]="selectedFile !== null || submittingCsv"
>
Browse file
</button>
<input
hidden
#fileUploadInput
id="file-upload"
type="file"
(change)="onFileChange($event)"
multiple
/>
</div>
<ng-container *ngIf="parsedDatasets.length && !submittedCsvDatasets.length">
<div *ngIf="!excelsSubmitted" class="text-center mb-10">
<button (click)="onDiscard()" class="btn btn-danger btn-sm mr-10">
Discard
</button>
<button
(click)="onSubmitAll()"
id="submit-all"
class="btn btn-primary btn-sm"
>
Submit All
</button>
@if (datasetsLoading) {
<div class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
}
@if (!parsedDatasets.length) {
<div class="text-center mb-10">
<button
(click)="fileUploadInput.click()"
id="browse-file"
class="btn btn-primary btn-sm"
[disabled]="selectedFile !== null || submittingCsv"
>
Browse file
</button>
<input
hidden
#fileUploadInput
id="file-upload"
type="file"
(change)="onFileChange($event)"
multiple
/>
</div>
}
@if (parsedDatasets.length && !submittedCsvDatasets.length) {
@if (!excelsSubmitted) {
<div class="text-center mb-10">
<button (click)="onDiscard()" class="btn btn-danger btn-sm mr-10">
Discard
</button>
<button
(click)="onSubmitAll()"
id="submit-all"
class="btn btn-primary btn-sm"
>
Submit All
</button>
</div>
}
<p cds-text="caption" class="ml-10 mb-10">Found tables:</p>
<clr-tree>
<clr-tree-node *ngFor="let dataset of parsedDatasets">
<button
(click)="onParsedDatasetClick(dataset)"
class="clr-treenode-link whitespace-nowrap d-flex clr-align-items-center"
[class.active]="dataset.active"
>
<ng-container *ngIf="dataset.submitResult">
<cds-icon
*ngIf="dataset.submitResult.error"
status="danger"
shape="exclamation-circle"
></cds-icon>
<cds-icon
*ngIf="dataset.submitResult.success"
status="success"
shape="check-circle"
></cds-icon>
</ng-container>
<ng-container *ngIf="!dataset.submitResult">
<ng-container *ngIf="dataset.datasource">
<cds-icon
*ngIf="!(dataset.datasource.length && dataset.parseResult)"
status="danger"
shape="exclamation-circle"
></cds-icon>
<cds-icon
*ngIf="dataset.datasource.length && dataset.parseResult"
shape="table"
></cds-icon>
</ng-container>
<ng-container *ngIf="!dataset.datasource">
<cds-icon *ngIf="!dataset.parsingTable" shape="table"></cds-icon>
<clr-spinner *ngIf="dataset.parsingTable" clrSmall></clr-spinner>
</ng-container>
</ng-container>
<span class="ml-5"> {{ dataset.libds }} </span>
</button>
</clr-tree-node>
@for (dataset of parsedDatasets; track dataset) {
<clr-tree-node>
<button
(click)="onParsedDatasetClick(dataset)"
class="clr-treenode-link whitespace-nowrap d-flex clr-align-items-center"
[class.active]="dataset.active"
>
@if (dataset.submitResult) {
@if (dataset.submitResult.error) {
<cds-icon status="danger" shape="exclamation-circle"></cds-icon>
}
@if (dataset.submitResult.success) {
<cds-icon status="success" shape="check-circle"></cds-icon>
}
}
@if (!dataset.submitResult) {
@if (dataset.datasource) {
@if (!(dataset.datasource.length && dataset.parseResult)) {
<cds-icon
status="danger"
shape="exclamation-circle"
></cds-icon>
}
@if (dataset.datasource.length && dataset.parseResult) {
<cds-icon shape="table"></cds-icon>
}
}
@if (!dataset.datasource) {
@if (!dataset.parsingTable) {
<cds-icon shape="table"></cds-icon>
}
@if (dataset.parsingTable) {
<clr-spinner clrSmall></clr-spinner>
}
}
}
<span class="ml-5"> {{ dataset.libds }} </span>
</button>
</clr-tree-node>
}
</clr-tree>
</ng-container>
}
<ng-container *ngIf="submittedCsvDatasets.length">
@if (submittedCsvDatasets.length) {
<p cds-text="caption" class="ml-10 mb-10 mt-10">Submitted tables:</p>
<clr-tree>
<clr-tree-node *ngFor="let dataset of submittedCsvDatasets">
<button
(click)="onSubmittedCsvDatasetClick(dataset)"
class="clr-treenode-link whitespace-nowrap"
[class.active]="dataset.active"
>
<cds-icon
*ngIf="dataset.error"
status="danger"
shape="exclamation-circle"
></cds-icon>
<cds-icon
*ngIf="dataset.success"
status="success"
shape="check-circle"
></cds-icon>
<cds-icon shape="table"></cds-icon>
{{ dataset.libds }}
</button>
</clr-tree-node>
@for (dataset of submittedCsvDatasets; track dataset) {
<clr-tree-node>
<button
(click)="onSubmittedCsvDatasetClick(dataset)"
class="clr-treenode-link whitespace-nowrap"
[class.active]="dataset.active"
>
@if (dataset.error) {
<cds-icon status="danger" shape="exclamation-circle"></cds-icon>
}
@if (dataset.success) {
<cds-icon status="success" shape="check-circle"></cds-icon>
}
<cds-icon shape="table"></cds-icon>
{{ dataset.libds }}
</button>
</clr-tree-node>
}
</clr-tree>
</ng-container>
}
<!-- <div *ngIf="librariesPaging" class="w-100 text-center">
<span class="spinner spinner-sm"> Loading... </span>
</div> -->
<span class="spinner spinner-sm"> Loading... </span>
</div> -->
</app-sidebar>
<div #contentArea class="content-area">
@@ -122,23 +123,22 @@
<p cds-text="section">Multi Dataset Load</p>
</div>
<div
*ngIf="selectedFile === null && !submittingCsv"
class="no-table-selected pointer-events-none"
>
<clr-icon
aria-hidden="true"
shape="upload-cloud"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please upload a file
</p>
</div>
@if (selectedFile === null && !submittingCsv) {
<div class="no-table-selected pointer-events-none">
<clr-icon
aria-hidden="true"
shape="upload-cloud"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please upload a file
</p>
</div>
}
<ng-container *ngIf="selectedFile !== null || submittingCsv">
<ng-container *ngIf="!parsedDatasets.length && selectedFile !== null">
@if (selectedFile !== null || submittingCsv) {
@if (!parsedDatasets.length && selectedFile !== null) {
<div class="d-flex clr-justify-content-center mt-15">
<div class="dataset-input-wrapper">
<p cds-text="secondary regular" class="mb-5">
@@ -160,12 +160,10 @@
<p cds-text="secondary regular" class="mb-15">
Paste or type the list of datasets to upload:
</p>
<clr-control-helper class="mb-5"
>Each row is one dataset. We will automatically detect tables by
the sheetname and populate if any.</clr-control-helper
>
<hot-table
#hotInstanceUserDataset
id="hotTableUserDataset"
@@ -173,7 +171,6 @@
[settings]="hotUserDatasetsSettings"
>
</hot-table>
<div class="dataset-selection-actions text-right mt-10">
<button
(click)="onStartParsingFile()"
@@ -187,109 +184,92 @@
</div>
</div>
</div>
</ng-container>
<ng-container
*ngIf="parsedDatasets.length && !submittedCsvDatasets.length"
>
<div
*ngIf="!activeParsedDataset"
class="no-table-selected pointer-events-none"
>
<ng-container *ngIf="fileLoadingState !== FileLoadingState.parsed">
<clr-icon
aria-hidden="true"
shape="process-on-vm"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
{{ fileLoadingState }}...
</p>
</ng-container>
<ng-container *ngIf="fileLoadingState === FileLoadingState.parsed">
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a dataset on the left to review the data
</p>
</ng-container>
</div>
<ng-container *ngIf="activeParsedDataset">
<div
*ngIf="activeParsedDataset.submitResult"
class="d-flex clr-justify-content-between p-10 mt-15 submission-results"
>
<div>
<p cds-text="secondary regular" class="mb-10">
Submit Status:
<span
*ngIf="activeParsedDataset.submitResult?.success"
class="color-green"
><strong>SUCCESS</strong></span
>
<span
*ngIf="activeParsedDataset.submitResult?.error"
class="color-red"
><strong>ERROR</strong></span
>
}
@if (parsedDatasets.length && !submittedCsvDatasets.length) {
@if (!activeParsedDataset) {
<div class="no-table-selected pointer-events-none">
@if (fileLoadingState !== FileLoadingState.parsed) {
<clr-icon
aria-hidden="true"
shape="process-on-vm"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
{{ fileLoadingState }}...
</p>
<p
*ngIf="activeParsedDataset.submitResult?.error"
cds-text="secondary regular"
>
Error details:
}
@if (fileLoadingState === FileLoadingState.parsed) {
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a dataset on the left to review the data
</p>
</div>
<div>
<button
*ngIf="
}
</div>
}
@if (activeParsedDataset) {
@if (activeParsedDataset.submitResult) {
<div
class="d-flex clr-justify-content-between p-10 mt-15 submission-results"
>
<div>
<p cds-text="secondary regular" class="mb-10">
Submit Status:
@if (activeParsedDataset.submitResult?.success) {
<span class="color-green"><strong>SUCCESS</strong></span>
}
@if (activeParsedDataset.submitResult?.error) {
<span class="color-red"><strong>ERROR</strong></span>
}
</p>
@if (activeParsedDataset.submitResult?.error) {
<p cds-text="secondary regular">Error details:</p>
}
</div>
<div>
@if (
!submittingCsv && activeParsedDataset.submitResult?.error
"
(click)="reSubmitTable(activeParsedDataset)"
class="btn btn-primary mt-10"
[clrLoading]="submitLoading"
>
Resubmit
</button>
<button
(click)="
downloadFile(
activeParsedDataset.submitResult.log ||
activeParsedDataset.submitResult.success ||
activeParsedDataset.submitResult.error
)
"
class="btn btn-primary-outline mt-10"
>
Download log
</button>
) {
<button
(click)="reSubmitTable(activeParsedDataset)"
class="btn btn-primary mt-10"
[clrLoading]="submitLoading"
>
Resubmit
</button>
}
<button
(click)="
downloadFile(
activeParsedDataset.submitResult.log ||
activeParsedDataset.submitResult.success ||
activeParsedDataset.submitResult.error
)
"
class="btn btn-primary-outline mt-10"
>
Download log
</button>
</div>
</div>
</div>
<div
*ngIf="activeParsedDataset.submitResult?.error"
class="error-field mt-15"
>
<div class="log-wrapper">
{{ activeParsedDataset.submitResult?.error | json }}
}
@if (activeParsedDataset.submitResult?.error) {
<div class="error-field mt-15">
<div class="log-wrapper">
{{ activeParsedDataset.submitResult?.error | json }}
</div>
</div>
</div>
}
<div class="d-flex clr-justify-content-between p-10 mt-15">
<div>
<p cds-text="secondary regular" class="mb-10">
Found in range:
<ng-container *ngIf="activeParsedDataset.parseResult">
@if (activeParsedDataset.parseResult) {
<strong
>"{{
activeParsedDataset.parseResult.rangeSheetRes?.sheetName
@@ -298,21 +278,18 @@
?.rangeAddress
}}</strong
>
</ng-container>
<ng-container *ngIf="!activeParsedDataset.parseResult">
<strong *ngIf="!activeParsedDataset.parsingTable"
>No data found</strong
>
<span
*ngIf="activeParsedDataset.parsingTable"
class="d-flex clr-align-items-center"
>
<strong>Searching for the data...</strong>
<clr-spinner class="ml-5" clrSmall></clr-spinner>
</span>
</ng-container>
}
@if (!activeParsedDataset.parseResult) {
@if (!activeParsedDataset.parsingTable) {
<strong>No data found</strong>
}
@if (activeParsedDataset.parsingTable) {
<span class="d-flex clr-align-items-center">
<strong>Searching for the data...</strong>
<clr-spinner class="ml-5" clrSmall></clr-spinner>
</span>
}
}
</p>
<p cds-text="secondary regular">
Dataset:
@@ -333,7 +310,6 @@
</strong>
</p>
</div>
<div>
<clr-toggle-wrapper>
<input
@@ -354,11 +330,11 @@
</clr-toggle-wrapper>
</div>
</div>
<div *ngIf="isHotHidden" class="text-center w-100">
<clr-spinner class="spinner-md"></clr-spinner>
</div>
@if (isHotHidden) {
<div class="text-center w-100">
<clr-spinner class="spinner-md"></clr-spinner>
</div>
}
<hot-table
#hotInstanceMain
id="hotTable"
@@ -366,27 +342,24 @@
[settings]="hotMainTableSettings"
>
</hot-table>
</ng-container>
</ng-container>
<ng-container *ngIf="submittedCsvDatasets.length">
<div
*ngIf="!activeSubmittedCsvDataset"
class="no-table-selected pointer-events-none"
>
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a dataset on the left to review the submit results
</p>
</div>
</ng-container>
<ng-container *ngIf="activeSubmittedCsvDataset">
}
}
@if (submittedCsvDatasets.length) {
@if (!activeSubmittedCsvDataset) {
<div class="no-table-selected pointer-events-none">
<clr-icon
aria-hidden="true"
shape="warning-standard"
size="40"
class="is-info icon-dc-fill"
></clr-icon>
<p class="text-center color-gray mt-10" cds-text="section">
Please select a dataset on the left to review the submit results
</p>
</div>
}
}
@if (activeSubmittedCsvDataset) {
<div class="d-flex clr-justify-content-between p-10">
<div>
<p cds-text="secondary regular" class="mb-10">
@@ -409,23 +382,17 @@
</p>
<p cds-text="secondary regular" class="mb-10">
Status:
<span
*ngIf="activeSubmittedCsvDataset.success"
class="color-green"
><strong>SUCCESS</strong></span
>
<span *ngIf="activeSubmittedCsvDataset.error" class="color-red"
><strong>ERROR</strong></span
>
</p>
<p
*ngIf="activeSubmittedCsvDataset.error"
cds-text="secondary regular"
>
Error details:
@if (activeSubmittedCsvDataset.success) {
<span class="color-green"><strong>SUCCESS</strong></span>
}
@if (activeSubmittedCsvDataset.error) {
<span class="color-red"><strong>ERROR</strong></span>
}
</p>
@if (activeSubmittedCsvDataset.error) {
<p cds-text="secondary regular">Error details:</p>
}
</div>
<div>
<button
(click)="
@@ -440,28 +407,29 @@
</button>
</div>
</div>
<div *ngIf="activeSubmittedCsvDataset.error" class="error-field mt-15">
<div class="log-wrapper">
{{ activeSubmittedCsvDataset.error | json }}
@if (activeSubmittedCsvDataset.error) {
<div class="error-field mt-15">
<div class="log-wrapper">
{{ activeSubmittedCsvDataset.error | json }}
</div>
</div>
</div>
</ng-container>
</ng-container>
}
}
}
<!-- <div>
<p
<p
*ngIf="
licenceState.value.viewer_rows_allowed !== Infinity &&
hotTable.data &&
hotTable.data.length > licenceState.value.viewer_rows_allowed
"
class="mt-2-i w-100 text-center"
>
To display more than {{ licenceState.value.viewer_rows_allowed }} rows,
contact <contact-link />
</p>
</div> -->
class="mt-2-i w-100 text-center"
>
To display more than {{ licenceState.value.viewer_rows_allowed }} rows,
contact <contact-link />
</p>
</div> -->
</div>
</div>
@@ -471,16 +439,14 @@
{{ tablesToSubmit.length === 1 ? 'table' : 'tables' }} for approval
</h3>
<div class="modal-body">
<p
*ngIf="licenceState.value.submit_rows_limit !== Infinity"
cds-text="body"
class="licence-limit-notice mt-0 mb-15"
>
Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in each file will be
submitted. To remove the restriction, contact
support&#64;datacontroller.io.
</p>
@if (licenceState.value.submit_rows_limit !== Infinity) {
<p cds-text="body" class="licence-limit-notice mt-0 mb-15">
Due to current licence, only
{{ licenceState.value.submit_rows_limit }} rows in each file will be
submitted. To remove the restriction, contact
support&#64;datacontroller.io.
</p>
}
<div class="text-area-full-width">
<label for="formFields_8" class="mb-5 d-block">Message</label>
+308 -298
View File
@@ -30,20 +30,21 @@
(ngModelChange)="setGroupLogic(groupLogic)"
clrSelect
>
<option
*ngFor="let logic of logicOperators"
[selected]="logicOperators[0]"
>
{{ logic }}
</option>
@for (logic of logicOperators; track logic) {
<option [selected]="logicOperators[0]">
{{ logic }}
</option>
}
</select>
</clr-select-container>
</div>
<div class="clr-col-md-10 mb-30">
<pre class="line-numbers language-markup">
<div *ngIf="whereClauseLoading" class="progresStatic progress loop">
<progress></progress>
</div>
@if (whereClauseLoading) {
<div class="progresStatic progress loop">
<progress></progress>
</div>
}
<code class="language-sql">{{whereClause}}</code>
</pre>
@@ -55,245 +56,246 @@
class="clauses-container clr-col-md-12"
[class.clr-col-md-10]="clauses?.queryObj?.length > 1"
>
<div *ngIf="clauses?.queryObj?.length > 1"></div>
@if (clauses?.queryObj?.length > 1) {
<div></div>
}
<div
class="clause-row"
*ngFor="let clause of clauses.queryObj; let clauseIndex = index"
>
<div class="clr-row" [class.invalid-clause]="clause.invalidClause">
<div class="clause-logic clr-col-md-2">
<div class="select">
<clr-select-container>
<label>Logic</label>
<select
[(ngModel)]="clause.clauseLogic"
(ngModelChange)="setLogic()"
[disabled]="clause.elements.length < 2"
clrSelect
>
<option
*ngFor="let logic of logicOperators"
[selected]="logicOperators[0]"
>
{{ logic }}
</option>
</select>
</clr-select-container>
</div>
<br />
<button
*ngIf="innerWidth > 768"
class="btn btn-primary btn-block mt-10"
(click)="addGroupClause()"
>
<clr-icon shape="plus"></clr-icon>
<span>Group</span>
</button>
</div>
<div class="clause-query clr-col-md-10">
<clr-icon
*ngIf="clauses.queryObj.length > 1"
(click)="removeGroupClause(clauseIndex)"
shape="times"
size="36"
class="remove-group-clause-button"
></clr-icon>
<div
class="clr-row"
*ngFor="let query of clause.elements; let queryIndex = index"
[class.invalid-clause]="query.invalidClause"
>
<!-- VARIABLE -->
<div class="variable-col form-group clr-col-md-3">
<div class="datalist-wrapper">
<app-soft-select
label="Variable"
[id]="'select_vals_var_id' + queryIndex + '_' + clauseIndex"
[inputId]="'vals_var_id' + queryIndex + '_' + clauseIndex"
[emitOnlySelected]="true"
[(value)]="query.variable"
(onInputEvent)="
variableInputChange(
query.variable,
queryIndex,
clauseIndex,
$event
)
"
>
<option *ngFor="let column of cols">
{{ column.NAME }}
</option>
</app-soft-select>
</div>
</div>
<!-- OPERATOR -->
<div class="operator-col form-group clr-col-md-3">
@for (clause of clauses.queryObj; track clause; let clauseIndex = $index) {
<div class="clause-row">
<div class="clr-row" [class.invalid-clause]="clause.invalidClause">
<div class="clause-logic clr-col-md-2">
<div class="select">
<clr-select-container>
<label>Operator</label>
<label>Logic</label>
<select
[(ngModel)]="query.operator"
(ngModelChange)="
setVariableOperator(queryIndex, query.operator, clauseIndex)
"
[(ngModel)]="clause.clauseLogic"
(ngModelChange)="setLogic()"
[disabled]="clause.elements.length < 2"
clrSelect
>
<option *ngFor="let opr of query.operators">{{ opr }}</option>
@for (logic of logicOperators; track logic) {
<option [selected]="logicOperators[0]">
{{ logic }}
</option>
}
</select>
</clr-select-container>
</div>
<!-- VALUE -->
<div
*ngVar="
query.ddtype === 'DATE' ||
query.ddtype === 'DATETIME' ||
query.ddtype === 'TIME' as isDateTime
"
class="value-col form-group clr-col-md-3"
>
<div
*ngIf="query.operator === 'IN' || query.operator === 'NOT IN'"
class="checkbox-vals"
>
<button
(click)="
currentQueryIndex = queryIndex;
currentClauseIndex = clauseIndex
"
type="button"
class="btn btn-link"
>
Choose values
</button>
<ng-container
*ngTemplateOutlet="
checkboxValues;
context: {
query: query,
queryIndex: queryIndex,
clauseIndex: clauseIndex
}
"
>
</ng-container>
</div>
<div
*ngIf="
query.operator !== 'BETWEEN' &&
query.operator !== 'IN' &&
query.operator !== 'NOT IN' &&
query.operator !== 'LIKE' &&
query.operator !== 'CONTAINS' &&
query.operator !== 'BEGINS_WITH'
"
class="single-field-vals"
>
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPicker;
context: {
query: query,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
<div *ngIf="query.operator === 'BETWEEN'" class="range-vals">
<div class="from">
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPickerRange;
context: {
range: 'start',
query: query,
queryValueIndex: 0,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
<div class="to">
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPickerRange;
context: {
range: 'end',
query: query,
queryValueIndex: 1,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
</div>
<div
*ngIf="
query.operator === 'LIKE' ||
query.operator === 'BEGINS_WITH' ||
query.operator === 'CONTAINS'
"
class="contains-vals"
>
<label class="clr-control-label">Value</label>
<input
class="input-val"
type="text"
[(ngModel)]="query.value"
(ngModelChange)="
setVariableValues($event, queryIndex, clauseIndex)
"
/>
</div>
</div>
<div class="clause-buttons clr-col-md-2 btn-group">
<br />
@if (innerWidth > 768) {
<button
class="btn btn-warning btn-block"
(click)="removeClause(queryIndex, clauseIndex)"
[disabled]="clauses.queryObj[clauseIndex].elements.length === 1"
>
<clr-icon shape="minus"></clr-icon>
<span></span>
</button>
<button
class="btn btn-success btn-block"
(click)="addClause(clauseIndex)"
class="btn btn-primary btn-block mt-10"
(click)="addGroupClause()"
>
<clr-icon shape="plus"></clr-icon>
<span></span>
<span>Group</span>
</button>
</div>
}
</div>
<div class="clause-query clr-col-md-10">
@if (clauses.queryObj.length > 1) {
<clr-icon
(click)="removeGroupClause(clauseIndex)"
shape="times"
size="36"
class="remove-group-clause-button"
></clr-icon>
}
@for (
query of clause.elements;
track query;
let queryIndex = $index
) {
<div class="clr-row" [class.invalid-clause]="query.invalidClause">
<!-- VARIABLE -->
<div class="variable-col form-group clr-col-md-3">
<div class="datalist-wrapper">
<app-soft-select
label="Variable"
[id]="
'select_vals_var_id' + queryIndex + '_' + clauseIndex
"
[inputId]="'vals_var_id' + queryIndex + '_' + clauseIndex"
[emitOnlySelected]="true"
[(value)]="query.variable"
(onInputEvent)="
variableInputChange(
query.variable,
queryIndex,
clauseIndex,
$event
)
"
>
@for (column of cols; track column) {
<option>
{{ column.NAME }}
</option>
}
</app-soft-select>
</div>
</div>
<!-- OPERATOR -->
<div class="operator-col form-group clr-col-md-3">
<clr-select-container>
<label>Operator</label>
<select
[(ngModel)]="query.operator"
(ngModelChange)="
setVariableOperator(
queryIndex,
query.operator,
clauseIndex
)
"
clrSelect
>
@for (opr of query.operators; track opr) {
<option>{{ opr }}</option>
}
</select>
</clr-select-container>
</div>
<!-- VALUE -->
<div
*ngVar="
query.ddtype === 'DATE' ||
query.ddtype === 'DATETIME' ||
query.ddtype === 'TIME' as isDateTime
"
class="value-col form-group clr-col-md-3"
>
@if (query.operator === 'IN' || query.operator === 'NOT IN') {
<div class="checkbox-vals">
<button
(click)="
currentQueryIndex = queryIndex;
currentClauseIndex = clauseIndex
"
type="button"
class="btn btn-link"
>
Choose values
</button>
<ng-container
*ngTemplateOutlet="
checkboxValues;
context: {
query: query,
queryIndex: queryIndex,
clauseIndex: clauseIndex
}
"
>
</ng-container>
</div>
}
@if (
query.operator !== 'BETWEEN' &&
query.operator !== 'IN' &&
query.operator !== 'NOT IN' &&
query.operator !== 'LIKE' &&
query.operator !== 'CONTAINS' &&
query.operator !== 'BEGINS_WITH'
) {
<div class="single-field-vals">
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPicker;
context: {
query: query,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
}
@if (query.operator === 'BETWEEN') {
<div class="range-vals">
<div class="from">
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPickerRange;
context: {
range: 'start',
query: query,
queryValueIndex: 0,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
<div class="to">
<ng-container
*ngTemplateOutlet="
isDateTime && usePickers ? picker : notPickerRange;
context: {
range: 'end',
query: query,
queryValueIndex: 1,
queryIndex: queryIndex,
clauseIndex: clauseIndex,
isDateTime: isDateTime
}
"
>
<!-- Based on check above, here correct ng-template will be put (picker or dropdown) -->
</ng-container>
</div>
</div>
}
@if (
query.operator === 'LIKE' ||
query.operator === 'BEGINS_WITH' ||
query.operator === 'CONTAINS'
) {
<div class="contains-vals">
<label class="clr-control-label">Value</label>
<input
class="input-val"
type="text"
[(ngModel)]="query.value"
(ngModelChange)="
setVariableValues($event, queryIndex, clauseIndex)
"
/>
</div>
}
</div>
<div class="clause-buttons clr-col-md-2 btn-group">
<button
class="btn btn-warning btn-block"
(click)="removeClause(queryIndex, clauseIndex)"
[disabled]="
clauses.queryObj[clauseIndex].elements.length === 1
"
>
<clr-icon shape="minus"></clr-icon>
<span></span>
</button>
<button
class="btn btn-success btn-block"
(click)="addClause(clauseIndex)"
>
<clr-icon shape="plus"></clr-icon>
<span></span>
</button>
</div>
</div>
}
</div>
</div>
<br />
</div>
<br />
</div>
}
</div>
</div>
@@ -305,8 +307,8 @@
let-queryIndex="queryIndex"
let-clauseIndex="clauseIndex"
>
<ng-container [ngSwitch]="query.ddtype">
<ng-container *ngSwitchCase="'DATE'">
@switch (query.ddtype) {
@case ('DATE') {
<app-soft-select
label="Value"
type="date"
@@ -323,14 +325,13 @@
>
<!-- In case we want to enable dropdown on the pickers, uncomment below -->
<!-- <ng-container *ngFor="let value of query.values">
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'date' }}
</option>
</ng-container> -->
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'date' }}
</option>
</ng-container> -->
</app-soft-select>
</ng-container>
<ng-container *ngSwitchCase="'DATETIME'">
}
@case ('DATETIME') {
<app-soft-select
label="Value"
type="date"
@@ -348,12 +349,11 @@
>
<!-- In case we want to enable dropdown on the pickers, uncomment below -->
<!-- <ng-container *ngFor="let value of query.values">
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'date' }}
</option>
</ng-container> -->
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'date' }}
</option>
</ng-container> -->
</app-soft-select>
<app-soft-select
type="time"
[disableSoftselect]="true"
@@ -370,14 +370,13 @@
>
<!-- In case we want to enable dropdown on the pickers, uncomment below -->
<!-- <ng-container *ngFor="let value of query.values">
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'time' }}
</option>
</ng-container> -->
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'time' }}
</option>
</ng-container> -->
</app-soft-select>
</ng-container>
<ng-container *ngSwitchCase="'TIME'">
}
@case ('TIME') {
<app-soft-select
label="Value"
type="time"
@@ -394,13 +393,13 @@
>
<!-- In case we want to enable dropdown on the pickers, uncomment below -->
<!-- <ng-container *ngFor="let value of query.values">
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'time' }}
</option>
</ng-container> -->
<option *ngIf="value.unformatted !== null">
{{ value.formatted | dateTimeFormatter: 'time' }}
</option>
</ng-container> -->
</app-soft-select>
</ng-container>
</ng-container>
}
}
</ng-template>
<ng-template
@@ -425,19 +424,27 @@
onAutocompleteLoadingMore($event, query.variable, queryIndex, clauseIndex)
"
>
<div *ngIf="!query.valueVariable">
<option [value]="column.unformatted" *ngFor="let column of query.values">
{{ column.formatted.trim() }}
</option>
</div>
@if (!query.valueVariable) {
<div>
@for (column of query.values; track column) {
<option [value]="column.unformatted">
{{ column.formatted.trim() }}
</option>
}
</div>
}
<div *ngIf="query.valueVariable">
<ng-container *ngFor="let column of cols">
<option [value]="column.NAME" *ngIf="column.TYPE === query.type">
{{ column.NAME }}
</option>
</ng-container>
</div>
@if (query.valueVariable) {
<div>
@for (column of cols; track column) {
@if (column.TYPE === query.type) {
<option [value]="column.NAME">
{{ column.NAME }}
</option>
}
}
</div>
}
</app-soft-select>
</ng-template>
@@ -466,9 +473,11 @@
: false
"
>
<option [value]="column.formatted" *ngFor="let column of query.values">
{{ column.formatted }}
</option>
@for (column of query.values; track column) {
<option [value]="column.formatted">
{{ column.formatted }}
</option>
}
</app-soft-select>
</ng-template>
@@ -487,30 +496,31 @@
>
<h3 class="modal-title">Select values</h3>
<div class="modal-body">
<h5 *ngIf="!isArr(query.value)" class="no-values">
No values available.
</h5>
@if (!isArr(query.value)) {
<h5 class="no-values">No values available.</h5>
}
<section class="form-block" *ngIf="isArr(query.value)">
<clr-checkbox-container>
<clr-checkbox-wrapper
*ngFor="let column of query.values; let i = index"
>
<input
type="checkbox"
clrCheckbox
[(ngModel)]="query.value[i].checked"
(ngModelChange)="
setVariableValues($event, queryIndex, clauseIndex)
"
/>
<label>
{{ column.formatted }}
</label>
</clr-checkbox-wrapper>
</clr-checkbox-container>
</section>
@if (isArr(query.value)) {
<section class="form-block">
<clr-checkbox-container>
@for (column of query.values; track column; let i = $index) {
<clr-checkbox-wrapper>
<input
type="checkbox"
clrCheckbox
[(ngModel)]="query.value[i].checked"
(ngModelChange)="
setVariableValues($event, queryIndex, clauseIndex)
"
/>
<label>
{{ column.formatted }}
</label>
</clr-checkbox-wrapper>
}
</clr-checkbox-container>
</section>
}
</div>
<div class="modal-footer">
<button
File diff suppressed because it is too large Load Diff
@@ -1,148 +1,161 @@
<div class="content-area">
<div class="card">
<div *ngIf="remained === 0" class="d-flex justify-content-center">
<div class="card-block noapprovals-info-wrapper">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<h3 class="text-center color-gray">There are no approvals remaining</h3>
@if (remained === 0) {
<div class="d-flex justify-content-center">
<div class="card-block noapprovals-info-wrapper">
<clr-icon
shape="warning-standard"
size="60"
class="is-info icon-dc-fill"
></clr-icon>
<h3 class="text-center color-gray">
There are no approvals remaining
</h3>
</div>
</div>
</div>
}
<div class="card-header" [ngClass]="{ noBorder: !loaded }">
<h3
class="center clr-col-md-12 text-center"
*ngIf="loaded && remained !== 0"
>
REVIEW
</h3>
<p
class="text-center font-weight-700 color-dark-gray"
*ngIf="loaded && remained !== 0"
>
You have <span>{{ remained }} </span>approvals remaining
</p>
@if (loaded && remained !== 0) {
<h3 class="center clr-col-md-12 text-center">REVIEW</h3>
}
@if (loaded && remained !== 0) {
<p class="text-center font-weight-700 color-dark-gray">
You have <span>{{ remained }} </span>approvals remaining
</p>
}
</div>
<div *ngIf="!loaded" class="approvals-list-wrapper">
<span class="spinner" *ngIf="!loaded"> Loading... </span>
<div *ngIf="!loaded">
<h3>Loading approvals list</h3>
@if (!loaded) {
<div class="approvals-list-wrapper">
@if (!loaded) {
<span class="spinner"> Loading... </span>
}
@if (!loaded) {
<div>
<h3>Loading approvals list</h3>
</div>
}
</div>
</div>
}
<div class="clr-col-md-12" ng-if="loaded">
<div *ngIf="approveList && remained !== 0">
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
<clr-dg-column [clrDgField]="'submitter'">
SUBMITTER
<clr-dg-string-filter
[clrDgStringFilter]="submitterFilter"
aria-label="Filter submitter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'baseTable'">
BASE TABLE
<clr-dg-string-filter
[clrDgStringFilter]="baseTableFilter"
aria-label="Filter base table"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>ACTION</clr-dg-column>
<clr-dg-column>DOWNLOAD</clr-dg-column>
<clr-dg-row
*clrDgItems="let approveItem of approveList; let i = index"
>
<clr-dg-cell>{{ approveItem.submitter }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.baseTable }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.submitted }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.submitReason }}</clr-dg-cell>
<clr-dg-cell>
<div
class="clr-row d-flex justify-content-around"
role="toolbar"
aria-label="Table actions"
>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-green"
(click)="getClicked(i)"
@if (approveList && remained !== 0) {
<div>
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
<clr-dg-column [clrDgField]="'submitter'">
SUBMITTER
<clr-dg-string-filter
[clrDgStringFilter]="submitterFilter"
aria-label="Filter submitter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'baseTable'">
BASE TABLE
<clr-dg-string-filter
[clrDgStringFilter]="baseTableFilter"
aria-label="Filter base table"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>ACTION</clr-dg-column>
<clr-dg-column>DOWNLOAD</clr-dg-column>
<clr-dg-row
*clrDgItems="let approveItem of approveList; let i = index"
>
<clr-dg-cell>{{ approveItem.submitter }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.baseTable }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.submitted }}</clr-dg-cell>
<clr-dg-cell>{{ approveItem.submitReason }}</clr-dg-cell>
<clr-dg-cell>
<div
class="clr-row d-flex justify-content-around"
role="toolbar"
aria-label="Table actions"
>
<clr-icon
shape="check"
size="24"
aria-hidden="true"
></clr-icon>
<span class="tooltip-content">Go to review page screen</span>
</a>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-red"
(click)="!approveItem.rejectLoading ? rejecting(i) : ''"
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-green"
(click)="getClicked(i)"
>
<clr-icon
shape="check"
size="24"
aria-hidden="true"
></clr-icon>
<span class="tooltip-content"
>Go to review page screen</span
>
</a>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-red"
(click)="!approveItem.rejectLoading ? rejecting(i) : ''"
>
@if (!approveItem.rejectLoading) {
<clr-icon
shape="ban"
size="22"
aria-hidden="true"
></clr-icon>
}
@if (approveItem.rejectLoading) {
<clr-spinner
[clrSmall]="true"
aria-hidden="true"
></clr-spinner>
}
<span class="tooltip-content">Reject</span>
</a>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
(click)="getTable(approveItem.tableId)"
>
<clr-icon
shape="code"
size="28"
aria-hidden="true"
></clr-icon>
<span class="tooltip-content"
>Go to staged data screen</span
>
</a>
</div>
</clr-dg-cell>
<clr-dg-cell class="p-0 d-flex justify-content-center">
<button
class="btn btn-success"
aria-label="Download audit file"
[id]="approveItem.tableId"
(click)="
download(approveItem.tableId); $event.stopPropagation()
"
>
<clr-icon
*ngIf="!approveItem.rejectLoading"
shape="ban"
size="22"
aria-hidden="true"
></clr-icon>
<clr-spinner
*ngIf="approveItem.rejectLoading"
[clrSmall]="true"
aria-hidden="true"
></clr-spinner>
<span class="tooltip-content">Reject</span>
</a>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
(click)="getTable(approveItem.tableId)"
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]"
>Items per page</clr-dg-page-size
>
<clr-icon
shape="code"
size="28"
aria-hidden="true"
></clr-icon>
<span class="tooltip-content">Go to staged data screen</span>
</a>
</div>
</clr-dg-cell>
<clr-dg-cell class="p-0 d-flex justify-content-center">
<button
class="btn btn-success"
aria-label="Download audit file"
[id]="approveItem.tableId"
(click)="
download(approveItem.tableId); $event.stopPropagation()
"
>
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]"
>Items per page</clr-dg-page-size
>
{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} approvals
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
{{ pagination.firstItem + 1 }} -
{{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} approvals
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
}
</div>
</div>
</div>
@@ -1,12 +1,13 @@
<div class="content-area">
<div
*ngIf="noData"
id="noDataContainer"
class="card-block d-flex justify-content-center flex-column align-items-center"
>
<clr-icon shape="warning-standard" size="60" class="is-info"></clr-icon>
<h3 class="text-center color-gray">There is no history to show</h3>
</div>
@if (noData) {
<div
id="noDataContainer"
class="card-block d-flex justify-content-center flex-column align-items-center"
>
<clr-icon shape="warning-standard" size="60" class="is-info"></clr-icon>
<h3 class="text-center color-gray">There is no history to show</h3>
</div>
}
<clr-modal [(clrModalOpen)]="openModal" [clrModalSize]="'xl'">
<h4 class="modal-title">Approval details</h4>
<div class="modal-body">
@@ -18,31 +19,38 @@
</tr>
</thead>
<tbody>
<tr *ngFor="let col of tableTitles; let ind = index">
<td class="left">{{ col }}</td>
<td class="left">
<a
*ngIf="ind < 1"
(click)="getTable(approveData[col])"
class="cursor-pointer table-link"
>{{ approveData[col] }}</a
>
<div *ngIf="ind < 2 && ind >= 1">
<a
(click)="getBaseTable(approveData[col])"
class="cursor-pointer table-link"
>VIEW</a
>
<span> / </span>
<a
(click)="getEditTable(approveData[col])"
class="cursor-pointer table-link"
>EDIT</a
>
</div>
<span *ngIf="ind >= 2">{{ approveData[col] }}</span>
</td>
</tr>
@for (col of tableTitles; track col; let ind = $index) {
<tr>
<td class="left">{{ col }}</td>
<td class="left">
@if (ind < 1) {
<a
(click)="getTable(approveData[col])"
class="cursor-pointer table-link"
>{{ approveData[col] }}</a
>
}
@if (ind < 2 && ind >= 1) {
<div>
<a
(click)="getBaseTable(approveData[col])"
class="cursor-pointer table-link"
>VIEW</a
>
<span> / </span>
<a
(click)="getEditTable(approveData[col])"
class="cursor-pointer table-link"
>EDIT</a
>
</div>
}
@if (ind >= 2) {
<span>{{ approveData[col] }}</span>
}
</td>
</tr>
}
</tbody>
</table>
</div>
@@ -58,148 +66,159 @@
</div>
</clr-modal>
<div
*ngIf="!loaded"
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
<span class="spinner" *ngIf="!loaded"> Loading... </span>
<div *ngIf="!loaded">
<h3>Loading history</h3>
</div>
</div>
<div *ngIf="!noData && loaded" class="card">
<div class="card-header">
<h3 class="center clr-col-md-12 text-center" *ngIf="loaded">HISTORY</h3>
<p
class="text-center font-weight-700 color-dark-gray"
*ngIf="licenceState.value.history_rows_allowed !== Infinity"
>
To unlock more than
{{ licenceState.value.history_rows_allowed }} records, contact
support&#64;datacontroller.io
</p>
</div>
<clr-datagrid
class="datagrid-history datagrid-custom-footer"
*ngIf="loaded"
>
<clr-dg-column [clrDgField]="'basetable'">
BASE_TABLE
<clr-dg-string-filter
[clrDgStringFilter]="baseTableFilter"
aria-label="Filter base table"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">
STATUS
<clr-dg-string-filter
[clrDgStringFilter]="statusFilter"
aria-label="Filter status"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitter'">
SUBMITTER
<clr-dg-string-filter
[clrDgStringFilter]="submitterFilter"
aria-label="Filter submitter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submittedReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'reviewed'">
APPROVED / REJECTED
<clr-dg-string-filter
[clrDgStringFilter]="reviewedFilter"
aria-label="Filter reviewed date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>DOWNLOAD</clr-dg-column>
<clr-dg-row
*clrDgItems="let historyItem of history"
(click)="getApprIndex(historyItem)"
>
<clr-dg-cell class="verCenter">
<a
class="btn btn-sm btn-link m-0"
(click)="getBaseTable(historyItem.basetable)"
>{{ historyItem.basetable }}</a
>
</clr-dg-cell>
<clr-dg-cell
class="verCenter"
[ngClass]="{
rejected: historyItem.status === 'REJECTED',
accepted: historyItem.status === 'APPROVED'
}"
>{{ historyItem.status }}</clr-dg-cell
>
<clr-dg-cell class="verCenter">{{ historyItem.submitter }}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{
historyItem.submittedReason
}}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{ historyItem.submitted }}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{ historyItem.reviewed }}</clr-dg-cell>
<clr-dg-cell class="verCenter p-0 d-flex justify-content-center">
<button
aria-label="Download audit file"
class="btn btn-success"
(click)="download(historyItem.tableId); $event.stopPropagation()"
>
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<!-- Let's keep this part if in future we decide to do the paging instead of `load more` approach -->
<!-- <clr-dg-footer class="d-flex justify-content-start">
<span>items per page</span>
<select class="mx-5" [(ngModel)]="itemsNum">
<option [ngValue]="3">3</option>
<option [ngValue]="5">5</option>
<option [ngValue]="10">10</option>
<option [ngValue]="15">15</option>
</select>
<clr-dg-pagination
#pagination
[clrDgPageSize]="itemsNum"
class="center"
>
{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} updates
</clr-dg-pagination>
</clr-dg-footer> -->
</clr-datagrid>
@if (!loaded) {
<div
class="load-more d-flex clr-justify-content-center clr-align-items-center"
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
<button
*ngIf="
@if (!loaded) {
<span class="spinner"> Loading... </span>
}
@if (!loaded) {
<div>
<h3>Loading history</h3>
</div>
}
</div>
}
@if (!noData && loaded) {
<div class="card">
<div class="card-header">
@if (loaded) {
<h3 class="center clr-col-md-12 text-center">HISTORY</h3>
}
@if (licenceState.value.history_rows_allowed !== Infinity) {
<p class="text-center font-weight-700 color-dark-gray">
To unlock more than
{{ licenceState.value.history_rows_allowed }} records, contact
support&#64;datacontroller.io
</p>
}
</div>
@if (loaded) {
<clr-datagrid class="datagrid-history datagrid-custom-footer">
<clr-dg-column [clrDgField]="'basetable'">
BASE_TABLE
<clr-dg-string-filter
[clrDgStringFilter]="baseTableFilter"
aria-label="Filter base table"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">
STATUS
<clr-dg-string-filter
[clrDgStringFilter]="statusFilter"
aria-label="Filter status"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitter'">
SUBMITTER
<clr-dg-string-filter
[clrDgStringFilter]="submitterFilter"
aria-label="Filter submitter"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submittedReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'reviewed'">
APPROVED / REJECTED
<clr-dg-string-filter
[clrDgStringFilter]="reviewedFilter"
aria-label="Filter reviewed date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column>DOWNLOAD</clr-dg-column>
<clr-dg-row
*clrDgItems="let historyItem of history"
(click)="getApprIndex(historyItem)"
>
<clr-dg-cell class="verCenter">
<a
class="btn btn-sm btn-link m-0"
(click)="getBaseTable(historyItem.basetable)"
>{{ historyItem.basetable }}</a
>
</clr-dg-cell>
<clr-dg-cell
class="verCenter"
[ngClass]="{
rejected: historyItem.status === 'REJECTED',
accepted: historyItem.status === 'APPROVED'
}"
>{{ historyItem.status }}</clr-dg-cell
>
<clr-dg-cell class="verCenter">{{
historyItem.submitter
}}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{
historyItem.submittedReason
}}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{
historyItem.submitted
}}</clr-dg-cell>
<clr-dg-cell class="verCenter">{{
historyItem.reviewed
}}</clr-dg-cell>
<clr-dg-cell class="verCenter p-0 d-flex justify-content-center">
<button
aria-label="Download audit file"
class="btn btn-success"
(click)="
download(historyItem.tableId); $event.stopPropagation()
"
>
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<!-- Let's keep this part if in future we decide to do the paging instead of `load more` approach -->
<!-- <clr-dg-footer class="d-flex justify-content-start">
<span>items per page</span>
<select class="mx-5" [(ngModel)]="itemsNum">
<option [ngValue]="3">3</option>
<option [ngValue]="5">5</option>
<option [ngValue]="10">10</option>
<option [ngValue]="15">15</option>
</select>
<clr-dg-pagination
#pagination
[clrDgPageSize]="itemsNum"
class="center"
>
{{ pagination.firstItem + 1 }} - {{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} updates
</clr-dg-pagination>
</clr-dg-footer> -->
</clr-datagrid>
}
<div
class="load-more d-flex clr-justify-content-center clr-align-items-center"
>
@if (
this.licenceState.value.history_rows_allowed === Infinity &&
rowsLeftToLoad > 0
"
(click)="loadData()"
[clrLoading]="loadingMore"
class="btn btn-success"
>
Load {{ rowsLeftToLoad }} more
</button>
) {
<button
(click)="loadData()"
[clrLoading]="loadingMore"
class="btn btn-success"
>
Load {{ rowsLeftToLoad }} more
</button>
}
</div>
</div>
</div>
}
</div>
@@ -1,123 +1,136 @@
<div class="w-100">
<div *ngIf="!subReady" class="content-area">
<div *ngIf="!subReady" class="card">
<div
*ngIf="remained === 0 && loaded"
class="d-flex justify-content-center"
>
<div
class="no-submitted-tables card-block d-flex justify-content-center flex-column align-items-center"
>
<clr-icon
shape="warning-standard"
size="60"
class="is-info"
></clr-icon>
<h3 class="text-center color-gray">
You have not submitted any tables
</h3>
</div>
</div>
<div class="card-header" [ngClass]="{ noBorder: !loaded }">
<h3 class="center clr-col-md-12 text-center" *ngIf="remained !== 0">
SUBMIT QUEUE
</h3>
<p
class="text-center font-weight-700 color-dark-gray"
*ngIf="loaded && remained !== 0"
>
You have <span>{{ remained }} </span>submissions waiting to be
approved
</p>
</div>
<div
*ngIf="!loaded"
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
<span class="spinner" *ngIf="!loaded"> Loading... </span>
<div *ngIf="!loaded">
<h3>Loading submitted list</h3>
</div>
</div>
<div class="clr-col-md-12" *ngIf="loaded">
<div *ngIf="submitterList && remained !== 0">
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
<clr-dg-column>BASE TABLE</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column class="d-flex justify-content-center"
>ACTION</clr-dg-column
@if (!subReady) {
<div class="content-area">
@if (!subReady) {
<div class="card">
@if (remained === 0 && loaded) {
<div class="d-flex justify-content-center">
<div
class="no-submitted-tables card-block d-flex justify-content-center flex-column align-items-center"
>
<clr-icon
shape="warning-standard"
size="60"
class="is-info"
></clr-icon>
<h3 class="text-center color-gray">
You have not submitted any tables
</h3>
</div>
</div>
}
<div class="card-header" [ngClass]="{ noBorder: !loaded }">
@if (remained !== 0) {
<h3 class="center clr-col-md-12 text-center">SUBMIT QUEUE</h3>
}
@if (loaded && remained !== 0) {
<p class="text-center font-weight-700 color-dark-gray">
You have <span>{{ remained }} </span>submissions waiting to be
approved
</p>
}
</div>
@if (!loaded) {
<div
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
<clr-dg-column class="d-flex justify-content-center"
>DOWNLOAD</clr-dg-column
>
<clr-dg-row
*clrDgItems="let sub of submitterList; let i = index"
(click)="goToDetails(sub.tableId)"
class="cursor-pointer"
>
<clr-dg-cell>{{ sub.base }}</clr-dg-cell>
<clr-dg-cell>{{ sub.submitted }}</clr-dg-cell>
<!-- <clr-dg-cell>{{sub.approver}}</clr-dg-cell> -->
<clr-dg-cell>{{ sub.submitReason }}</clr-dg-cell>
<clr-dg-cell>
<div
class="row justify-content-around"
role="tooltip"
aria-label="Go to staged data screen"
>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
(click)="goToStage(sub.tableId)"
>
<clr-icon shape="code" size="28"></clr-icon>
<span class="tooltip-content"
>Go to staged data screen</span
>
</a>
@if (!loaded) {
<span class="spinner"> Loading... </span>
}
@if (!loaded) {
<div>
<h3>Loading submitted list</h3>
</div>
</clr-dg-cell>
<clr-dg-cell class="p-0 d-flex justify-content-center">
<button
class="btn btn-success"
aria-label="Download audit file for table record"
(click)="download(sub.tableId); $event.stopPropagation()"
>
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]"
>Items per page</clr-dg-page-size
>
{{ pagination.firstItem + 1 }} -
{{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} submissions
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
}
</div>
}
@if (loaded) {
<div class="clr-col-md-12">
@if (submitterList && remained !== 0) {
<div>
<clr-datagrid class="datagrid-compact datagrid-custom-footer">
<clr-dg-column>BASE TABLE</clr-dg-column>
<clr-dg-column [clrDgField]="'submitted'">
SUBMITTED
<clr-dg-string-filter
[clrDgStringFilter]="submittedFilter"
aria-label="Filter submitted date"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column [clrDgField]="'submitReason'">
SUBMIT REASON
<clr-dg-string-filter
[clrDgStringFilter]="submitReasonFilter"
aria-label="Filter submit reason"
></clr-dg-string-filter>
</clr-dg-column>
<clr-dg-column class="d-flex justify-content-center"
>ACTION</clr-dg-column
>
<clr-dg-column class="d-flex justify-content-center"
>DOWNLOAD</clr-dg-column
>
<clr-dg-row
*clrDgItems="let sub of submitterList; let i = index"
(click)="goToDetails(sub.tableId)"
class="cursor-pointer"
>
<clr-dg-cell>{{ sub.base }}</clr-dg-cell>
<clr-dg-cell>{{ sub.submitted }}</clr-dg-cell>
<!-- <clr-dg-cell>{{sub.approver}}</clr-dg-cell> -->
<clr-dg-cell>{{ sub.submitReason }}</clr-dg-cell>
<clr-dg-cell>
<div
class="row justify-content-around"
role="tooltip"
aria-label="Go to staged data screen"
>
<a
class="column-center links tooltip tooltip-md tooltip-bottom-left color-blue"
(click)="goToStage(sub.tableId)"
>
<clr-icon shape="code" size="28"></clr-icon>
<span class="tooltip-content"
>Go to staged data screen</span
>
</a>
</div>
</clr-dg-cell>
<clr-dg-cell class="p-0 d-flex justify-content-center">
<button
class="btn btn-success"
aria-label="Download audit file for table record"
(click)="
download(sub.tableId); $event.stopPropagation()
"
>
<clr-icon shape="download"></clr-icon>
</button>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[3, 5, 10, 15]"
>Items per page</clr-dg-page-size
>
{{ pagination.firstItem + 1 }} -
{{ pagination.lastItem + 1 }} of
{{ pagination.totalItems }} submissions
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
}
</div>
}
</div>
</div>
}
</div>
</div>
}
<div *ngIf="subReady">
<app-approve-details></app-approve-details>
</div>
@if (subReady) {
<div>
<app-approve-details></app-approve-details>
</div>
}
</div>
+131 -115
View File
@@ -1,130 +1,146 @@
<app-sidebar class="sidebar-height">
<clr-tree>
<clr-tree-node *ngIf="roles" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Roles"
name="input"
[(ngModel)]="roleSearch"
(keyup)="roleListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="roleSearch = ''; roleListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let role of roles">
<clr-tree-node
(click)="roleOnClick(role)"
*ngIf="!role['hidden']"
[class.active]="role.ROLEURI === roleUri"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="blocks-group"></clr-icon>
{{ role.ROLENAME }}
</p>
@if (roles) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Roles"
name="input"
[(ngModel)]="roleSearch"
(keyup)="roleListOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
(click)="roleSearch = ''; roleListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (role of roles; track role) {
@if (!role['hidden']) {
<clr-tree-node
(click)="roleOnClick(role)"
[class.active]="role.ROLEURI === roleUri"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="blocks-group"></clr-icon>
{{ role.ROLENAME }}
</p>
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
<div class="content-area">
<div *ngIf="loading" class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
<div *ngIf="roleMembers && !loading">
<div class="clr-row">
<div class="clr-col-8">
<table class="table role-info">
<tbody>
<tr>
<td class="left">
<p class="role-info-text">
<b>{{ roleName }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<i>{{ roleDesc }}</i>
</td>
</tr>
</tbody>
</table>
</div>
@if (loading) {
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
<div class="clr-row">
<div class="clr-col-8">
<div class="card role-data">
<div>
<h3>MEMBERS ({{ roleMembersCount }})</h3>
<h5 *ngIf="roleMembersCount == 0">No Members Present</h5>
<div class="table-container">
<table *ngIf="roleMembersCount != 0" class="table member-table">
<thead>
<tr>
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
</tr>
</thead>
<tbody>
<tr
[routerLink]="'/view/usernav/users/' + member.URIMEM"
*ngFor="let member of roleMembers"
>
<td class="">{{ member.MEMBERNAME }}</td>
<td class="">{{ member.EMAIL }}</td>
<td class="">{{ member.MEMBERCREATED }}</td>
<td class="">{{ member.MEMBERUPDATED }}</td>
</tr>
</tbody>
</table>
}
@if (roleMembers && !loading) {
<div>
<div class="clr-row">
<div class="clr-col-8">
<table class="table role-info">
<tbody>
<tr>
<td class="left">
<p class="role-info-text">
<b>{{ roleName }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<i>{{ roleDesc }}</i>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="clr-row">
<div class="clr-col-8">
<div class="card role-data">
<div>
<h3>MEMBERS ({{ roleMembersCount }})</h3>
@if (roleMembersCount == 0) {
<h5>No Members Present</h5>
}
<div class="table-container">
@if (roleMembersCount != 0) {
<table class="table member-table">
<thead>
<tr>
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
</tr>
</thead>
<tbody>
@for (member of roleMembers; track member) {
<tr
[routerLink]="'/view/usernav/users/' + member.URIMEM"
>
<td class="">{{ member.MEMBERNAME }}</td>
<td class="">{{ member.EMAIL }}</td>
<td class="">{{ member.MEMBERCREATED }}</td>
<td class="">{{ member.MEMBERUPDATED }}</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
</div>
<hr />
<div>
<h3>Groups ({{ roleGroupsCount }})</h3>
<h5 *ngIf="roleGroupsCount == 0">No Groups Present !</h5>
<div class="table-container">
<table *ngIf="roleGroupsCount != 0" class="table member-table">
<thead>
<tr>
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
</tr>
</thead>
<tbody>
<tr
[routerLink]="'/view/usernav/groups/' + group.URIMEM"
*ngFor="let group of roleGroups"
>
<td class="">{{ group.MEMBERNAME }}</td>
<td class="">{{ group.EMAIL }}</td>
<td class="">{{ group.MEMBERCREATED }}</td>
<td class="">{{ group.MEMBERUPDATED }}</td>
</tr>
</tbody>
</table>
<hr />
<div>
<h3>Groups ({{ roleGroupsCount }})</h3>
@if (roleGroupsCount == 0) {
<h5>No Groups Present !</h5>
}
<div class="table-container">
@if (roleGroupsCount != 0) {
<table class="table member-table">
<thead>
<tr>
<td class="width-25"><b>NAME</b></td>
<td class="width-25"><b>EMAIL</b></td>
<td class="width-25"><b>CREATED</b></td>
<td class=""><b>UPDATED</b></td>
</tr>
</thead>
<tbody>
@for (group of roleGroups; track group) {
<tr
[routerLink]="'/view/usernav/groups/' + group.URIMEM"
>
<td class="">{{ group.MEMBERNAME }}</td>
<td class="">{{ group.EMAIL }}</td>
<td class="">{{ group.MEMBERCREATED }}</td>
<td class="">{{ group.MEMBERUPDATED }}</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
<br />
</div>
<br />
</div>
</div>
</div>
</div>
}
</div>
@@ -7,66 +7,62 @@
>
<h3 class="modal-title">
{{ data.modalTitle }}
<p
*ngIf="data.sasService && data.sasService.length > 0"
class="sasService mt-0"
>
SAS Service: <strong>{{ data.sasService }}</strong>
</p>
@if (data.sasService && data.sasService.length > 0) {
<p class="sasService mt-0">
SAS Service: <strong>{{ data.sasService }}</strong>
</p>
}
</h3>
<div class="modal-body">
<div [innerHTML]="data.message" class="abortMsg"></div>
<div *ngIf="data.details !== null" class="systext">
<p><strong>SYSWARNINGTEXT:</strong> {{ data.details.SYSWARNINGTEXT }}</p>
<p><strong>SYSERRORTEXT:</strong> {{ data.details.SYSERRORTEXT }}</p>
<p><strong>MAC:</strong> {{ data.details.MAC }}</p>
</div>
@if (data.details !== null) {
<div class="systext">
<p>
<strong>SYSWARNINGTEXT:</strong> {{ data.details.SYSWARNINGTEXT }}
</p>
<p><strong>SYSERRORTEXT:</strong> {{ data.details.SYSERRORTEXT }}</p>
<p><strong>MAC:</strong> {{ data.details.MAC }}</p>
</div>
}
</div>
<div class="modal-footer">
<button
*ngIf="showConfiguratorButton(data.sasService)"
type="button"
class="btn btn-primary"
(click)="openConfigurator()"
>
Open configurator
</button>
@if (showConfiguratorButton(data.sasService)) {
<button
type="button"
class="btn btn-primary"
(click)="openConfigurator()"
>
Open configurator
</button>
}
<button
*ngIf="data.details !== null"
type="button"
class="btn btn-primary"
(click)="openRequestsModal()"
>
Open requests modal
</button>
@if (data.details !== null) {
<button
type="button"
class="btn btn-primary"
(click)="openRequestsModal()"
>
Open requests modal
</button>
}
<button
*ngIf="data.details?.LOG && (data.details?.LOG?.trim())!.length > 0"
type="button"
class="btn btn-primary"
(click)="downloadLog()"
>
Download log
</button>
@if (data.details?.LOG && (data.details?.LOG?.trim())!.length > 0) {
<button type="button" class="btn btn-primary" (click)="downloadLog()">
Download log
</button>
}
<button
*ngIf="!forceReload"
type="button"
class="btn btn-primary"
(click)="closeAbortModal()"
>
Close
</button>
@if (!forceReload) {
<button type="button" class="btn btn-primary" (click)="closeAbortModal()">
Close
</button>
}
<button
*ngIf="forceReload"
type="button"
class="btn btn-primary"
(click)="reload()"
>
Reload
</button>
@if (forceReload) {
<button type="button" class="btn btn-primary" (click)="reload()">
Reload
</button>
}
</div>
</clr-modal>
@@ -1,15 +1,18 @@
<clr-alerts *ngIf="hasOpenAlert">
<clr-alert
*ngFor="let alert of alerts"
[clrAlertType]="alert.type"
[clrAlertAppLevel]="true"
[(clrAlertClosed)]="alert.closed"
(clrAlertClosedChange)="onAlertClose()"
>
<div class="alert-item">
<span class="alert-text">
{{ alert.message }}
</span>
</div>
</clr-alert>
</clr-alerts>
@if (hasOpenAlert) {
<clr-alerts>
@for (alert of alerts; track alert) {
<clr-alert
[clrAlertType]="alert.type"
[clrAlertAppLevel]="true"
[(clrAlertClosed)]="alert.closed"
(clrAlertClosedChange)="onAlertClose()"
>
<div class="alert-item">
<span class="alert-text">
{{ alert.message }}
</span>
</div>
</clr-alert>
}
</clr-alerts>
}
@@ -41,12 +41,10 @@
<ng-content></ng-content>
</div>
<option
*ngIf="options.children.length > 0 && enableLoadMore"
data-type="load-more"
class="load-more"
>
{{ loadingMore ? 'Loading...' : 'LOAD MORE' }}
</option>
@if (options.children.length > 0 && enableLoadMore) {
<option data-type="load-more" class="load-more">
{{ loadingMore ? 'Loading...' : 'LOAD MORE' }}
</option>
}
</div>
</div>
@@ -6,35 +6,37 @@
>
<h3 class="modal-title center text-center color-darker-gray">Dataset Meta</h3>
<div class="modal-body">
<p *ngIf="dsmetaTabs.length < 1" class="text-center">
No dataset meta to show.
</p>
@if (dsmetaTabs.length < 1) {
<p class="text-center">No dataset meta to show.</p>
}
<clr-tabs clrLayout="vertical">
<clr-tab *ngFor="let tab of tabs; let index = index">
<button clrTabLink id="link1">{{ tab.name }}</button>
<clr-tab-content
id="content1"
*clrIfActive="index === 0"
class="d-flex clr-justify-content-center w-100"
>
<clr-datagrid>
<ng-container *ngFor="let col of tab.colsToDisplay">
<clr-dg-column>{{ col.colName || col.colKey }}</clr-dg-column>
</ng-container>
<clr-dg-row
(click)="tab.onRowClick ? tab.onRowClick(info) : ''"
class="clickable-row"
*ngFor="let info of tab.meta"
>
<ng-container *ngFor="let col of tab.colsToDisplay">
<clr-dg-cell>{{ info[col.colKey] }}</clr-dg-cell>
</ng-container>
</clr-dg-row>
</clr-datagrid>
</clr-tab-content>
</clr-tab>
@for (tab of tabs; track tab; let index = $index) {
<clr-tab>
<button clrTabLink id="link1">{{ tab.name }}</button>
<clr-tab-content
id="content1"
*clrIfActive="index === 0"
class="d-flex clr-justify-content-center w-100"
>
<clr-datagrid>
@for (col of tab.colsToDisplay; track col) {
<clr-dg-column>{{ col.colName || col.colKey }}</clr-dg-column>
}
@for (info of tab.meta; track info) {
<clr-dg-row
(click)="tab.onRowClick ? tab.onRowClick(info) : ''"
class="clickable-row"
>
@for (col of tab.colsToDisplay; track col) {
<clr-dg-cell>{{ info[col.colKey] }}</clr-dg-cell>
}
</clr-dg-row>
}
</clr-datagrid>
</clr-tab-content>
</clr-tab>
}
</clr-tabs>
</div>
<div class="modal-footer">
@@ -1,164 +1,180 @@
<clr-tree>
<clr-tree-node *ngIf="libraryList" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let library of libraryList">
<clr-tree-node
#libTreeNode
(click)="treeNodeClicked($event, library, libraryList)"
*ngIf="!library['hidden'] && library['inForeground']"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<p
(click)="
lib = library.LIBRARYID;
libraryOnClick(lib || '', library, libTreeNode)
"
class="m-0 cursor-pointer"
>
<clr-icon shape="rack-server"></clr-icon>
{{ library.LIBRARYNAME }}
</p>
<clr-tree-node *ngIf="library['tables']" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables')"
autocomplete="off"
/>
@if (libraryList) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Libraries"
name="input"
[(ngModel)]="librariesSearch"
(keyup)="libraryOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
(click)="
searchTreeInput.value = '';
library['searchString'] = '';
treeOnFilter(library, 'tables.TABLENAME')
"
(click)="librariesSearch = ''; libraryOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
}
</div>
</clr-tree-node>
}
@for (library of libraryList; track library) {
@if (!library['hidden'] && library['inForeground']) {
<clr-tree-node
(click)="treeNodeClicked($event, libTable, library['tables'])"
*ngFor="let libTable of library['tables']; let index = index"
[(clrExpanded)]="libTable['expanded']"
[clrLoading]="libTable['loadingColumns'] && !libTable.columns"
[class.clr-expanded]="libTable['expanded']"
#libTreeNode
(click)="treeNodeClicked($event, library, libraryList)"
[(clrExpanded)]="library['expanded']"
[clrLoading]="library['loadingTables'] && !library.tables"
[class.clr-expanded]="library['expanded']"
>
<clr-tooltip
*ngVar="
index + 1 >
licenceState.value.tables_in_library_limit as tableLocked
<p
(click)="
lib = library.LIBRARYID;
libraryOnClick(lib || '', library, libTreeNode)
"
class="m-0 cursor-pointer"
>
<button
clrTooltipTrigger
(click)="
!tableLocked
? tableOnClick(libTable.TABLEURI, libTable, library)
: ''
"
class="clr-treenode-link"
[class.dc-locked-control]="tableLocked"
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
>
<ng-container [ngSwitch]="libTable.includes('-FC')">
<clr-icon *ngSwitchCase="true" shape="bolt"></clr-icon>
<clr-icon *ngSwitchCase="false" shape="table"></clr-icon>
</ng-container>
{{ libTable.replace('-FC', '') }}
</button>
<clr-tooltip-content
clrPosition="bottom-right"
clrSize="lg"
*clrIfOpen
>
<span *ngIf="tableLocked">
To unlock all tables, contact support&#64;datacontroller.io
</span>
</clr-tooltip-content>
<ng-container *ngIf="hasColumns">
<clr-tree-node *ngIf="libTable['columns']" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Columns"
name="input"
[(ngModel)]="libTable['searchString']"
(keyup)="treeOnFilter(libTable, 'columns.COLNAME')"
autocomplete="off"
/>
<clr-icon shape="rack-server"></clr-icon>
{{ library.LIBRARYNAME }}
</p>
@if (library['tables']) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Tables"
name="input"
[(ngModel)]="library['searchString']"
(keyup)="treeOnFilter(library, 'tables')"
autocomplete="off"
/>
@if (searchTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchTreeInput.value.length > 0) {
<clr-icon
*ngIf="searchTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchTreeInput.value.length > 0"
(click)="
searchTreeInput.value = '';
libTable['searchString'] = '';
treeOnFilter(libTable, 'columns.COLNAME')
library['searchString'] = '';
treeOnFilter(library, 'tables.TABLENAME')
"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<clr-tree-node *ngFor="let libColumn of libTable['columns']">
}
</div>
</clr-tree-node>
}
@for (
libTable of library['tables'];
track libTable;
let index = $index
) {
<clr-tree-node
(click)="treeNodeClicked($event, libTable, library['tables'])"
[(clrExpanded)]="libTable['expanded']"
[clrLoading]="libTable['loadingColumns'] && !libTable.columns"
[class.clr-expanded]="libTable['expanded']"
>
<clr-tooltip
*ngVar="
index + 1 >
licenceState.value.tables_in_library_limit as tableLocked
"
>
<button
(click)="columnOnClick(libColumn, library, libTable)"
clrTooltipTrigger
(click)="
!tableLocked
? tableOnClick(libTable.TABLEURI, libTable, library)
: ''
"
class="clr-treenode-link"
[class.column-active]="libColumnActive(libColumn.COLURI)"
[class.dc-locked-control]="tableLocked"
[class.active]="libTabActive(library.LIBRARYREF, libTable)"
>
<clr-icon shape="objects"></clr-icon>
{{ libColumn.COLNAME }}
@switch (libTable.includes('-FC')) {
@case (true) {
<clr-icon shape="bolt"></clr-icon>
}
@case (false) {
<clr-icon shape="table"></clr-icon>
}
}
{{ libTable.replace('-FC', '') }}
</button>
</clr-tree-node>
</ng-container>
</clr-tooltip>
<clr-tooltip-content
clrPosition="bottom-right"
clrSize="lg"
*clrIfOpen
>
@if (tableLocked) {
<span>
To unlock all tables, contact support&#64;datacontroller.io
</span>
}
</clr-tooltip-content>
@if (hasColumns) {
@if (libTable['columns']) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchTreeInput
placeholder="Columns"
name="input"
[(ngModel)]="libTable['searchString']"
(keyup)="treeOnFilter(libTable, 'columns.COLNAME')"
autocomplete="off"
/>
@if (searchTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchTreeInput.value.length > 0) {
<clr-icon
(click)="
searchTreeInput.value = '';
libTable['searchString'] = '';
treeOnFilter(libTable, 'columns.COLNAME')
"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
}
@for (libColumn of libTable['columns']; track libColumn) {
<clr-tree-node>
<button
(click)="columnOnClick(libColumn, library, libTable)"
class="clr-treenode-link"
[class.column-active]="libColumnActive(libColumn.COLURI)"
>
<clr-icon shape="objects"></clr-icon>
{{ libColumn.COLNAME }}
</button>
</clr-tree-node>
}
}
</clr-tooltip>
</clr-tree-node>
}
</clr-tree-node>
</clr-tree-node>
</ng-container>
}
}
</clr-tree>
<div *ngIf="librariesPaging" class="w-100 text-center">
<span class="spinner spinner-sm"> Loading... </span>
</div>
@if (librariesPaging) {
<div class="w-100 text-center">
<span class="spinner spinner-sm"> Loading... </span>
</div>
}
@@ -1,44 +1,49 @@
<div *ngIf="options$ | async as options" class="excel-password-root">
<clr-modal
[clrModalOpen]="options.open"
[clrModalSize]="'md'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">
Password Protected File
</h3>
<div class="modal-body d-flex clr-justify-content-center">
<p class="m-0">Please enter password:</p>
<input
#filePasswordInput
[(ngModel)]="passwordInput"
data-lpignore="true"
autocomplete="off"
id="filePasswordInput"
type="text"
class="clr-input disable-password-manager"
/>
</div>
<div class="modal-footer">
<div>
<p *ngIf="options.error" class="m-0 color-red">
Sorry that didn't work, try again.
</p>
@if (options$ | async; as options) {
<div class="excel-password-root">
<clr-modal
[clrModalOpen]="options.open"
[clrModalSize]="'md'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">
Password Protected File
</h3>
<div class="modal-body d-flex clr-justify-content-center">
<p class="m-0">Please enter password:</p>
<input
#filePasswordInput
[(ngModel)]="passwordInput"
data-lpignore="true"
autocomplete="off"
id="filePasswordInput"
type="text"
class="clr-input disable-password-manager"
/>
</div>
<div class="buttons">
<button type="button" class="btn btn-sm btn-outline" (click)="close()">
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
[disabled]="filePasswordInput.value.length < 1"
(click)="close(filePasswordInput.value)"
>
Unlock
</button>
<div class="modal-footer">
<div>
@if (options.error) {
<p class="m-0 color-red">Sorry that didn't work, try again.</p>
}
</div>
<div class="buttons">
<button
type="button"
class="btn btn-sm btn-outline"
(click)="close()"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
[disabled]="filePasswordInput.value.length < 1"
(click)="close(filePasswordInput.value)"
>
Unlock
</button>
</div>
</div>
</div>
</clr-modal>
</div>
</clr-modal>
</div>
}
@@ -29,17 +29,21 @@
></clr-icon>
<clr-signpost-content [clrPosition]="'bottom-middle'" *clrIfOpen>
<p *ngFor="let request of requests">
<code>{{ request.program }}</code>
&nbsp;
<span class="running" *ngIf="request.running">loading</span>
<span class="success" *ngIf="!request.running && request.successful"
>loaded</span
>
<span class="fail" *ngIf="!request.running && !request.successful"
>failed</span
>
</p>
@for (request of requests; track request) {
<p>
<code>{{ request.program }}</code>
&nbsp;
@if (request.running) {
<span class="running">loading</span>
}
@if (!request.running && request.successful) {
<span class="success">loaded</span>
}
@if (!request.running && !request.successful) {
<span class="fail">failed</span>
}
</p>
}
</clr-signpost-content>
</clr-signpost>
</div>
@@ -9,219 +9,252 @@
<p class="mt-0 d-inline-block">App Location: {{ sasjsConfig.appLoc }}</p>
</h3>
<div class="modal-body h-70vh">
<p *ngIf="sasjsRequests?.length! < 1" class="no-reqs">No requests made</p>
@if (sasjsRequests?.length! < 1) {
<p class="no-reqs">No requests made</p>
}
<clr-stack-view *ngIf="opened && sasjsRequests?.length! > 0">
<clr-stack-block
*ngFor="let programLog of sasjsRequests; let i = index"
[id]="'request_' + i"
[clrStackViewLevel]="1"
>
<clr-stack-label>
{{ programLog.serviceLink }}
<span class="ml-10">
<span *ngIf="programLog.logWarnings?.length! > 0">
<clr-icon
class="mb-5 color-green"
shape="exclamation-circle"
></clr-icon>
</span>
<span *ngIf="programLog.logErrors?.length! > 0">
<clr-icon
class="mb-5 color-red"
shape="exclamation-triangle"
></clr-icon>
</span>
</span>
</clr-stack-label>
<clr-stack-content>{{ programLog.parsedTimestamp }}</clr-stack-content>
<clr-stack-block class="content">
<clr-stack-content>
<clr-tabs>
<clr-tab>
<button clrTabLink>SAS Log</button>
<ng-template [(clrIfActive)]="sasLogActive">
<clr-tab-content>
<clr-tree>
<clr-tree-node
*ngIf="programLog.logErrors"
class="err-links"
>
<clr-icon
class="color-red"
shape="exclamation-triangle"
></clr-icon>
Errors ({{ programLog.logErrors.length! }})
<clr-tree-node
(click)="goToLogLine(err, 'request_' + i, 'error')"
*ngFor="let err of programLog.logErrors || []"
>
<button class="clr-treenode-link">
{{ err }}
</button>
</clr-tree-node>
</clr-tree-node>
<clr-tree-node
*ngIf="programLog.logWarnings"
class="err-links"
>
<clr-icon
class="color-green"
shape="exclamation-circle"
></clr-icon>
Warnings ({{ programLog.logWarnings.length! }})
<clr-tree-node
(click)="
goToLogLine(warning, 'request_' + i, 'warning')
"
*ngFor="let warning of programLog.logWarnings || []"
>
<button class="clr-treenode-link">
{{ warning }}
</button>
</clr-tree-node>
</clr-tree-node>
</clr-tree>
<div
*ngIf="serverType !== 'SASVIYA'"
class="log-wrapper saslog"
[innerHTML]="programLog.logFile"
></div>
<div
*ngIf="serverType === 'SASVIYA'"
class="log-wrapper saslog viya"
[textContent]="programLog.logFile"
></div>
<button
*ngIf="programLog.logFile?.length! > 0"
(click)="downloadLog(programLog.logFile)"
type="button"
class="btn btn-link"
>
Download
</button>
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab>
<button clrTabLink>SAS Source Code</button>
<ng-template [(clrIfActive)]="sasSourceCodeActive">
<clr-tab-content>
<div class="log-wrapper">{{ programLog.sourceCode }}</div>
<button
*ngIf="programLog.sourceCode?.length! > 0"
(click)="downloadSourceCode(programLog.sourceCode)"
type="button"
class="btn btn-link"
>
Download
</button>
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab>
<button clrTabLink>SAS Generated Code</button>
<ng-template [(clrIfActive)]="sasGeneratedCodeActive">
<clr-tab-content>
<div class="log-wrapper">
{{ programLog.generatedCode }}
</div>
<button
*ngIf="programLog.generatedCode?.length! > 0"
(click)="downloadGeneratedCode(programLog.generatedCode)"
type="button"
class="btn btn-link"
>
Download
</button>
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab *ngIf="programLog.SASWORK">
<clr-dropdown
[clrCloseMenuOnItemClick]="true"
class="work-tables-dropdown"
clrTabLink
>
<button clrDropdownTrigger>
SAS Work Tables
<clr-icon shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<div
*ngFor="let table of programLog.SASWORK | keyvalue"
(click)="programLog.selectedTable = $any(table).key"
[class.selected]="programLog.selectedTable === table.key"
class="dropdown-item"
clrDropdownItem
>
{{ table.key }}
</div>
</clr-dropdown-menu>
</clr-dropdown>
<ng-template [(clrIfActive)]="tablesActive">
<clr-tab-content>
<p *ngIf="programLog.selectedTable" class="mt-10">
<strong
>{{ programLog.selectedTable }} (first 10 rows)
</strong>
</p>
<div class="min-h-50vh">
<table
*ngIf="programLog.selectedTable"
class="table mt-10"
>
<thead>
<tr>
<th
*ngFor="
let column of programLog.SASWORK[
programLog.selectedTable
].first10rows[0] | keyvalue
"
>
{{ column.key }}
</th>
</tr>
</thead>
<tbody>
<tr
*ngFor="
let row of programLog.SASWORK[
programLog.selectedTable
].first10rows
"
@if (opened && sasjsRequests?.length! > 0) {
<clr-stack-view>
@for (programLog of sasjsRequests; track programLog; let i = $index) {
<clr-stack-block [id]="'request_' + i" [clrStackViewLevel]="1">
<clr-stack-label>
{{ programLog.serviceLink }}
<span class="ml-10">
@if (programLog.logWarnings?.length! > 0) {
<span>
<clr-icon
class="mb-5 color-green"
shape="exclamation-circle"
></clr-icon>
</span>
}
@if (programLog.logErrors?.length! > 0) {
<span>
<clr-icon
class="mb-5 color-red"
shape="exclamation-triangle"
></clr-icon>
</span>
}
</span>
</clr-stack-label>
<clr-stack-content>{{
programLog.parsedTimestamp
}}</clr-stack-content>
<clr-stack-block class="content">
<clr-stack-content>
<clr-tabs>
<clr-tab>
<button clrTabLink>SAS Log</button>
<ng-template [(clrIfActive)]="sasLogActive">
<clr-tab-content>
<clr-tree>
@if (programLog.logErrors) {
<clr-tree-node class="err-links">
<clr-icon
class="color-red"
shape="exclamation-triangle"
></clr-icon>
Errors ({{ programLog.logErrors.length! }})
@for (
err of programLog.logErrors || [];
track err
) {
<clr-tree-node
(click)="
goToLogLine(err, 'request_' + i, 'error')
"
>
<button class="clr-treenode-link">
{{ err }}
</button>
</clr-tree-node>
}
</clr-tree-node>
}
@if (programLog.logWarnings) {
<clr-tree-node class="err-links">
<clr-icon
class="color-green"
shape="exclamation-circle"
></clr-icon>
Warnings ({{ programLog.logWarnings.length! }})
@for (
warning of programLog.logWarnings || [];
track warning
) {
<clr-tree-node
(click)="
goToLogLine(
warning,
'request_' + i,
'warning'
)
"
>
<button class="clr-treenode-link">
{{ warning }}
</button>
</clr-tree-node>
}
</clr-tree-node>
}
</clr-tree>
@if (serverType !== 'SASVIYA') {
<div
class="log-wrapper saslog"
[innerHTML]="programLog.logFile"
></div>
}
@if (serverType === 'SASVIYA') {
<div
class="log-wrapper saslog viya"
[textContent]="programLog.logFile"
></div>
}
@if (programLog.logFile?.length! > 0) {
<button
(click)="downloadLog(programLog.logFile)"
type="button"
class="btn btn-link"
>
<td
*ngFor="
let column of programLog.SASWORK[
programLog.selectedTable
].first10rows[0] | keyvalue
Download
</button>
}
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab>
<button clrTabLink>SAS Source Code</button>
<ng-template [(clrIfActive)]="sasSourceCodeActive">
<clr-tab-content>
<div class="log-wrapper">
{{ programLog.sourceCode }}
</div>
@if (programLog.sourceCode?.length! > 0) {
<button
(click)="downloadSourceCode(programLog.sourceCode)"
type="button"
class="btn btn-link"
>
Download
</button>
}
</clr-tab-content>
</ng-template>
</clr-tab>
<clr-tab>
<button clrTabLink>SAS Generated Code</button>
<ng-template [(clrIfActive)]="sasGeneratedCodeActive">
<clr-tab-content>
<div class="log-wrapper">
{{ programLog.generatedCode }}
</div>
@if (programLog.generatedCode?.length! > 0) {
<button
(click)="
downloadGeneratedCode(programLog.generatedCode)
"
type="button"
class="btn btn-link"
>
Download
</button>
}
</clr-tab-content>
</ng-template>
</clr-tab>
@if (programLog.SASWORK) {
<clr-tab>
<clr-dropdown
[clrCloseMenuOnItemClick]="true"
class="work-tables-dropdown"
clrTabLink
>
<button clrDropdownTrigger>
SAS Work Tables
<clr-icon shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
@for (
table of programLog.SASWORK | keyvalue;
track table
) {
<div
(click)="
programLog.selectedTable = $any(table).key
"
[class.selected]="
programLog.selectedTable === table.key
"
class="dropdown-item"
clrDropdownItem
>
{{ row[$any(column).key] }}
</td>
</tr>
</tbody>
</table>
</div>
</clr-tab-content>
</ng-template>
</clr-tab>
</clr-tabs>
</clr-stack-content>
</clr-stack-block>
</clr-stack-block>
</clr-stack-view>
{{ table.key }}
</div>
}
</clr-dropdown-menu>
</clr-dropdown>
<ng-template [(clrIfActive)]="tablesActive">
<clr-tab-content>
@if (programLog.selectedTable) {
<p class="mt-10">
<strong
>{{ programLog.selectedTable }} (first 10 rows)
</strong>
</p>
}
<div class="min-h-50vh">
@if (programLog.selectedTable) {
<table class="table mt-10">
<thead>
<tr>
@for (
column of programLog.SASWORK[
programLog.selectedTable
].first10rows[0] | keyvalue;
track column
) {
<th>
{{ column.key }}
</th>
}
</tr>
</thead>
<tbody>
@for (
row of programLog.SASWORK[
programLog.selectedTable
].first10rows;
track row
) {
<tr>
@for (
column of programLog.SASWORK[
programLog.selectedTable
].first10rows[0] | keyvalue;
track column
) {
<td>
{{ row[$any(column).key] }}
</td>
}
</tr>
}
</tbody>
</table>
}
</div>
</clr-tab-content>
</ng-template>
</clr-tab>
}
</clr-tabs>
</clr-stack-content>
</clr-stack-block>
</clr-stack-block>
}
</clr-stack-view>
}
</div>
</clr-modal>
@@ -6,143 +6,151 @@
[style.max-width.px]="sidebarMaxWidth"
[class.resizing]="resizing"
>
<div
*ngIf="isMainRoute('view')"
class="d-flex justify-content-center sub-dropdown"
>
<clr-dropdown>
<button class="dropdown-toggle btn btn-primary" clrDropdownTrigger>
{{ getSubPage() }}
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a
*ngIf="isMainRoute('view')"
clrVerticalNavLink
(click)="navigateToViewer()"
routerLinkActive="active"
>Data</a
@if (isMainRoute('view')) {
<div class="d-flex justify-content-center sub-dropdown">
<clr-dropdown>
<button class="dropdown-toggle btn btn-primary" clrDropdownTrigger>
{{ getSubPage() }}
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
@if (isMainRoute('view')) {
<a
clrVerticalNavLink
(click)="navigateToViewer()"
routerLinkActive="active"
>Data</a
>
}
@if (isMainRoute('view') && serverType === 'SASVIYA') {
<a
clrVerticalNavLink
routerLink="/view/viya-api-explorer"
routerLinkActive="active"
>API Explorer</a
>
}
@if (isMainRoute('view') && serverType === 'SAS9') {
<a
clrVerticalNavLink
routerLink="/view/lineage"
routerLinkActive="active"
>Lineage</a
>
}
@if (isMainRoute('view') && serverType === 'SAS9') {
<a
clrVerticalNavLink
routerLink="/view/metadata"
routerLinkActive="active"
>Metadata</a
>
}
@if (isMainRoute('view')) {
<a
clrVerticalNavLink
routerLink="/view/usernav"
routerLinkActive="active"
>
Users</a
>
}
@if (isMainRoute('approve')) {
<a
clrVerticalNavLink
routerLink="/review/approve/submitted"
routerLinkActive="active"
>Submitted</a
>
}
@if (isMainRoute('approve')) {
<a
clrVerticalNavLink
routerLink="/review/approve/toapprove"
routerLinkActive="active"
>To Approve</a
>
}
</clr-dropdown-menu>
</clr-dropdown>
</div>
}
@if (isMainRoute('view/usernav')) {
<clr-tabs class="p-10">
<clr-tab>
<button
[routerLink]="['/view/usernav/users']"
class="user-nav-btn"
clrTabLink
>
<a
*ngIf="isMainRoute('view') && serverType === 'SASVIYA'"
clrVerticalNavLink
routerLink="/view/viya-api-explorer"
routerLinkActive="active"
>API Explorer</a
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="user"></clr-icon> Users
</p>
</button>
<clr-tab-content> </clr-tab-content>
</clr-tab>
<clr-tab>
<button
[routerLink]="['/view/usernav/groups']"
class="user-nav-btn"
clrTabLink
>
<a
*ngIf="isMainRoute('view') && serverType === 'SAS9'"
clrVerticalNavLink
routerLink="/view/lineage"
routerLinkActive="active"
>Lineage</a
>
<a
*ngIf="isMainRoute('view') && serverType === 'SAS9'"
clrVerticalNavLink
routerLink="/view/metadata"
routerLinkActive="active"
>Metadata</a
>
<a
*ngIf="isMainRoute('view')"
clrVerticalNavLink
routerLink="/view/usernav"
routerLinkActive="active"
>
Users</a
>
<a
*ngIf="isMainRoute('approve')"
clrVerticalNavLink
routerLink="/review/approve/submitted"
routerLinkActive="active"
>Submitted</a
>
<a
*ngIf="isMainRoute('approve')"
clrVerticalNavLink
routerLink="/review/approve/toapprove"
routerLinkActive="active"
>To Approve</a
>
</clr-dropdown-menu>
</clr-dropdown>
</div>
<clr-tabs class="p-10" *ngIf="isMainRoute('view/usernav')">
<clr-tab>
<button
[routerLink]="['/view/usernav/users']"
class="user-nav-btn"
clrTabLink
>
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="user"></clr-icon> Users
</p>
</button>
<clr-tab-content> </clr-tab-content>
</clr-tab>
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="users"></clr-icon> Groups
</p>
</button>
<clr-tab-content *clrIfActive="isMainRoute('/view/usernav/groups')">
</clr-tab-content>
</clr-tab>
@if (serverType === 'SAS9') {
<clr-tab>
<button
[routerLink]="['/view/usernav/roles']"
class="user-nav-btn"
clrTabLink
>
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="blocks-group"></clr-icon>
Roles
</p>
</button>
<clr-tab-content *clrIfActive="isMainRoute('/view/usernav/roles')">
</clr-tab-content>
</clr-tab>
}
</clr-tabs>
}
<clr-tab>
<button
[routerLink]="['/view/usernav/groups']"
class="user-nav-btn"
clrTabLink
>
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="users"></clr-icon> Groups
</p>
</button>
<clr-tab-content *clrIfActive="isMainRoute('/view/usernav/groups')">
</clr-tab-content>
</clr-tab>
<clr-tab *ngIf="serverType === 'SAS9'">
<button
[routerLink]="['/view/usernav/roles']"
class="user-nav-btn"
clrTabLink
>
<p class="zero-margin">
<clr-icon aria-hidden="true" shape="blocks-group"></clr-icon> Roles
</p>
</button>
<clr-tab-content *clrIfActive="isMainRoute('/view/usernav/roles')">
</clr-tab-content>
</clr-tab>
</clr-tabs>
<div
*ngIf="isMainRoute('home')"
class="d-flex justify-content-center sub-dropdown"
>
<clr-dropdown>
<button class="dropdown-toggle btn btn-primary" clrDropdownTrigger>
{{ getSubPage() }}
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a
clrVerticalNavLink
routerLink="/home/tables"
routerLinkActive="active"
>Tables</a
>
<a
clrVerticalNavLink
routerLink="/home/excel-maps"
routerLinkActive="active"
>Excel Maps</a
>
<a
clrVerticalNavLink
routerLink="/home/multi-load"
routerLinkActive="active"
>Multi Load</a
>
</clr-dropdown-menu>
</clr-dropdown>
</div>
@if (isMainRoute('home')) {
<div class="d-flex justify-content-center sub-dropdown">
<clr-dropdown>
<button class="dropdown-toggle btn btn-primary" clrDropdownTrigger>
{{ getSubPage() }}
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
<clr-dropdown-menu *clrIfOpen>
<a
clrVerticalNavLink
routerLink="/home/tables"
routerLinkActive="active"
>Tables</a
>
<a
clrVerticalNavLink
routerLink="/home/excel-maps"
routerLinkActive="active"
>Excel Maps</a
>
<a
clrVerticalNavLink
routerLink="/home/multi-load"
routerLinkActive="active"
>Multi Load</a
>
</clr-dropdown-menu>
</clr-dropdown>
</div>
}
<div class="nav-divider"></div>
@@ -1,24 +1,25 @@
<label
*ngIf="label"
[class.secondLabelActive]="secondLabel && secondLabel.length > 0"
class="clr-control-label"
>
<span
(click)="onChangeLabel('first')"
[class.value-type-selected]="labelSelected === 'first'"
>{{ label }}</span
@if (label) {
<label
[class.secondLabelActive]="secondLabel && secondLabel.length > 0"
class="clr-control-label"
>
<ng-container *ngIf="secondLabel">
/
<span
(click)="onChangeLabel('second')"
[class.value-type-selected]="labelSelected === 'second'"
>{{ secondLabel }}</span
(click)="onChangeLabel('first')"
[class.value-type-selected]="labelSelected === 'first'"
>{{ label }}</span
>
</ng-container>
</label>
<ng-container [ngSwitch]="type">
<ng-container *ngSwitchCase="'date'">
@if (secondLabel) {
/
<span
(click)="onChangeLabel('second')"
[class.value-type-selected]="labelSelected === 'second'"
>{{ secondLabel }}</span
>
}
</label>
}
@switch (type) {
@case ('date') {
<clr-date-container>
<input
type="date"
@@ -36,8 +37,8 @@
[disabled]="this.disabled"
/>
</clr-date-container>
</ng-container>
<ng-container *ngSwitchCase="'time'">
}
@case ('time') {
<input
type="time"
step="1"
@@ -53,8 +54,8 @@
(mouseout)="onInputMouseOut()"
[disabled]="this.disabled"
/>
</ng-container>
<ng-container *ngSwitchCase="'text'">
}
@case ('text') {
<app-autocomplete
#input
class="input-val"
@@ -71,5 +72,5 @@
>
<ng-content></ng-content>
</app-autocomplete>
</ng-container>
</ng-container>
}
}
@@ -28,9 +28,8 @@
<label>I have read and agree to the terms of above.</label>
</clr-checkbox-wrapper>
<clr-spinner
*ngIf="agreeBoxLocked && agreeClicked"
clrInline="small"
></clr-spinner>
@if (agreeBoxLocked && agreeClicked) {
<clr-spinner clrInline="small"></clr-spinner>
}
</div>
</div>
@@ -1,23 +1,25 @@
<div class="header-actions">
<div class="d-flex clr-align-items-center">
<cds-icon
*ngIf="!darkMode.value"
(click)="toggleDarkMode(true)"
size="md"
class="theme-toggle-icon cursor-pointer"
shape="moon"
inverse="false"
solid="false"
></cds-icon>
<cds-icon
*ngIf="!!darkMode.value"
(click)="toggleDarkMode(false)"
size="md"
class="theme-toggle-icon cursor-pointer"
shape="sun"
inverse="false"
solid="false"
></cds-icon>
@if (!darkMode.value) {
<cds-icon
(click)="toggleDarkMode(true)"
size="md"
class="theme-toggle-icon cursor-pointer"
shape="moon"
inverse="false"
solid="false"
></cds-icon>
}
@if (!!darkMode.value) {
<cds-icon
(click)="toggleDarkMode(false)"
size="md"
class="theme-toggle-icon cursor-pointer"
shape="sun"
inverse="false"
solid="false"
></cds-icon>
}
</div>
<app-loading-indicator></app-loading-indicator>
@@ -25,29 +27,31 @@
<clr-dropdown class="app-nav-dropdown">
<button class="nav-text color-white user-nav-toggle" clrDropdownToggle>
<span>{{ userName }}</span>
<span *ngIf="userName !== 'Not logged in' && isViya"
><img
class="avatar-img"
src="{{ getPictureUrl() }}"
alt="profile picture"
/></span>
<span
class="badge badge-danger"
*ngIf="!sasjsConfig.debug"
[class.hidden]="failedReqs.length === 0"
>{{ failedReqs.length }}</span
>
<span
class="badge badge-info"
*ngIf="sasjsConfig.debug"
[class.hidden]="debugLogs.length === 0"
>{{ debugLogs.length }}</span
>
<clr-icon
*ngIf="!isViya"
aria-hidden="true"
shape="caret down"
></clr-icon>
@if (userName !== 'Not logged in' && isViya) {
<span
><img
class="avatar-img"
src="{{ getPictureUrl() }}"
alt="profile picture"
/></span>
}
@if (!sasjsConfig.debug) {
<span
class="badge badge-danger"
[class.hidden]="failedReqs.length === 0"
>{{ failedReqs.length }}</span
>
}
@if (sasjsConfig.debug) {
<span
class="badge badge-info"
[class.hidden]="debugLogs.length === 0"
>{{ debugLogs.length }}</span
>
}
@if (!isViya) {
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
}
</button>
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
<div #dropdownItemDebug class="debug-switch-item" clrDropdownItem>
@@ -71,7 +75,7 @@
<span>SAS Requests</span>
</a>
<ng-container *ngIf="!isDeployPage">
@if (!isDeployPage) {
<a
target="_blank"
href="https://docs.datacontroller.io"
@@ -79,7 +83,7 @@
>
<span class="dropdown-text">Documentation</span>
</a>
</ng-container>
}
<div class="separator"></div>
<a href="..." routerLink="/system" clrDropdownItem>
@@ -8,13 +8,13 @@
>
<h3 class="modal-title">
Viewboxes
<span
*ngIf="licenceState.value.viewbox_limit !== Infinity"
class="licence-notice"
>To unlock more then {{ licenceState.value.viewbox_limit }}
{{ licenceState.value.viewbox_limit === 1 ? 'viewbox' : 'viewboxes' }},
contact support&#64;datacontroller.io</span
>
@if (licenceState.value.viewbox_limit !== Infinity) {
<span class="licence-notice"
>To unlock more then {{ licenceState.value.viewbox_limit }}
{{ licenceState.value.viewbox_limit === 1 ? 'viewbox' : 'viewboxes' }},
contact support&#64;datacontroller.io</span
>
}
</h3>
<div class="modal-body d-flex">
@@ -23,20 +23,23 @@
<hr class="w-100" />
<div *ngIf="librariesLoading" class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
@if (librariesLoading) {
<div class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
}
<p *ngIf="viewboxLimitReached" class="m-0 viewbox-limit-notice">
Viewbox open limit reached
</p>
@if (viewboxLimitReached) {
<p class="m-0 viewbox-limit-notice">Viewbox open limit reached</p>
}
<dc-tree
*ngIf="viewboxModal"
[libraryList]="libraries"
(libraryOnClickEmitter)="libraryOnClick($event)"
(tableOnClickEmitter)="tableOnClick($event)"
></dc-tree>
@if (viewboxModal) {
<dc-tree
[libraryList]="libraries"
(libraryOnClickEmitter)="libraryOnClick($event)"
(tableOnClickEmitter)="tableOnClick($event)"
></dc-tree>
}
</div>
<div class="currently-open">
@@ -82,78 +85,77 @@
<hr />
<p *ngIf="viewboxes.length < 1" class="text-center w-100 m-0">
Pick table from the list on the left
</p>
@if (viewboxes.length < 1) {
<p class="text-center w-100 m-0">
Pick table from the list on the left
</p>
}
<div
*ngFor="let viewbox of viewboxes"
(click)="selectViewbox(viewbox)"
[class.selected]="selectedViewbox?.id === viewbox.id"
class="open-viewbox d-flex clr-justify-content-between clr-align-items-center"
>
<div class="d-flex clr-align-items-center">
#{{ viewbox.id }} {{ viewbox.library }}.{{ viewbox.table }}
<clr-spinner
*ngIf="viewbox.loadingData"
clrSmall
class="ml-3"
></clr-spinner>
@for (viewbox of viewboxes; track viewbox) {
<div
(click)="selectViewbox(viewbox)"
[class.selected]="selectedViewbox?.id === viewbox.id"
class="open-viewbox d-flex clr-justify-content-between clr-align-items-center"
>
<div class="d-flex clr-align-items-center">
#{{ viewbox.id }} {{ viewbox.library }}.{{ viewbox.table }}
@if (viewbox.loadingData) {
<clr-spinner clrSmall class="ml-3"></clr-spinner>
}
</div>
<div>
<a
(click)="resetPosSize(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left"
[class.disabled]="viewbox.x === 0 && viewbox.y === 0"
>
<clr-icon
aria-hidden="true"
shape="cursor-move"
size="13"
></clr-icon>
<span class="tooltip-content">Reset position and size</span>
</a>
@if (!viewbox.minimized) {
<a
(click)="minimize(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon aria-hidden="true" shape="minus" size="19"></clr-icon>
<span class="tooltip-content">Minimize viewbox</span>
</a>
}
@if (viewbox.minimized) {
<a
(click)="restore(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon
aria-hidden="true"
shape="window-restore"
size="19"
></clr-icon>
<span class="tooltip-content">Restore viewbox</span>
</a>
}
<a
(click)="close(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon aria-hidden="true" shape="times" size="19"></clr-icon>
<span class="tooltip-content">Close viewbox</span>
</a>
</div>
</div>
<div>
<a
(click)="resetPosSize(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left"
[class.disabled]="viewbox.x === 0 && viewbox.y === 0"
>
<clr-icon
aria-hidden="true"
shape="cursor-move"
size="13"
></clr-icon>
<span class="tooltip-content">Reset position and size</span>
</a>
<a
*ngIf="!viewbox.minimized"
(click)="minimize(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon aria-hidden="true" shape="minus" size="19"></clr-icon>
<span class="tooltip-content">Minimize viewbox</span>
</a>
<a
*ngIf="viewbox.minimized"
(click)="restore(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon
aria-hidden="true"
shape="window-restore"
size="19"
></clr-icon>
<span class="tooltip-content">Restore viewbox</span>
</a>
<a
(click)="close(viewbox); stopPropagation($event)"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-md tooltip-bottom-left ml-3"
>
<clr-icon aria-hidden="true" shape="times" size="19"></clr-icon>
<span class="tooltip-content">Close viewbox</span>
</a>
</div>
</div>
}
<div
class="d-flex clr-justify-content-between clr-align-items-center mt-10"
@@ -186,7 +188,7 @@
} as hotColumns"
cdkDropListGroup
>
<ng-container *ngIf="hotColumns.colHeadersHidden">
@if (hotColumns.colHeadersHidden) {
<div class="cols-search">
<!-- <input #columnSearch clrInput (input)="columnSearchChange($event, selectedViewboxTable)" placeholder="Search columns"/> -->
<app-autocomplete
@@ -202,14 +204,16 @@
onColsearchChange(input, $event, hotColumns.colHeadersHidden)
"
>
<option *ngFor="let column of hotColumns.colHeadersHidden">
{{ column }}
</option>
@for (column of hotColumns.colHeadersHidden; track column) {
<option>
{{ column }}
</option>
}
</app-autocomplete>
</div>
</ng-container>
}
<ng-container *ngIf="hotColumns.colHeadersVisible">
@if (hotColumns.colHeadersVisible) {
<p class="mt-0">Columns To Display (drag and re-order)</p>
<div
class="cols-list"
@@ -217,42 +221,41 @@
[cdkDropListData]="hotColumns.colHeadersVisible"
(cdkDropListDropped)="columnsDrop($event)"
>
<div
class="col-box column-{{ column }}"
*ngFor="let column of hotColumns.colHeadersVisible"
[class.cursor-default-i]="hotColumns.headerPks.includes(column)"
[class.primaryKeyHeaderStyle]="
hotColumns.headerPks.includes(column)
"
[cdkDragData]="column"
[cdkDragDisabled]="hotColumns.headerPks.includes(column)"
cdkDrag
>
<span>
{{ column }}
{{
selectedViewboxTable?.hotTable?.$dataformats.vars[column]
.type === 'num'
? '(numeric)'
: ''
}}
<clr-icon
*ngIf="hotColumns.headerPks.includes(column)"
aria-hidden="true"
shape="key"
></clr-icon>
</span>
<clr-icon
*ngIf="!hotColumns.headerPks.includes(column)"
(click)="onColRemove(column)"
aria-label="Remove column"
shape="trash"
class="cursor-pointer"
></clr-icon>
</div>
@for (column of hotColumns.colHeadersVisible; track column) {
<div
class="col-box column-{{ column }}"
[class.cursor-default-i]="hotColumns.headerPks.includes(column)"
[class.primaryKeyHeaderStyle]="
hotColumns.headerPks.includes(column)
"
[cdkDragData]="column"
[cdkDragDisabled]="hotColumns.headerPks.includes(column)"
cdkDrag
>
<span>
{{ column }}
{{
selectedViewboxTable?.hotTable?.$dataformats.vars[column]
.type === 'num'
? '(numeric)'
: ''
}}
@if (hotColumns.headerPks.includes(column)) {
<clr-icon aria-hidden="true" shape="key"></clr-icon>
}
</span>
@if (!hotColumns.headerPks.includes(column)) {
<clr-icon
(click)="onColRemove(column)"
aria-label="Remove column"
shape="trash"
class="cursor-pointer"
></clr-icon>
}
</div>
}
</div>
</ng-container>
}
</div>
</div>
</div>
@@ -268,158 +271,159 @@
</clr-modal>
<div class="viewboxes-container">
<div
*ngFor="let viewbox of viewboxes; let index = index"
(mousedown)="focusViewbox(viewbox)"
[class.focused]="viewbox.focused"
[style.display]="viewbox.minimized ? 'none' : ''"
[style.width.px]="viewbox.width"
[style.height.px]="viewbox.height"
[style.pointer-events]="viewbox.collapsed ? 'none' : ''"
[style.box-shadow]="viewbox.collapsed ? 'none' : ''"
class="viewbox"
cdkDragBoundary=".viewboxes-container"
cdkDrag
[cdkDragFreeDragPosition]="{ x: viewbox.x, y: viewbox.y }"
(cdkDragEnded)="viewboxDragEnded($event, viewbox)"
tabindex="0"
[id]="'viewbox_' + viewbox.id"
#resizeBox
>
@for (viewbox of viewboxes; track viewbox; let index = $index) {
<div
class="drag-handle d-flex clr-justify-content-between clr-align-items-center"
cdkDragHandle
>
<span class="table-title">
#{{ viewbox.id }} {{ viewbox.library }}.{{ viewbox.table }}
</span>
<div class="actions">
<clr-icon
*ngIf="!viewbox.collapsed"
(click)="collapse(viewbox)"
aria-label="Collapse"
shape="angle top"
></clr-icon>
<clr-icon
*ngIf="viewbox.collapsed"
(click)="expand(viewbox)"
aria-label="Expand"
shape="angle down"
></clr-icon>
<clr-icon
(click)="minimize(viewbox)"
aria-label="Minimize"
shape="minus"
></clr-icon>
<clr-icon
(click)="close(viewbox)"
aria-label="Close"
shape="times"
></clr-icon>
</div>
</div>
<span
#dragHandleCorner
[id]="'handle_viewbox_' + viewbox.id"
class="dragHandle corner"
(mousedown)="focusViewbox(viewbox)"
[class.focused]="viewbox.focused"
[style.display]="viewbox.minimized ? 'none' : ''"
[style.width.px]="viewbox.width"
[style.height.px]="viewbox.height"
[style.pointer-events]="viewbox.collapsed ? 'none' : ''"
[style.box-shadow]="viewbox.collapsed ? 'none' : ''"
class="viewbox"
cdkDragBoundary=".viewboxes-container"
cdkDrag
(cdkDragMoved)="dragMove(dragHandleCorner, resizeBox, viewbox, $event)"
></span>
<form
class="d-flex align-items-center clr-justify-content-between clr-flex-wrap table-search-wrapper"
[style.display]="viewbox.collapsed ? 'none' : ''"
clrForm
>
<div class="d-flex align-items-center clr-flex-wrap">
<div class="input-wrapper">
<input
#searchEl
(keyup.enter)="searchTable(searchEl, viewbox)"
clrInput
[type]="viewbox.searchNumeric ? 'number' : 'text'"
placeholder="Search"
name="search-input"
/>
<clr-icon
*ngIf="!viewbox.searchLoading"
(click)="searchTable(searchEl, viewbox)"
aria-label="Search"
shape="search"
></clr-icon>
<span *ngIf="viewbox.searchLoading" class="spinner spinner-inline">
Loading...
</span>
</div>
<clr-checkbox-container>
<clr-checkbox-wrapper>
<input
type="checkbox"
clrCheckbox
[(ngModel)]="viewbox.searchNumeric"
name="numeric_check"
/>
<label>Numeric</label>
</clr-checkbox-wrapper>
</clr-checkbox-container>
</div>
<div class="d-flex clr-align-items-center">
<span *ngIf="viewbox.loadingData" class="spinner spinner-sm"
>Loading...</span
>
<clr-icon
*ngIf="!viewbox.loadingData"
(click)="reloadTableData(viewbox); searchEl.value = ''"
aria-label="Reload data"
shape="refresh"
class="click-icon"
></clr-icon>
<a
(click)="openFilter(viewbox); searchEl.value = ''"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-lg tooltip-bottom-left ml-5"
>
<clr-icon
aria-hidden="true"
shape="filter"
[class.filter-active]="viewbox.filter_pk !== '0'"
class="click-icon"
></clr-icon>
<span class="tooltip-content tooltip-long">{{
viewbox.filterText
}}</span>
</a>
<clr-icon
(click)="openTableEdit(viewbox)"
[class.disabled]="!tableEditExists(viewbox)"
aria-label="Edit table"
shape="pencil"
class="click-icon ml-5"
></clr-icon>
</div>
</form>
<div
class="content"
*ngVar="getViewboxTableIndex(viewbox) as viewboxTableIndex"
[style.display]="viewbox.collapsed ? 'none' : ''"
[cdkDragFreeDragPosition]="{ x: viewbox.x, y: viewbox.y }"
(cdkDragEnded)="viewboxDragEnded($event, viewbox)"
tabindex="0"
[id]="'viewbox_' + viewbox.id"
#resizeBox
>
<div
*ngIf="viewbox.loadingData && viewboxTableIndex < 0"
class="hot-spinner my-10-mx-auto text-center"
class="drag-handle d-flex clr-justify-content-between clr-align-items-center"
cdkDragHandle
>
<clr-spinner clrMedium></clr-spinner>
<span class="table-title">
#{{ viewbox.id }} {{ viewbox.library }}.{{ viewbox.table }}
</span>
<div class="actions">
@if (!viewbox.collapsed) {
<clr-icon
(click)="collapse(viewbox)"
aria-label="Collapse"
shape="angle top"
></clr-icon>
}
@if (viewbox.collapsed) {
<clr-icon
(click)="expand(viewbox)"
aria-label="Expand"
shape="angle down"
></clr-icon>
}
<clr-icon
(click)="minimize(viewbox)"
aria-label="Minimize"
shape="minus"
></clr-icon>
<clr-icon
(click)="close(viewbox)"
aria-label="Close"
shape="times"
></clr-icon>
</div>
</div>
<span
#dragHandleCorner
[id]="'handle_viewbox_' + viewbox.id"
class="dragHandle corner"
cdkDrag
(cdkDragMoved)="dragMove(dragHandleCorner, resizeBox, viewbox, $event)"
></span>
<form
class="d-flex align-items-center clr-justify-content-between clr-flex-wrap table-search-wrapper"
[style.display]="viewbox.collapsed ? 'none' : ''"
clrForm
>
<div class="d-flex align-items-center clr-flex-wrap">
<div class="input-wrapper">
<input
#searchEl
(keyup.enter)="searchTable(searchEl, viewbox)"
clrInput
[type]="viewbox.searchNumeric ? 'number' : 'text'"
placeholder="Search"
name="search-input"
/>
@if (!viewbox.searchLoading) {
<clr-icon
(click)="searchTable(searchEl, viewbox)"
aria-label="Search"
shape="search"
></clr-icon>
}
@if (viewbox.searchLoading) {
<span class="spinner spinner-inline"> Loading... </span>
}
</div>
<clr-checkbox-container>
<clr-checkbox-wrapper>
<input
type="checkbox"
clrCheckbox
[(ngModel)]="viewbox.searchNumeric"
name="numeric_check"
/>
<label>Numeric</label>
</clr-checkbox-wrapper>
</clr-checkbox-container>
</div>
<div class="d-flex clr-align-items-center">
@if (viewbox.loadingData) {
<span class="spinner spinner-sm">Loading...</span>
}
@if (!viewbox.loadingData) {
<clr-icon
(click)="reloadTableData(viewbox); searchEl.value = ''"
aria-label="Reload data"
shape="refresh"
class="click-icon"
></clr-icon>
}
<a
(click)="openFilter(viewbox); searchEl.value = ''"
role="tooltip"
aria-haspopup="true"
class="tooltip tooltip-lg tooltip-bottom-left ml-5"
>
<clr-icon
aria-hidden="true"
shape="filter"
[class.filter-active]="viewbox.filter_pk !== '0'"
class="click-icon"
></clr-icon>
<span class="tooltip-content tooltip-long">{{
viewbox.filterText
}}</span>
</a>
<clr-icon
(click)="openTableEdit(viewbox)"
[class.disabled]="!tableEditExists(viewbox)"
aria-label="Edit table"
shape="pencil"
class="click-icon ml-5"
></clr-icon>
</div>
</form>
<div
class="content"
*ngVar="getViewboxTableIndex(viewbox) as viewboxTableIndex"
[style.display]="viewbox.collapsed ? 'none' : ''"
>
@if (viewbox.loadingData && viewboxTableIndex < 0) {
<div class="hot-spinner my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
}
@if (viewboxTableIndex > -1 && viewboxHotSettings.get(viewbox.id)) {
<hot-table
[settings]="viewboxHotSettings.get(viewbox.id) || {}"
[id]="'hotTable_' + viewbox.id"
></hot-table>
}
</div>
<hot-table
*ngIf="viewboxTableIndex > -1 && viewboxHotSettings.get(viewbox.id)"
[settings]="viewboxHotSettings.get(viewbox.id) || {}"
[id]="'hotTable_' + viewbox.id"
></hot-table>
</div>
</div>
}
</div>
<clr-modal
@@ -433,11 +437,9 @@
<span> #{{ filteringViewbox?.id }} {{ filterLibds }} </span>
</h3>
<div class="modal-body">
<app-query
#queryFilter
*ngIf="filter"
[viewboxId]="filteringViewbox?.id"
></app-query>
@if (filter) {
<app-query #queryFilter [viewboxId]="filteringViewbox?.id"></app-query>
}
</div>
<div class="modal-footer">
<button (click)="resetFilter()" type="button" class="btn btn-sm btn-link">
+130 -124
View File
@@ -1,140 +1,146 @@
<main class="content-area">
<div class="clr-col-md-12 card">
<div
*ngIf="!loaded"
class="h-80vh d-flex justify-content-center flex-column align-items-center"
>
<span class="spinner" *ngIf="!loaded"> Loading... </span>
<div *ngIf="!loaded">
<h3>Loading submitted table</h3>
@if (!loaded) {
<div
class="h-80vh d-flex justify-content-center flex-column align-items-center"
>
@if (!loaded) {
<span class="spinner"> Loading... </span>
}
@if (!loaded) {
<div>
<h3>Loading submitted table</h3>
</div>
}
</div>
</div>
<div *ngIf="loaded">
<div class="card-header" ng-if="loaded">
<div class="card-title text-center">
<h4 class="mt-0">Staged Data</h4>
</div>
</div>
<div class="clr-row" ng-if="loaded">
<div class="card-block clr-col-md-6 no-borders">
<div class="card-header py-5">
<div class="card-title text-center">Basic Submitted Details</div>
</div>
<div>
<span>Table Id:</span>
<strong>
<span> {{ tableDetails?.TABLE_ID }}</span>
</strong>
</div>
<div>
<span>Base Table:</span>
<a (click)="viewerTableScreen()" class="baseTableLink">
{{ tableDetails?.BASE_TABLE }}
</a>
</div>
<div>
<span>Submit Date:</span>
<span class="mt-10">
{{ tableDetails?.SUBMITTED_ON_DTTM }}
</span>
</div>
<div>
<span>Review Status:</span>
<span
class="mt-10"
[ngClass]="{
rejected: tableDetails?.REVIEW_STATUS_ID === 'REJECTED',
accepted: tableDetails?.REVIEW_STATUS_ID === 'APPROVED'
}"
[attr.aria-label]="
'Review status: ' + tableDetails?.REVIEW_STATUS_ID
"
role="status"
>
{{ tableDetails?.REVIEW_STATUS_ID }}
</span>
}
@if (loaded) {
<div>
<div class="card-header" ng-if="loaded">
<div class="card-title text-center">
<h4 class="mt-0">Staged Data</h4>
</div>
</div>
<div class="clr-col-md-6 card-block no-borders">
<div class="card-header py-5">
<div class="card-title text-center">Actions</div>
</div>
<div class="mt-20">
<div class="row">
<button
class="btn btn-sm btn-outline text-center mr-5i"
(click)="viewerTableScreen()"
[disabled]="revertingChanges"
aria-label="View base table"
>
View base table
</button>
<button
*ngIf="!(tableDetails?.['ALLOW_RESTORE'] === 'YES')"
id="approval-btn"
class="btn btn-sm btn-success-outline text-center mr-5i"
[disabled]="
tableDetails?.REVIEW_STATUS_ID === 'APPROVED' ||
tableDetails?.REVIEW_STATUS_ID === 'REJECTED'
<div class="clr-row" ng-if="loaded">
<div class="card-block clr-col-md-6 no-borders">
<div class="card-header py-5">
<div class="card-title text-center">Basic Submitted Details</div>
</div>
<div>
<span>Table Id:</span>
<strong>
<span> {{ tableDetails?.TABLE_ID }}</span>
</strong>
</div>
<div>
<span>Base Table:</span>
<a (click)="viewerTableScreen()" class="baseTableLink">
{{ tableDetails?.BASE_TABLE }}
</a>
</div>
<div>
<span>Submit Date:</span>
<span class="mt-10">
{{ tableDetails?.SUBMITTED_ON_DTTM }}
</span>
</div>
<div>
<span>Review Status:</span>
<span
class="mt-10"
[ngClass]="{
rejected: tableDetails?.REVIEW_STATUS_ID === 'REJECTED',
accepted: tableDetails?.REVIEW_STATUS_ID === 'APPROVED'
}"
[attr.aria-label]="
'Review status: ' + tableDetails?.REVIEW_STATUS_ID
"
(click)="approveTableScreen()"
[disabled]="revertingChanges"
aria-label="Approve table"
role="status"
>
Approve
</button>
<button
class="btn btn-sm btn-info-outline text-center mr-5i"
(click)="goBack()"
[disabled]="revertingChanges"
aria-label="Edit base table"
>
Edit base table
</button>
<button
class="btn btn-sm btn-success text-center mr-5i min-w-0"
(click)="download(tableDetails?.TABLE_ID)"
aria-label="Download audit file"
>
<clr-icon shape="download" aria-hidden="true"></clr-icon>
</button>
<clr-tooltip>
{{ tableDetails?.REVIEW_STATUS_ID }}
</span>
</div>
</div>
<div class="clr-col-md-6 card-block no-borders">
<div class="card-header py-5">
<div class="card-title text-center">Actions</div>
</div>
<div class="mt-20">
<div class="row">
<button
*ngIf="tableDetails?.['ALLOW_RESTORE'] === 'YES'"
(click)="revertChanges()"
clrTooltipTrigger
[clrLoading]="revertingChanges"
class="btn btn-sm btn-danger text-center mt-20"
aria-label="Revert this and all subsequent changes"
class="btn btn-sm btn-outline text-center mr-5i"
(click)="viewerTableScreen()"
[disabled]="revertingChanges"
aria-label="View base table"
>
REVERT
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
<span> Revert this and all subsequent changes </span>
</clr-tooltip-content>
View base table
</button>
</clr-tooltip>
@if (!(tableDetails?.['ALLOW_RESTORE'] === 'YES')) {
<button
id="approval-btn"
class="btn btn-sm btn-success-outline text-center mr-5i"
[disabled]="
tableDetails?.REVIEW_STATUS_ID === 'APPROVED' ||
tableDetails?.REVIEW_STATUS_ID === 'REJECTED'
"
(click)="approveTableScreen()"
[disabled]="revertingChanges"
aria-label="Approve table"
>
Approve
</button>
}
<button
class="btn btn-sm btn-info-outline text-center mr-5i"
(click)="goBack()"
[disabled]="revertingChanges"
aria-label="Edit base table"
>
Edit base table
</button>
<button
class="btn btn-sm btn-success text-center mr-5i min-w-0"
(click)="download(tableDetails?.TABLE_ID)"
aria-label="Download audit file"
>
<clr-icon shape="download" aria-hidden="true"></clr-icon>
</button>
<clr-tooltip>
@if (tableDetails?.['ALLOW_RESTORE'] === 'YES') {
<button
(click)="revertChanges()"
clrTooltipTrigger
[clrLoading]="revertingChanges"
class="btn btn-sm btn-danger text-center mt-20"
aria-label="Revert this and all subsequent changes"
>
REVERT
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
<span> Revert this and all subsequent changes </span>
</clr-tooltip-content>
</button>
}
</clr-tooltip>
</div>
</div>
</div>
</div>
<div class="card-block">
<hot-table
id="hotTable"
[data]="hotTable.data"
[settings]="hotTableSettings()"
aria-label="Staged data table"
>
<!--[licenseKey]=null-->
</hot-table>
</div>
<div class="card-footer d-flex justify-content-center"></div>
</div>
<div class="card-block">
<hot-table
id="hotTable"
[data]="hotTable.data"
[settings]="hotTableSettings()"
aria-label="Staged data table"
>
<!--[licenseKey]=null-->
</hot-table>
</div>
<div class="card-footer d-flex justify-content-center"></div>
</div>
}
</div>
</main>
+80 -89
View File
@@ -104,71 +104,68 @@
Free Tier:
<span class="dark">{{ licenceInfo?.demo ? 'YES' : 'NO' }}</span>
</p>
<p
*ngIf="licenceState.value.viewer_rows_allowed !== Infinity"
class="m-0"
>
Viewer rows limit:
<span class="dark">{{
licenceState.value.viewer_rows_allowed
}}</span>
</p>
<p
*ngIf="licenceState.value.editor_rows_allowed !== Infinity"
class="m-0"
>
Editor rows limit:
<span class="dark">{{
licenceState.value.editor_rows_allowed
}}</span>
</p>
<p
*ngIf="licenceState.value.stage_rows_allowed !== Infinity"
class="m-0"
>
Stage rows limit:
<span class="dark">{{
licenceState.value.stage_rows_allowed
}}</span>
</p>
<p
*ngIf="licenceState.value.history_rows_allowed !== Infinity"
class="m-0"
>
History rows limit:
<span class="dark">{{
licenceState.value.history_rows_allowed
}}</span>
</p>
<p
*ngIf="licenceState.value.submit_rows_limit !== Infinity"
class="m-0"
>
Submit rows limit:
<span class="dark">{{ licenceState.value.submit_rows_limit }}</span>
</p>
<p
*ngIf="licenceState.value.tables_in_library_limit !== Infinity"
class="m-0"
>
Tables in library limit:
<span class="dark">{{
licenceState.value.tables_in_library_limit
}}</span>
</p>
<p *ngIf="licenceState.value.viewbox_limit !== Infinity" class="m-0">
Viewboxes limit:
<span class="dark">{{ licenceState.value.viewbox_limit }}</span>
</p>
<p
*ngIf="licenceState.value.lineage_daily_limit !== Infinity"
class="m-0"
>
Lineage daily limit:
<span class="dark">{{
licenceState.value.lineage_daily_limit
}}</span>
</p>
@if (licenceState.value.viewer_rows_allowed !== Infinity) {
<p class="m-0">
Viewer rows limit:
<span class="dark">{{
licenceState.value.viewer_rows_allowed
}}</span>
</p>
}
@if (licenceState.value.editor_rows_allowed !== Infinity) {
<p class="m-0">
Editor rows limit:
<span class="dark">{{
licenceState.value.editor_rows_allowed
}}</span>
</p>
}
@if (licenceState.value.stage_rows_allowed !== Infinity) {
<p class="m-0">
Stage rows limit:
<span class="dark">{{
licenceState.value.stage_rows_allowed
}}</span>
</p>
}
@if (licenceState.value.history_rows_allowed !== Infinity) {
<p class="m-0">
History rows limit:
<span class="dark">{{
licenceState.value.history_rows_allowed
}}</span>
</p>
}
@if (licenceState.value.submit_rows_limit !== Infinity) {
<p class="m-0">
Submit rows limit:
<span class="dark">{{
licenceState.value.submit_rows_limit
}}</span>
</p>
}
@if (licenceState.value.tables_in_library_limit !== Infinity) {
<p class="m-0">
Tables in library limit:
<span class="dark">{{
licenceState.value.tables_in_library_limit
}}</span>
</p>
}
@if (licenceState.value.viewbox_limit !== Infinity) {
<p class="m-0">
Viewboxes limit:
<span class="dark">{{ licenceState.value.viewbox_limit }}</span>
</p>
}
@if (licenceState.value.lineage_daily_limit !== Infinity) {
<p class="m-0">
Lineage daily limit:
<span class="dark">{{
licenceState.value.lineage_daily_limit
}}</span>
</p>
}
<p cds-text="label" class="m-0">
Viewboxes:
<span class="dark">{{
@@ -201,29 +198,28 @@
<!-- Keep the logic in case we in future have more then 2 themes (light, dark) -->
<!-- -->
<!-- <div class="user-action">
Keep selected theme after reload
Keep selected theme after reload
<clr-checkbox-wrapper>
<clr-checkbox-wrapper>
<input [(ngModel)]="settings.persistSelectedTheme" (change)="settingChange($event)" value="persistDarkMode" type="checkbox" clrToggle />
</clr-checkbox-wrapper>
</div> -->
<ng-container *ngIf="environmentInfo?.ISADMIN === 1">
<div *ngIf="serverType === 'SAS9'" class="admin-action">
Refresh Data Lineage
<button
(click)="refreshDataLineage()"
[clrLoading]="refreshingDataLineage"
class="btn btn-info btn-sm"
>
REFRESH
</button>
</div>
</clr-checkbox-wrapper>
</div> -->
@if (environmentInfo?.ISADMIN === 1) {
@if (serverType === 'SAS9') {
<div class="admin-action">
Refresh Data Lineage
<button
(click)="refreshDataLineage()"
[clrLoading]="refreshingDataLineage"
class="btn btn-info btn-sm"
>
REFRESH
</button>
</div>
}
<div class="admin-action">
Refresh Data Catalog
<button
(click)="refreshDataCatalog()"
[clrLoading]="refreshingDataCatalog"
@@ -232,10 +228,8 @@
REFRESH
</button>
</div>
<div class="admin-action">
Download Configuration
<div class="libref-group">
<clr-tooltip class="libref-tooltip">
<label clrTooltipTrigger class="libref-label">
@@ -251,7 +245,6 @@
contain this, instead of the original.
</clr-tooltip-content>
</clr-tooltip>
<input
type="text"
class="clr-input libref-input"
@@ -269,15 +262,13 @@
DOWNLOAD
</button>
</div>
<div class="admin-action">
Update Licence Key
<button routerLink="/licensing/update" class="btn btn-info btn-sm">
UPDATE
</button>
</div>
</ng-container>
}
</div>
</div>
</div>
+238 -191
View File
@@ -1,210 +1,257 @@
<app-sidebar class="sidebar-height">
<clr-tree>
<clr-tree-node *ngIf="users" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Users"
name="input"
[(ngModel)]="userSearch"
(keyup)="userListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLibTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLibTreeInput.value.length > 0"
(click)="userSearch = ''; userListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let user of users">
<clr-tree-node
(click)="userOnClick(user)"
*ngIf="!user['hidden']"
[class.active]="userInfo?.URI === user.URI"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="user"></clr-icon>
{{ user.NAME }}
</p>
@if (users) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLibTreeInput
placeholder="Filter by Users"
name="input"
[(ngModel)]="userSearch"
(keyup)="userListOnFilter()"
autocomplete="off"
/>
@if (searchLibTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLibTreeInput.value.length > 0) {
<clr-icon
(click)="userSearch = ''; userListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (user of users; track user) {
@if (!user['hidden']) {
<clr-tree-node
(click)="userOnClick(user)"
[class.active]="userInfo?.URI === user.URI"
>
<p class="m-0 cursor-pointer list-padding">
<clr-icon shape="user"></clr-icon>
{{ user.NAME }}
</p>
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
<div class="content-area">
<div *ngIf="loading" class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
<div *ngIf="userData && !loading">
<div *ngIf="serverType !== ServerType.SasViya" class="clr-row">
<div class="clr-col-8">
<table class="table user-info">
<tbody>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.NAME }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.DISPLAYNAME }}</b>
</p>
</td>
</tr>
<ng-container *ngIf="serverType === ServerType.Sas9">
<tr>
<td class="left">
Metadata Created: <b>{{ userInfo.METADATACREATED }}</b>
</td>
</tr>
<tr>
<td class="left">
Metadata Updated: <b>{{ userInfo.METADATAUPDATED }}</b>
</td>
</tr>
</ng-container>
</tbody>
</table>
</div>
@if (loading) {
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
</div>
}
<div *ngIf="serverType === ServerType.SasViya && userInfo" class="clr-row">
<div class="clr-col-8">
<table class="table user-info">
<tbody>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.NAME }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
User ID: <b>{{ userInfo.URI }}</b>
</td>
</tr>
<tr>
<td class="left">
User provider ID: <b>{{ userInfo.PROVIDER }}</b>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="clr-row">
<div class="clr-col-8">
<div class="card user-data">
<div *ngIf="serverType === ServerType.Sas9">
<h3>EMAILS ({{ userEmailsCount || '0' }})</h3>
<h5 *ngIf="userEmailsCount == 0">No Emails Present</h5>
<div class="table-container">
<table *ngIf="userEmailsCount != 0" class="table user-table">
<thead>
@if (userData && !loading) {
<div>
@if (serverType !== ServerType.SasViya) {
<div class="clr-row">
<div class="clr-col-8">
<table class="table user-info">
<tbody>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.NAME }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.DISPLAYNAME }}</b>
</p>
</td>
</tr>
@if (serverType === ServerType.Sas9) {
<tr>
<td class="width-50"><b>EMAIL</b></td>
<td class=""><b>TYPE</b></td>
</tr>
</thead>
<tbody>
<tr *ngFor="let email of userEmails">
<td class="">{{ email.EMAIL }}</td>
<td class="">{{ email.TYPE }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr *ngIf="serverType === ServerType.Sas9" />
<div>
<h3>GROUPS ({{ userGroupsCount || '0' }})</h3>
<h5 *ngIf="userGroupsCount == 0">No Groups Present</h5>
<div class="table-container">
<table *ngIf="userGroupsCount != 0" class="table user-table">
<tbody>
<tr *ngFor="let group of userGroups">
<td
*ngIf="serverType === ServerType.SasViya"
[routerLink]="'/view/usernav/groups/' + group.URI"
routerLinkActive="active"
class=""
>
{{ group.GROUPNAME }}
</td>
<ng-container *ngIf="serverType !== ServerType.SasViya">
<td
[routerLink]="
'/view/usernav/groups/' +
(group.GROUPURI || group.URI)
"
routerLinkActive="active"
class=""
>
{{ group.GROUPNAME }}
</td>
</ng-container>
</tr>
</tbody>
</table>
</div>
</div>
<hr *ngIf="serverType === ServerType.Sas9" />
<div *ngIf="serverType === ServerType.Sas9">
<h3>ROLES ({{ userRolesCount || '0' }})</h3>
<h5 *ngIf="userRolesCount == 0">No Roles Present</h5>
<div class="table-container">
<table *ngIf="userRolesCount != 0" class="table user-table">
<tbody>
<tr *ngFor="let role of userRoles">
<td
[routerLink]="
'/view/usernav/roles/' + getRoleURI(role.ROLEURI)
"
>
{{ role.ROLENAME }}
<td class="left">
Metadata Created: <b>{{ userInfo.METADATACREATED }}</b>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<hr *ngIf="serverType === ServerType.Sas9" />
<div *ngIf="serverType === ServerType.Sas9">
<h3>LOGINS ({{ userLoginsCount || '0' }})</h3>
<h5 *ngIf="userLoginsCount == 0">No Logins Present</h5>
<div class="table-container">
<table *ngIf="userLoginsCount != 0" class="table user-table">
<thead>
<tr>
<td class="width-50"><b>DOMAIN</b></td>
<td><b>USER ID</b></td>
<td class="left">
Metadata Updated: <b>{{ userInfo.METADATAUPDATED }}</b>
</td>
</tr>
</thead>
<tbody>
<tr *ngFor="let login of userLogins">
<td class="">{{ login.DOMAIN }}</td>
<td class="">{{ login.USERID }}</td>
</tr>
</tbody>
</table>
</div>
}
</tbody>
</table>
</div>
</div>
}
@if (serverType === ServerType.SasViya && userInfo) {
<div class="clr-row">
<div class="clr-col-8">
<table class="table user-info">
<tbody>
<tr>
<td class="left">
<p class="user-info-text">
<b>{{ userInfo.NAME }}</b>
</p>
</td>
</tr>
<tr>
<td class="left">
User ID: <b>{{ userInfo.URI }}</b>
</td>
</tr>
<tr>
<td class="left">
User provider ID: <b>{{ userInfo.PROVIDER }}</b>
</td>
</tr>
</tbody>
</table>
</div>
</div>
}
<div class="clr-row">
<div class="clr-col-8">
<div class="card user-data">
@if (serverType === ServerType.Sas9) {
<div>
<h3>EMAILS ({{ userEmailsCount || '0' }})</h3>
@if (userEmailsCount == 0) {
<h5>No Emails Present</h5>
}
<div class="table-container">
@if (userEmailsCount != 0) {
<table class="table user-table">
<thead>
<tr>
<td class="width-50"><b>EMAIL</b></td>
<td class=""><b>TYPE</b></td>
</tr>
</thead>
<tbody>
@for (email of userEmails; track email) {
<tr>
<td class="">{{ email.EMAIL }}</td>
<td class="">{{ email.TYPE }}</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
}
@if (serverType === ServerType.Sas9) {
<hr />
}
<div>
<h3>GROUPS ({{ userGroupsCount || '0' }})</h3>
@if (userGroupsCount == 0) {
<h5>No Groups Present</h5>
}
<div class="table-container">
@if (userGroupsCount != 0) {
<table class="table user-table">
<tbody>
@for (group of userGroups; track group) {
<tr>
@if (serverType === ServerType.SasViya) {
<td
[routerLink]="'/view/usernav/groups/' + group.URI"
routerLinkActive="active"
class=""
>
{{ group.GROUPNAME }}
</td>
}
@if (serverType !== ServerType.SasViya) {
<td
[routerLink]="
'/view/usernav/groups/' +
(group.GROUPURI || group.URI)
"
routerLinkActive="active"
class=""
>
{{ group.GROUPNAME }}
</td>
}
</tr>
}
</tbody>
</table>
}
</div>
</div>
@if (serverType === ServerType.Sas9) {
<hr />
}
@if (serverType === ServerType.Sas9) {
<div>
<h3>ROLES ({{ userRolesCount || '0' }})</h3>
@if (userRolesCount == 0) {
<h5>No Roles Present</h5>
}
<div class="table-container">
@if (userRolesCount != 0) {
<table class="table user-table">
<tbody>
@for (role of userRoles; track role) {
<tr>
<td
[routerLink]="
'/view/usernav/roles/' +
getRoleURI(role.ROLEURI)
"
>
{{ role.ROLENAME }}
</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
}
@if (serverType === ServerType.Sas9) {
<hr />
}
@if (serverType === ServerType.Sas9) {
<div>
<h3>LOGINS ({{ userLoginsCount || '0' }})</h3>
@if (userLoginsCount == 0) {
<h5>No Logins Present</h5>
}
<div class="table-container">
@if (userLoginsCount != 0) {
<table class="table user-table">
<thead>
<tr>
<td class="width-50"><b>DOMAIN</b></td>
<td><b>USER ID</b></td>
</tr>
</thead>
<tbody>
@for (login of userLogins; track login) {
<tr>
<td class="">{{ login.DOMAIN }}</td>
<td class="">{{ login.USERID }}</td>
</tr>
}
</tbody>
</table>
}
</div>
</div>
}
<br />
</div>
<br />
</div>
</div>
</div>
</div>
}
</div>
File diff suppressed because it is too large Load Diff
@@ -8,107 +8,113 @@
[(ngModel)]="collection"
(change)="updateSelectedCollection()"
>
<optgroup
*ngFor="let collection of collections | keyvalue"
[label]="collection.key"
>
<option
*ngFor="let endpoint of collections[collection.key] | keyvalue"
value="{{ endpoint.value }}"
>
{{ endpoint.key }}
</option>
</optgroup>
@for (collection of collections | keyvalue; track collection) {
<optgroup [label]="collection.key">
@for (
endpoint of collections[collection.key] | keyvalue;
track endpoint
) {
<option value="{{ endpoint.value }}">
{{ endpoint.key }}
</option>
}
</optgroup>
}
</select>
</clr-select-container>
</div>
<p
*ngIf="endpointLinks.length < 1 && !collectionLoading"
class="w-100 text-center"
>
Select collection to start
</p>
@if (endpointLinks.length < 1 && !collectionLoading) {
<p class="w-100 text-center">Select collection to start</p>
}
<div class="d-flex clr-justify-content-center">
<clr-spinner *ngIf="collectionLoading" clrMedium></clr-spinner>
@if (collectionLoading) {
<clr-spinner clrMedium></clr-spinner>
}
</div>
<clr-tree>
<clr-tree-node *ngIf="endpointLinks.length > 0" class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLinksTreeInput
placeholder="Search links"
name="input"
[(ngModel)]="linksSearch"
(keyup)="linksListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchLinksTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchLinksTreeInput.value.length > 0"
(click)="linksSearch = ''; linksListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let link of endpointLinks">
<clr-tree-node
(click)="treeNodeClicked($event, link, endpointLinks)"
*ngIf="!link['hidden']"
[(clrExpanded)]="link['expanded']"
[clrLoading]="!!link['loadingTables'] && !link.tables"
>
<p (click)="linkOnClick(link)" class="m-0 cursor-pointer">
<clr-icon shape="block"></clr-icon>
{{ link.method }} {{ link.rel }}
</p>
@if (endpointLinks.length > 0) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
appStealFocus
clrInput
#searchLinksTreeInput
placeholder="Search links"
name="input"
[(ngModel)]="linksSearch"
(keyup)="linksListOnFilter()"
autocomplete="off"
/>
@if (searchLinksTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchLinksTreeInput.value.length > 0) {
<clr-icon
(click)="linksSearch = ''; linksListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (link of endpointLinks; track link) {
@if (!link['hidden']) {
<clr-tree-node
(click)="treeNodeClicked($event, link, endpointLinks)"
[(clrExpanded)]="link['expanded']"
[clrLoading]="!!link['loadingTables'] && !link.tables"
>
<p (click)="linkOnClick(link)" class="m-0 cursor-pointer">
<clr-icon shape="block"></clr-icon>
{{ link.method }} {{ link.rel }}
</p>
</clr-tree-node>
}
}
</clr-tree>
<clr-tree>
<clr-tree-node *ngIf="endpointItems.length > 0" class="search-node">
<div class="tree-search-wrapper">
<input
clrInput
#searchItemsTreeInput
placeholder="Search items"
name="input"
[(ngModel)]="itemsSearch"
(keyup)="itemsListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchItemsTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchItemsTreeInput.value.length > 0"
(click)="itemsSearch = ''; itemsListOnFilter()"
shape="times"
></clr-icon>
</div>
</clr-tree-node>
<ng-container *ngFor="let item of endpointItems">
<clr-tree-node
(click)="treeNodeClicked($event, item, endpointItems)"
*ngIf="!item['hidden']"
[(clrExpanded)]="item['expanded']"
[clrLoading]="!!item['loadingTables'] && !item.tables"
>
<p (click)="itemOnClick(item)" class="m-0 cursor-pointer">
<clr-icon shape="block"></clr-icon>
{{ item.name || item.id }}
</p>
@if (endpointItems.length > 0) {
<clr-tree-node class="search-node">
<div class="tree-search-wrapper">
<input
clrInput
#searchItemsTreeInput
placeholder="Search items"
name="input"
[(ngModel)]="itemsSearch"
(keyup)="itemsListOnFilter()"
autocomplete="off"
/>
@if (searchItemsTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchItemsTreeInput.value.length > 0) {
<clr-icon
(click)="itemsSearch = ''; itemsListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
</ng-container>
}
@for (item of endpointItems; track item) {
@if (!item['hidden']) {
<clr-tree-node
(click)="treeNodeClicked($event, item, endpointItems)"
[(clrExpanded)]="item['expanded']"
[clrLoading]="!!item['loadingTables'] && !item.tables"
>
<p (click)="itemOnClick(item)" class="m-0 cursor-pointer">
<clr-icon shape="block"></clr-icon>
{{ item.name || item.id }}
</p>
</clr-tree-node>
}
}
</clr-tree>
</app-sidebar>
@@ -132,12 +138,14 @@
<h4 class="text-center m-0">
API Explorer
<clr-spinner *ngIf="endpointLoading" clrSmall></clr-spinner>
@if (endpointLoading) {
<clr-spinner clrSmall></clr-spinner>
}
</h4>
</div>
<hr class="w-100 light" />
<ng-container *ngIf="collection.length > 0">
@if (collection.length > 0) {
<div class="d-flex clr-align-items-center clr-justify-content-between">
<span class="d-flex clr-align-items-center">
GET
@@ -154,7 +162,6 @@
<clr-icon shape="copy"></clr-icon>
</button>
</span>
<div class="d-flex clr-align-items-center query-params">
<clr-input-container class="m-0">
<label>Start</label>
@@ -187,13 +194,15 @@
</div>
</div>
<pre class="json-area">
<ng-container *ngIf="endpointJson">
<ngx-json-viewer *ngIf="!rawJson" class="d-block" [json]="endpointJson" [expanded]="false" (segmentLinkClicked)="onJsonLinkClick($event)"></ngx-json-viewer>
<ng-container *ngIf="rawJson">
@if (endpointJson) {
@if (!rawJson) {
<ngx-json-viewer class="d-block" [json]="endpointJson" [expanded]="false" (segmentLinkClicked)="onJsonLinkClick($event)"></ngx-json-viewer>
}
@if (rawJson) {
{{ endpointJson | prettyjson }}
</ng-container>
</ng-container>
}
}
</pre>
</ng-container>
}
</div>
</div>
+137 -138
View File
@@ -1,7 +1,9 @@
<app-sidebar>
<div *ngIf="xlmapsLoading" class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
@if (xlmapsLoading) {
<div class="my-10-mx-auto text-center">
<clr-spinner clrMedium></clr-spinner>
</div>
}
<clr-tree>
<clr-tree-node class="search-node">
@@ -16,19 +18,19 @@
(keyup)="xlmapListOnFilter()"
autocomplete="off"
/>
<clr-icon
*ngIf="searchXLMapTreeInput.value.length < 1"
shape="search"
></clr-icon>
<clr-icon
*ngIf="searchXLMapTreeInput.value.length > 0"
(click)="searchString = ''; xlmapListOnFilter()"
shape="times"
></clr-icon>
@if (searchXLMapTreeInput.value.length < 1) {
<clr-icon shape="search"></clr-icon>
}
@if (searchXLMapTreeInput.value.length > 0) {
<clr-icon
(click)="searchString = ''; xlmapListOnFilter()"
shape="times"
></clr-icon>
}
</div>
</clr-tree-node>
<ng-container *ngFor="let xlmap of xlmaps">
@for (xlmap of xlmaps; track xlmap) {
<clr-tree-node>
<button
(click)="xlmapOnClick(xlmap)"
@@ -39,99 +41,99 @@
{{ xlmap.id }}
</button>
</clr-tree-node>
</ng-container>
}
</clr-tree>
</app-sidebar>
<div class="content-area">
<div *ngIf="!selectedXLMap" class="no-table-selected">
<img
src="images/select-table.png"
class="select-table-icon"
alt="select table icon"
/>
<p
*ngIf="xlmaps.length > 0"
class="text-center color-gray mt-10"
cds-text="section"
@if (!selectedXLMap) {
<div class="no-table-selected">
<img
src="images/select-table.png"
class="select-table-icon"
alt="select table icon"
/>
@if (xlmaps.length > 0) {
<p class="text-center color-gray mt-10" cds-text="section">
Please select a map
</p>
}
@if (xlmaps.length < 1) {
<p class="text-center color-gray mt-10" cds-text="section">
No excel map is found
</p>
}
</div>
}
@if (isLoading) {
<div class="loadingSpinner">
<span class="spinner"> Loading... </span>
<div>
<h4>{{ isLoadingDesc }}</h4>
</div>
</div>
}
@if (!isLoading && selectedXLMap) {
<div
appDragNdrop
(fileDraggedOver)="onShowUploadModal()"
class="card h-100 d-flex clr-flex-column"
>
Please select a map
</p>
<p
*ngIf="xlmaps.length < 1"
class="text-center color-gray mt-10"
cds-text="section"
>
No excel map is found
</p>
</div>
<div class="loadingSpinner" *ngIf="isLoading">
<span class="spinner"> Loading... </span>
<div>
<h4>{{ isLoadingDesc }}</h4>
</div>
</div>
<div
appDragNdrop
(fileDraggedOver)="onShowUploadModal()"
class="card h-100 d-flex clr-flex-column"
*ngIf="!isLoading && selectedXLMap"
>
<clr-tabs>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Rules">Rules</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Rules">
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Data">Data</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Data">
</clr-tab-content>
</clr-tab>
</clr-tabs>
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
<div class="clr-row m-0 mb-10-i viewerTitle">
<h3 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
{{ selectedXLMap.id }}
</h3>
<i class="d-flex clr-col-12 clr-justify-content-center mt-5-i">{{
selectedXLMap.description
}}</i>
<p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Rules Source:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + rulesSource"
<clr-tabs>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Rules">
Rules
</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Rules">
</clr-tab-content>
</clr-tab>
<clr-tab>
<button clrTabLink (click)="selectedTab = TabsEnum.Data">Data</button>
<clr-tab-content *clrIfActive="selectedTab === TabsEnum.Data">
</clr-tab-content>
</clr-tab>
</clr-tabs>
<ng-container *ngTemplateOutlet="actionButtons"></ng-container>
<div class="clr-row m-0 mb-10-i viewerTitle">
<h3 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
{{ selectedXLMap.id }}
</h3>
<i class="d-flex clr-col-12 clr-justify-content-center mt-5-i">{{
selectedXLMap.description
}}</i>
<p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Rules Source:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + rulesSource"
>
{{ rulesSource }}
</a>
</p>
<p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Target dataset:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + selectedXLMap.targetDS"
>
{{ selectedXLMap.targetDS }}
</a>
</p>
</div>
<div class="clr-flex-1">
<hot-table
id="hot-table"
[data]="selectedTab === TabsEnum.Rules ? xlmapRules : xlData"
[settings]="hotTableSettings()"
>
{{ rulesSource }}
</a>
</p>
<p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
Target dataset:
<a
cds-text="labelLink"
class="ml-10"
[routerLink]="'/view/data/' + selectedXLMap.targetDS"
>
{{ selectedXLMap.targetDS }}
</a>
</p>
</hot-table>
</div>
</div>
<div class="clr-flex-1">
<hot-table
id="hot-table"
[data]="selectedTab === TabsEnum.Rules ? xlmapRules : xlData"
[settings]="hotTableSettings()"
>
</hot-table>
</div>
</div>
}
<clr-modal
appFileDrop
@@ -207,44 +209,41 @@
<ng-template #actionButtons>
<div class="clr-row m-0 clr-justify-content-center">
<div
*ngIf="status === StatusEnum.ReadyToUpload"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="onShowUploadModal()"
>
<clr-icon shape="upload"></clr-icon>
<span>Upload</span>
</button>
</div>
<div
*ngIf="status === StatusEnum.ReadyToSubmit"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="submitExcel()"
>
<clr-icon shape="upload"></clr-icon>
<span>Submit</span>
</button>
</div>
<div
*ngIf="status === StatusEnum.ReadyToSubmit"
class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4"
>
<button
type="button"
class="btn btn-sm btn-outline-danger btn-block mr-0"
(click)="discardExtractedData()"
>
<clr-icon shape="times"></clr-icon>
<span>Discard</span>
</button>
</div>
@if (status === StatusEnum.ReadyToUpload) {
<div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="onShowUploadModal()"
>
<clr-icon shape="upload"></clr-icon>
<span>Upload</span>
</button>
</div>
}
@if (status === StatusEnum.ReadyToSubmit) {
<div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
<button
type="button"
class="btn btn-sm btn-success btn-block mr-0"
(click)="submitExcel()"
>
<clr-icon shape="upload"></clr-icon>
<span>Submit</span>
</button>
</div>
}
@if (status === StatusEnum.ReadyToSubmit) {
<div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
<button
type="button"
class="btn btn-sm btn-outline-danger btn-block mr-0"
(click)="discardExtractedData()"
>
<clr-icon shape="times"></clr-icon>
<span>Discard</span>
</button>
</div>
}
</div>
</ng-template>
+1 -2
View File
@@ -9,11 +9,10 @@
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"lib": ["ES2022", "dom"],
"skipLibCheck": true,
"module": "ES2022",
"importHelpers": true,
"moduleResolution": "node",
"moduleResolution": "bundler",
"sourceMap": true,
"resolveJsonModule": true,
"target": "ES2022",