mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-27 01:02:34 +01:00
Add new app level warning message (#18449)
1. Show a app level warning if there is a stuck job 2. Chang `Replication finished` to `Replication status changed` Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
a95808c120
commit
95972ba693
@ -1,5 +1,4 @@
|
|||||||
<clr-main-container>
|
<clr-main-container>
|
||||||
<global-message [isAppLevel]="true"></global-message>
|
|
||||||
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
||||||
<search-result></search-result>
|
<search-result></search-result>
|
||||||
<div
|
<div
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
import { Component } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { AppConfigService } from './services/app-config.service';
|
import { AppConfigService } from './services/app-config.service';
|
||||||
@ -31,20 +31,28 @@ import {
|
|||||||
} from './shared/entities/shared.const';
|
} from './shared/entities/shared.const';
|
||||||
import { SkinableConfig } from './services/skinable-config.service';
|
import { SkinableConfig } from './services/skinable-config.service';
|
||||||
import { isSupportedLanguage } from './shared/units/shared.utils';
|
import { isSupportedLanguage } from './shared/units/shared.utils';
|
||||||
|
import {
|
||||||
|
CHECK_HEALTH_INTERVAL,
|
||||||
|
JobServiceDashboardHealthCheckService,
|
||||||
|
} from './base/left-side-nav/job-service-dashboard/job-service-dashboard-health-check.service';
|
||||||
|
import { SessionService } from './shared/services/session.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'harbor-app',
|
selector: 'harbor-app',
|
||||||
templateUrl: 'app.component.html',
|
templateUrl: 'app.component.html',
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
||||||
styleMode: string = this.themeArray[0].showStyle;
|
styleMode: string = this.themeArray[0].showStyle;
|
||||||
|
interval: any;
|
||||||
constructor(
|
constructor(
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private appConfigService: AppConfigService,
|
private appConfigService: AppConfigService,
|
||||||
private titleService: Title,
|
private titleService: Title,
|
||||||
public theme: ThemeService,
|
public theme: ThemeService,
|
||||||
private skinableConfig: SkinableConfig
|
private skinableConfig: SkinableConfig,
|
||||||
|
private jobServiceDashboardHealthCheckService: JobServiceDashboardHealthCheckService,
|
||||||
|
private sessionService: SessionService
|
||||||
) {
|
) {
|
||||||
// init language
|
// init language
|
||||||
this.initLanguage();
|
this.initLanguage();
|
||||||
@ -66,6 +74,26 @@ export class AppComponent {
|
|||||||
});
|
});
|
||||||
this.setTheme();
|
this.setTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
this.interval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.interval) {
|
||||||
|
this.interval = setInterval(() => {
|
||||||
|
if (this.sessionService.getCurrentUser()?.has_admin_role) {
|
||||||
|
this.jobServiceDashboardHealthCheckService.checkHealth();
|
||||||
|
} else {
|
||||||
|
this.jobServiceDashboardHealthCheckService.setHealthy(true);
|
||||||
|
}
|
||||||
|
}, CHECK_HEALTH_INTERVAL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTheme() {
|
setTheme() {
|
||||||
let styleMode = this.themeArray[0].showStyle;
|
let styleMode = this.themeArray[0].showStyle;
|
||||||
const localHasStyle =
|
const localHasStyle =
|
||||||
|
@ -22,6 +22,7 @@ import { PasswordSettingComponent } from './password-setting/password-setting.co
|
|||||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||||
import { ForgotPasswordComponent } from './password-setting/forgot-password/forgot-password.component';
|
import { ForgotPasswordComponent } from './password-setting/forgot-password/forgot-password.component';
|
||||||
import { GlobalConfirmationDialogComponent } from './global-confirmation-dialog/global-confirmation-dialog.component';
|
import { GlobalConfirmationDialogComponent } from './global-confirmation-dialog/global-confirmation-dialog.component';
|
||||||
|
import { AppLevelAlertsComponent } from './harbor-shell/app-level-alerts/app-level-alerts.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -172,6 +173,7 @@ const routes: Routes = [
|
|||||||
AccountSettingsModalComponent,
|
AccountSettingsModalComponent,
|
||||||
ForgotPasswordComponent,
|
ForgotPasswordComponent,
|
||||||
GlobalConfirmationDialogComponent,
|
GlobalConfirmationDialogComponent,
|
||||||
|
AppLevelAlertsComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class BaseModule {}
|
export class BaseModule {}
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
<clr-alerts>
|
||||||
|
<clr-alert
|
||||||
|
*ngIf="showReadOnly && isLogin()"
|
||||||
|
[clrAlertType]="message?.type"
|
||||||
|
[clrAlertAppLevel]="true"
|
||||||
|
[clrAlertClosable]="false">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">{{ message?.message | translate }}</span>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
<clr-alert
|
||||||
|
*ngIf="showLogin"
|
||||||
|
[clrAlertType]="message?.type"
|
||||||
|
[clrAlertAppLevel]="true">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">{{ message?.message | translate }}</span>
|
||||||
|
<div class="alert-actions">
|
||||||
|
<button
|
||||||
|
class="btn alert-action no-underline"
|
||||||
|
(click)="signIn()">
|
||||||
|
{{ 'BUTTON.LOG_IN' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
<clr-alert
|
||||||
|
(clrAlertClosedChange)="closeHealthWarning()"
|
||||||
|
*ngIf="showJobServiceDashboardHealthCheck() && isLogin()"
|
||||||
|
[clrAlertType]="'warning'"
|
||||||
|
[clrAlertAppLevel]="true">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">
|
||||||
|
{{ 'JOB_SERVICE_DASHBOARD.WAITING_TOO_LONG_1' | translate }}
|
||||||
|
<a
|
||||||
|
class="alert-action"
|
||||||
|
href="#"
|
||||||
|
routerLink="/harbor/job-service-dashboard/pending-jobs">
|
||||||
|
{{ 'JOB_SERVICE_DASHBOARD.WAITING_TOO_LONG_2' | translate }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<div class="alert-actions">
|
||||||
|
{{ 'JOB_SERVICE_DASHBOARD.WAITING_TOO_LONG_3' | translate }}
|
||||||
|
<a
|
||||||
|
class="alert-action"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href="https://github.com/goharbor/harbor/wiki/Reduce-job-queue-latency(wait-time)"
|
||||||
|
>{{
|
||||||
|
'JOB_SERVICE_DASHBOARD.WAITING_TOO_LONG_4' | translate
|
||||||
|
}}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
<clr-alert
|
||||||
|
[clrAlertType]="'info'"
|
||||||
|
[clrAlertAppLevel]="true"
|
||||||
|
(clrAlertClosedChange)="closeInfo()"
|
||||||
|
*ngIf="shouldShowScannerInfo() && isLogin()">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">
|
||||||
|
{{ 'SCANNER.HELP_INFO_1' | translate }}
|
||||||
|
<a
|
||||||
|
class="alert-action"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href="{{ scannerDocUrl }}"
|
||||||
|
>{{ 'SCANNER.HELP_INFO_2' | translate }}</a
|
||||||
|
></span
|
||||||
|
>
|
||||||
|
<div class="alert-actions last">
|
||||||
|
<a
|
||||||
|
class="alert-action"
|
||||||
|
href="#"
|
||||||
|
routerLink="/harbor/interrogation-services/scanners"
|
||||||
|
>{{ 'SCANNER.ALL_SCANNERS' | translate }}</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
</clr-alerts>
|
@ -0,0 +1,16 @@
|
|||||||
|
.alert-text {
|
||||||
|
flex: 0 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-action {
|
||||||
|
text-decoration: underline
|
||||||
|
}
|
||||||
|
|
||||||
|
.last {
|
||||||
|
position: absolute;
|
||||||
|
right: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-underline {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { AppLevelAlertsComponent } from './app-level-alerts.component';
|
||||||
|
import { SharedTestingModule } from '../../../shared/shared.module';
|
||||||
|
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
import { Scanner } from '../../left-side-nav/interrogation-services/scanner/scanner';
|
||||||
|
import { ScannerService } from 'ng-swagger-gen/services/scanner.service';
|
||||||
|
import { SessionService } from 'src/app/shared/services/session.service';
|
||||||
|
|
||||||
|
describe('AppLevelAlertsComponent', () => {
|
||||||
|
let component: AppLevelAlertsComponent;
|
||||||
|
let fixture: ComponentFixture<AppLevelAlertsComponent>;
|
||||||
|
|
||||||
|
const fakeScannerService = {
|
||||||
|
listScannersResponse() {
|
||||||
|
const response: HttpResponse<Array<Scanner>> = new HttpResponse<
|
||||||
|
Array<Scanner>
|
||||||
|
>({
|
||||||
|
headers: new HttpHeaders({
|
||||||
|
'x-total-count': '1',
|
||||||
|
}),
|
||||||
|
body: [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
is_default: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
return of(response).pipe(delay(0));
|
||||||
|
},
|
||||||
|
listScanners() {
|
||||||
|
return of([
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
is_default: true,
|
||||||
|
},
|
||||||
|
]).pipe(delay(0));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeSessionService = {
|
||||||
|
getCurrentUser: function () {
|
||||||
|
return { has_admin_role: true };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SharedTestingModule],
|
||||||
|
declarations: [AppLevelAlertsComponent],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ScannerService,
|
||||||
|
useValue: fakeScannerService,
|
||||||
|
},
|
||||||
|
{ provide: SessionService, useValue: fakeSessionService },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AppLevelAlertsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show scanner alert', async () => {
|
||||||
|
await fixture.whenStable();
|
||||||
|
fixture.detectChanges();
|
||||||
|
const compiled = fixture.nativeElement;
|
||||||
|
expect(compiled.querySelector('.alerts.alert-info')).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,191 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { SCANNERS_DOC } from '../../left-side-nav/interrogation-services/scanner/scanner';
|
||||||
|
import { SessionService } from '../../../shared/services/session.service';
|
||||||
|
import { DEFAULT_PAGE_SIZE, delUrlParam } from '../../../shared/units/utils';
|
||||||
|
import { forkJoin, Observable, Subscription } from 'rxjs';
|
||||||
|
import { Project } from '../../../../../ng-swagger-gen/models/project';
|
||||||
|
import { ScannerService } from '../../../../../ng-swagger-gen/services/scanner.service';
|
||||||
|
import { UN_LOGGED_PARAM } from '../../../account/sign-in/sign-in.service';
|
||||||
|
import {
|
||||||
|
CommonRoutes,
|
||||||
|
httpStatusCode,
|
||||||
|
} from '../../../shared/entities/shared.const';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { MessageService } from '../../../shared/components/global-message/message.service';
|
||||||
|
import { Message } from '../../../shared/components/global-message/message';
|
||||||
|
import { JobServiceDashboardHealthCheckService } from '../../left-side-nav/job-service-dashboard/job-service-dashboard-health-check.service';
|
||||||
|
import { AppLevelMessage } from '../../../shared/services/message-handler.service';
|
||||||
|
const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo';
|
||||||
|
const YES: string = 'yes';
|
||||||
|
@Component({
|
||||||
|
selector: 'app-app-level-alerts',
|
||||||
|
templateUrl: './app-level-alerts.component.html',
|
||||||
|
styleUrls: ['./app-level-alerts.component.scss'],
|
||||||
|
})
|
||||||
|
export class AppLevelAlertsComponent implements OnInit, OnDestroy {
|
||||||
|
scannerDocUrl: string = SCANNERS_DOC;
|
||||||
|
showScannerInfo: boolean = false;
|
||||||
|
message: Message;
|
||||||
|
appLevelMsgSub: Subscription;
|
||||||
|
clearSub: Subscription;
|
||||||
|
showLogin: boolean = false;
|
||||||
|
showReadOnly: boolean = false;
|
||||||
|
constructor(
|
||||||
|
private session: SessionService,
|
||||||
|
private scannerService: ScannerService,
|
||||||
|
private router: Router,
|
||||||
|
private messageService: MessageService,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private jobServiceDashboardHealthCheckService: JobServiceDashboardHealthCheckService
|
||||||
|
) {}
|
||||||
|
ngOnInit() {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
localStorage &&
|
||||||
|
localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (this.session.getCurrentUser()?.has_admin_role) {
|
||||||
|
this.getDefaultScanner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.appLevelMsgSub) {
|
||||||
|
this.appLevelMsgSub =
|
||||||
|
this.messageService.appLevelAnnounced$.subscribe(message => {
|
||||||
|
this.message = message;
|
||||||
|
this.showReadOnly =
|
||||||
|
message.statusCode === httpStatusCode.AppLevelWarning &&
|
||||||
|
message.message === AppLevelMessage.REPO_READ_ONLY;
|
||||||
|
|
||||||
|
if (message.statusCode === httpStatusCode.Unauthorized) {
|
||||||
|
this.showLogin = true;
|
||||||
|
// User session timed out, then redirect to sign-in page
|
||||||
|
if (
|
||||||
|
this.session.getCurrentUser() &&
|
||||||
|
!this.isSignInUrl() &&
|
||||||
|
this.route.snapshot.queryParams[UN_LOGGED_PARAM] !==
|
||||||
|
YES
|
||||||
|
) {
|
||||||
|
const url = delUrlParam(
|
||||||
|
this.router.url,
|
||||||
|
UN_LOGGED_PARAM
|
||||||
|
);
|
||||||
|
this.session.clear(); // because of SignInGuard, must clear user session before navigating to sign-in page
|
||||||
|
this.router.navigate(
|
||||||
|
[CommonRoutes.EMBEDDED_SIGN_IN],
|
||||||
|
{
|
||||||
|
queryParams: { redirect_url: url },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showLogin = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!this.clearSub) {
|
||||||
|
this.clearSub = this.messageService.clearChan$.subscribe(clear => {
|
||||||
|
this.showLogin = false;
|
||||||
|
this.showReadOnly = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.appLevelMsgSub) {
|
||||||
|
this.appLevelMsgSub.unsubscribe();
|
||||||
|
this.appLevelMsgSub = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldShowScannerInfo(): boolean {
|
||||||
|
return (
|
||||||
|
this.session.getCurrentUser()?.has_admin_role &&
|
||||||
|
this.showScannerInfo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultScanner() {
|
||||||
|
this.scannerService
|
||||||
|
.listScannersResponse({
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
|
.subscribe(res => {
|
||||||
|
if (res.headers) {
|
||||||
|
const xHeader: string = res.headers.get('X-Total-Count');
|
||||||
|
const totalCount = parseInt(xHeader, 0);
|
||||||
|
let arr = res.body || [];
|
||||||
|
if (totalCount <= DEFAULT_PAGE_SIZE) {
|
||||||
|
// already gotten all scanners
|
||||||
|
if (arr && arr.length) {
|
||||||
|
this.showScannerInfo = arr.some(
|
||||||
|
scanner => scanner.is_default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// get all the scanners in specified times
|
||||||
|
const times: number = Math.ceil(
|
||||||
|
totalCount / DEFAULT_PAGE_SIZE
|
||||||
|
);
|
||||||
|
const observableList: Observable<Project[]>[] = [];
|
||||||
|
for (let i = 2; i <= times; i++) {
|
||||||
|
observableList.push(
|
||||||
|
this.scannerService.listScanners({
|
||||||
|
page: i,
|
||||||
|
pageSize: DEFAULT_PAGE_SIZE,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
forkJoin(observableList).subscribe(response => {
|
||||||
|
if (response && response.length) {
|
||||||
|
response.forEach(item => {
|
||||||
|
arr = arr.concat(item);
|
||||||
|
});
|
||||||
|
this.showScannerInfo = arr.some(
|
||||||
|
scanner => scanner.is_default
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeInfo() {
|
||||||
|
if (localStorage) {
|
||||||
|
localStorage.setItem(HAS_SHOWED_SCANNER_INFO, YES);
|
||||||
|
}
|
||||||
|
this.showScannerInfo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
signIn(): void {
|
||||||
|
// remove queryParam UN_LOGGED_PARAM of redirect url
|
||||||
|
const url = delUrlParam(this.router.url, UN_LOGGED_PARAM);
|
||||||
|
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], {
|
||||||
|
queryParams: { redirect_url: url },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isSignInUrl(): boolean {
|
||||||
|
const url: string =
|
||||||
|
this.router.url?.indexOf('?') === -1
|
||||||
|
? this.router.url
|
||||||
|
: this.router.url?.split('?')[0];
|
||||||
|
return url === CommonRoutes.EMBEDDED_SIGN_IN;
|
||||||
|
}
|
||||||
|
|
||||||
|
showJobServiceDashboardHealthCheck(): boolean {
|
||||||
|
return (
|
||||||
|
this.jobServiceDashboardHealthCheckService.hasUnhealthyQueue() &&
|
||||||
|
!this.jobServiceDashboardHealthCheckService.hasManuallyClosed()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
closeHealthWarning() {
|
||||||
|
this.jobServiceDashboardHealthCheckService.setManuallyClosed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLogin(): boolean {
|
||||||
|
return this.session.getCurrentUser()?.has_admin_role;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<clr-main-container>
|
<clr-main-container>
|
||||||
<div class="clr-row scanner-info" *ngIf="showScannerInfo && isSystemAdmin">
|
<!--<div class="clr-row scanner-info" *ngIf="showScannerInfo && isSystemAdmin">
|
||||||
<div class="clr-col-2"></div>
|
<div class="clr-col-2"></div>
|
||||||
<div class="clr-col text-center">
|
<div class="clr-col text-center">
|
||||||
<clr-icon shape="info-standard" size="20"></clr-icon>
|
<clr-icon shape="info-standard" size="20"></clr-icon>
|
||||||
@ -26,8 +26,8 @@
|
|||||||
shape="times"
|
shape="times"
|
||||||
size="24"></clr-icon>
|
size="24"></clr-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>-->
|
||||||
<global-message [isAppLevel]="true"></global-message>
|
<app-app-level-alerts></app-app-level-alerts>
|
||||||
<navigator
|
<navigator
|
||||||
(showAccountSettingsModal)="openModal($event)"
|
(showAccountSettingsModal)="openModal($event)"
|
||||||
(showDialogModalAction)="openModal($event)"></navigator>
|
(showDialogModalAction)="openModal($event)"></navigator>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
[class.content-area-override]="!shouldOverrideContent"
|
[class.content-area-override]="!shouldOverrideContent"
|
||||||
[class.start-content-padding]="shouldOverrideContent"
|
[class.start-content-padding]="shouldOverrideContent"
|
||||||
(scroll)="publishScrollEvent()">
|
(scroll)="publishScrollEvent()">
|
||||||
<global-message [isAppLevel]="false"></global-message>
|
<global-message></global-message>
|
||||||
<!-- Only appear when searching -->
|
<!-- Only appear when searching -->
|
||||||
<search-result></search-result>
|
<search-result></search-result>
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
@ -38,43 +38,3 @@ clr-vertical-nav {
|
|||||||
.font-size-13 {
|
.font-size-13 {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scanner-info {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
background-color: #0079b8;
|
|
||||||
min-height: 2rem;
|
|
||||||
color: #fff;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0.5rem;
|
|
||||||
|
|
||||||
a {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: #bababa underline;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover, a:visited, a:link {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.all-scanners {
|
|
||||||
margin-right: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-icon {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-05 {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
@ -20,9 +20,6 @@ import { ErrorHandler } from '../../shared/units/error-handler';
|
|||||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||||
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
||||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
|
||||||
import { Registry } from '../../../../ng-swagger-gen/models/registry';
|
|
||||||
import { delay } from 'rxjs/operators';
|
|
||||||
import { UserService } from '../../../../ng-swagger-gen/services/user.service';
|
import { UserService } from '../../../../ng-swagger-gen/services/user.service';
|
||||||
|
|
||||||
describe('HarborShellComponent', () => {
|
describe('HarborShellComponent', () => {
|
||||||
@ -72,22 +69,6 @@ describe('HarborShellComponent', () => {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let fakeScannerService = {
|
|
||||||
listScannersResponse() {
|
|
||||||
const response: HttpResponse<Array<Registry>> = new HttpResponse<
|
|
||||||
Array<Registry>
|
|
||||||
>({
|
|
||||||
headers: new HttpHeaders({
|
|
||||||
'x-total-count': [].length.toString(),
|
|
||||||
}),
|
|
||||||
body: [],
|
|
||||||
});
|
|
||||||
return of(response).pipe(delay(0));
|
|
||||||
},
|
|
||||||
listScanners() {
|
|
||||||
return of([]).pipe(delay(0));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const fakedUserService = {
|
const fakedUserService = {
|
||||||
getCurrentUserInfo() {
|
getCurrentUserInfo() {
|
||||||
return of({});
|
return of({});
|
||||||
@ -120,7 +101,6 @@ describe('HarborShellComponent', () => {
|
|||||||
useValue: fakeSearchTriggerService,
|
useValue: fakeSearchTriggerService,
|
||||||
},
|
},
|
||||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||||
{ provide: ScannerService, useValue: fakeScannerService },
|
|
||||||
{
|
{
|
||||||
provide: MessageHandlerService,
|
provide: MessageHandlerService,
|
||||||
useValue: mockMessageHandlerService,
|
useValue: mockMessageHandlerService,
|
||||||
@ -143,7 +123,6 @@ describe('HarborShellComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(HarborShellComponent);
|
fixture = TestBed.createComponent(HarborShellComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.showScannerInfo = true;
|
|
||||||
component.accountSettingsModal = TestBed.createComponent(
|
component.accountSettingsModal = TestBed.createComponent(
|
||||||
AccountSettingsModalComponent
|
AccountSettingsModalComponent
|
||||||
).componentInstance;
|
).componentInstance;
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { forkJoin, Observable, Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { AppConfigService } from '../../services/app-config.service';
|
import { AppConfigService } from '../../services/app-config.service';
|
||||||
import { ModalEvent } from '../modal-event';
|
import { ModalEvent } from '../modal-event';
|
||||||
import { modalEvents } from '../modal-events.const';
|
import { modalEvents } from '../modal-events.const';
|
||||||
@ -34,19 +34,14 @@ import {
|
|||||||
CONFIG_AUTH_MODE,
|
CONFIG_AUTH_MODE,
|
||||||
} from '../../shared/entities/shared.const';
|
} from '../../shared/entities/shared.const';
|
||||||
import { THEME_ARRAY, ThemeInterface } from '../../services/theme';
|
import { THEME_ARRAY, ThemeInterface } from '../../services/theme';
|
||||||
import { clone, DEFAULT_PAGE_SIZE } from '../../shared/units/utils';
|
import { clone } from '../../shared/units/utils';
|
||||||
import { ThemeService } from '../../services/theme.service';
|
import { ThemeService } from '../../services/theme.service';
|
||||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||||
import {
|
import {
|
||||||
EventService,
|
EventService,
|
||||||
HarborEvent,
|
HarborEvent,
|
||||||
} from '../../services/event-service/event.service';
|
} from '../../services/event-service/event.service';
|
||||||
import { SCANNERS_DOC } from '../left-side-nav/interrogation-services/scanner/scanner';
|
|
||||||
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
|
||||||
import { Project } from '../../../../ng-swagger-gen/models/project';
|
|
||||||
|
|
||||||
const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo';
|
|
||||||
const YES: string = 'yes';
|
|
||||||
const HAS_STYLE_MODE: string = 'styleModeLocal';
|
const HAS_STYLE_MODE: string = 'styleModeLocal';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -73,10 +68,7 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
searchSub: Subscription;
|
searchSub: Subscription;
|
||||||
searchCloseSub: Subscription;
|
searchCloseSub: Subscription;
|
||||||
showScannerInfo: boolean = false;
|
|
||||||
scannerDocUrl: string = SCANNERS_DOC;
|
|
||||||
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
||||||
|
|
||||||
styleMode = this.themeArray[0].showStyle;
|
styleMode = this.themeArray[0].showStyle;
|
||||||
@ViewChild('scrollDiv') scrollDiv: ElementRef;
|
@ViewChild('scrollDiv') scrollDiv: ElementRef;
|
||||||
scrollToPositionSub: Subscription;
|
scrollToPositionSub: Subscription;
|
||||||
@ -86,7 +78,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||||||
private session: SessionService,
|
private session: SessionService,
|
||||||
private searchTrigger: SearchTriggerService,
|
private searchTrigger: SearchTriggerService,
|
||||||
private appConfigService: AppConfigService,
|
private appConfigService: AppConfigService,
|
||||||
private scannerService: ScannerService,
|
|
||||||
public theme: ThemeService,
|
public theme: ThemeService,
|
||||||
private event: EventService,
|
private event: EventService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
@ -117,16 +108,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||||||
this.isSearchResultsOpened = false;
|
this.isSearchResultsOpened = false;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (
|
|
||||||
!(
|
|
||||||
localStorage &&
|
|
||||||
localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (this.isSystemAdmin) {
|
|
||||||
this.getDefaultScanner();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// set local in app
|
// set local in app
|
||||||
if (localStorage) {
|
if (localStorage) {
|
||||||
this.styleMode = localStorage.getItem(HAS_STYLE_MODE);
|
this.styleMode = localStorage.getItem(HAS_STYLE_MODE);
|
||||||
@ -150,59 +131,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
closeInfo() {
|
|
||||||
if (localStorage) {
|
|
||||||
localStorage.setItem(HAS_SHOWED_SCANNER_INFO, YES);
|
|
||||||
}
|
|
||||||
this.showScannerInfo = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultScanner() {
|
|
||||||
this.scannerService
|
|
||||||
.listScannersResponse({
|
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
.subscribe(res => {
|
|
||||||
if (res.headers) {
|
|
||||||
const xHeader: string = res.headers.get('X-Total-Count');
|
|
||||||
const totalCount = parseInt(xHeader, 0);
|
|
||||||
let arr = res.body || [];
|
|
||||||
if (totalCount <= DEFAULT_PAGE_SIZE) {
|
|
||||||
// already gotten all scanners
|
|
||||||
if (arr && arr.length) {
|
|
||||||
this.showScannerInfo = arr.some(
|
|
||||||
scanner => scanner.is_default
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// get all the scanners in specified times
|
|
||||||
const times: number = Math.ceil(
|
|
||||||
totalCount / DEFAULT_PAGE_SIZE
|
|
||||||
);
|
|
||||||
const observableList: Observable<Project[]>[] = [];
|
|
||||||
for (let i = 2; i <= times; i++) {
|
|
||||||
observableList.push(
|
|
||||||
this.scannerService.listScanners({
|
|
||||||
page: i,
|
|
||||||
pageSize: DEFAULT_PAGE_SIZE,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
forkJoin(observableList).subscribe(response => {
|
|
||||||
if (response && response.length) {
|
|
||||||
response.forEach(item => {
|
|
||||||
arr = arr.concat(item);
|
|
||||||
});
|
|
||||||
this.showScannerInfo = arr.some(
|
|
||||||
scanner => scanner.is_default
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.searchSub) {
|
if (this.searchSub) {
|
||||||
this.searchSub.unsubscribe();
|
this.searchSub.unsubscribe();
|
||||||
|
@ -0,0 +1,42 @@
|
|||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
import { JobServiceDashboardHealthCheckService } from './job-service-dashboard-health-check.service';
|
||||||
|
import { JobserviceService } from '../../../../../ng-swagger-gen/services/jobservice.service';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
|
describe('JobServiceDashboardHealthCheckService', () => {
|
||||||
|
let service: JobServiceDashboardHealthCheckService;
|
||||||
|
|
||||||
|
const fakedJobserviceService = {
|
||||||
|
listJobQueues() {
|
||||||
|
return of({});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: JobserviceService,
|
||||||
|
useValue: fakedJobserviceService,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
service = TestBed.inject(JobServiceDashboardHealthCheckService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when hasUnhealthyQueue is called', () => {
|
||||||
|
expect(service.hasUnhealthyQueue()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when hasManuallyClosed is called', () => {
|
||||||
|
expect(service.hasManuallyClosed()).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when hasUnhealthyQueue is called', () => {
|
||||||
|
expect(service.hasUnhealthyQueue()).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,38 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { JobserviceService } from '../../../../../ng-swagger-gen/services/jobservice.service';
|
||||||
|
|
||||||
|
export const HEALTHY_TIME: number = 24; // unit hours
|
||||||
|
export const CHECK_HEALTH_INTERVAL: number = 15 * 60 * 1000; //15 minutes, unit ms
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class JobServiceDashboardHealthCheckService {
|
||||||
|
private _hasUnhealthyQueue: boolean = false;
|
||||||
|
private _hasManuallyClosed: boolean = false;
|
||||||
|
|
||||||
|
constructor(private jobServiceService: JobserviceService) {}
|
||||||
|
|
||||||
|
hasUnhealthyQueue(): boolean {
|
||||||
|
return this._hasUnhealthyQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasManuallyClosed(): boolean {
|
||||||
|
return this._hasManuallyClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHealthy(value: boolean): void {
|
||||||
|
this._hasUnhealthyQueue = value;
|
||||||
|
}
|
||||||
|
setManuallyClosed(value: boolean): void {
|
||||||
|
this._hasManuallyClosed = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHealth(): void {
|
||||||
|
this.jobServiceService.listJobQueues().subscribe(res => {
|
||||||
|
this._hasUnhealthyQueue = res?.some(
|
||||||
|
item => item.latency >= HEALTHY_TIME * 60 * 60
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -80,7 +80,16 @@
|
|||||||
<clr-dg-row *clrDgItems="let j of jobQueue" [clrDgItem]="j">
|
<clr-dg-row *clrDgItems="let j of jobQueue" [clrDgItem]="j">
|
||||||
<clr-dg-cell>{{ j.job_type }}</clr-dg-cell>
|
<clr-dg-cell>{{ j.job_type }}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ j.count || 0 }}</clr-dg-cell>
|
<clr-dg-cell>{{ j.count || 0 }}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ getDuration(j?.latency) || 0 }}</clr-dg-cell>
|
<clr-dg-cell>
|
||||||
|
<span class="container">
|
||||||
|
<cds-icon
|
||||||
|
*ngIf="showWarning(j?.latency)"
|
||||||
|
size="20"
|
||||||
|
class="warning"
|
||||||
|
shape="exclamation-triangle"></cds-icon>
|
||||||
|
<span class="ml-5px">{{ getDuration(j?.latency) || 0 }}</span>
|
||||||
|
</span>
|
||||||
|
</clr-dg-cell>
|
||||||
<clr-dg-cell>{{ isPaused(j?.paused) | translate }}</clr-dg-cell>
|
<clr-dg-cell>{{ isPaused(j?.paused) | translate }}</clr-dg-cell>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
|
@ -7,3 +7,16 @@
|
|||||||
.refresh-btn {
|
.refresh-btn {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: #a36500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ml-5px {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@ import {
|
|||||||
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
import { OperationService } from '../../../../shared/components/operation/operation.service';
|
||||||
import { errorHandler } from '../../../../shared/units/shared.utils';
|
import { errorHandler } from '../../../../shared/units/shared.utils';
|
||||||
import { JobServiceDashboardSharedDataService } from '../job-service-dashboard-shared-data.service';
|
import { JobServiceDashboardSharedDataService } from '../job-service-dashboard-shared-data.service';
|
||||||
|
import { HEALTHY_TIME } from '../job-service-dashboard-health-check.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-pending-job-list',
|
selector: 'app-pending-job-list',
|
||||||
@ -310,4 +311,7 @@ export class PendingListComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
showWarning(latency: number): boolean {
|
||||||
|
return latency && latency >= HEALTHY_TIME * 60 * 60;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
:host::ng-deep.modal-dialog {
|
:host::ng-deep.modal-dialog {
|
||||||
width: 30rem;
|
width: 31rem;
|
||||||
height: 35rem;
|
height: 35rem;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ describe('WebhookService', () => {
|
|||||||
expect(service).toBeTruthy();
|
expect(service).toBeTruthy();
|
||||||
const eventType: string = 'REPLICATION';
|
const eventType: string = 'REPLICATION';
|
||||||
expect(service.eventTypeToText(eventType)).toEqual(
|
expect(service.eventTypeToText(eventType)).toEqual(
|
||||||
'Replication finished'
|
'Replication status changed'
|
||||||
);
|
);
|
||||||
const mockedEventType: string = 'TEST';
|
const mockedEventType: string = 'TEST';
|
||||||
expect(service.eventTypeToText(mockedEventType)).toEqual('TEST');
|
expect(service.eventTypeToText(mockedEventType)).toEqual('TEST');
|
||||||
|
@ -15,7 +15,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { MarkdownPipe } from 'ngx-markdown/src/markdown.pipe';
|
import { MarkdownPipe } from 'ngx-markdown/src/markdown.pipe';
|
||||||
|
|
||||||
const EVENT_TYPES_TEXT_MAP = {
|
const EVENT_TYPES_TEXT_MAP = {
|
||||||
REPLICATION: 'Replication finished',
|
REPLICATION: 'Replication status changed',
|
||||||
PUSH_ARTIFACT: 'Artifact pushed',
|
PUSH_ARTIFACT: 'Artifact pushed',
|
||||||
PULL_ARTIFACT: 'Artifact pulled',
|
PULL_ARTIFACT: 'Artifact pulled',
|
||||||
DELETE_ARTIFACT: 'Artifact deleted',
|
DELETE_ARTIFACT: 'Artifact deleted',
|
||||||
|
@ -1,20 +1,11 @@
|
|||||||
<div [class.alert-app-level]="!isAppLevel" *ngIf="globalMessageOpened">
|
<div *ngIf="globalMessageOpened">
|
||||||
<clr-alert
|
<clr-alert
|
||||||
[clrAlertType]="globalMessage.type"
|
[clrAlertType]="globalMessage.type"
|
||||||
[clrAlertClosable]="!needAuth"
|
[clrAlertAppLevel]="false"
|
||||||
[clrAlertAppLevel]="isAppLevel"
|
|
||||||
[(clrAlertClosed)]="!globalMessageOpened"
|
[(clrAlertClosed)]="!globalMessageOpened"
|
||||||
(clrAlertClosedChange)="onClose()">
|
(clrAlertClosedChange)="onClose()">
|
||||||
<div class="alert-item">
|
<clr-alert-item class="flex-center">
|
||||||
<span class="alert-text">{{ message }}</span>
|
<span class="alert-text">{{ message }}</span>
|
||||||
<div class="alert-actions alert-style" *ngIf="needAuth">
|
</clr-alert-item>
|
||||||
<button class="btn alert-action" (click)="signIn()">
|
|
||||||
{{ 'BUTTON.LOG_IN' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</clr-alert>
|
</clr-alert>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
*ngIf="globalMessageOpened && needAuth && !isFromGlobalSearch()"
|
|
||||||
class="mask-layer"></div>
|
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mask-layer {
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-text {
|
.alert-text {
|
||||||
flex: 0 1 auto !important;
|
flex: 0 1 auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
@ -27,32 +27,4 @@ describe('MessageComponent', () => {
|
|||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open mask layer when unauthorized', async () => {
|
|
||||||
component.globalMessageOpened = true;
|
|
||||||
component.globalMessage = Message.newMessage(
|
|
||||||
401,
|
|
||||||
'unauthorized',
|
|
||||||
AlertType.DANGER
|
|
||||||
);
|
|
||||||
fixture.detectChanges();
|
|
||||||
await fixture.whenStable();
|
|
||||||
const ele: HTMLDivElement =
|
|
||||||
fixture.nativeElement.querySelector('.mask-layer');
|
|
||||||
expect(ele).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should not open mask layer when it's not unauthorized", async () => {
|
|
||||||
component.globalMessageOpened = true;
|
|
||||||
component.globalMessage = Message.newMessage(
|
|
||||||
403,
|
|
||||||
'forbidden',
|
|
||||||
AlertType.WARNING
|
|
||||||
);
|
|
||||||
fixture.detectChanges();
|
|
||||||
await fixture.whenStable();
|
|
||||||
const ele: HTMLDivElement =
|
|
||||||
fixture.nativeElement.querySelector('.mask-layer');
|
|
||||||
expect(ele).toBeFalsy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -17,14 +17,7 @@ import { Subscription } from 'rxjs';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Message } from './message';
|
import { Message } from './message';
|
||||||
import { MessageService } from './message.service';
|
import { MessageService } from './message.service';
|
||||||
import {
|
import { dismissInterval } from '../../entities/shared.const';
|
||||||
CommonRoutes,
|
|
||||||
dismissInterval,
|
|
||||||
httpStatusCode,
|
|
||||||
} from '../../entities/shared.const';
|
|
||||||
import { delUrlParam } from '../../units/utils';
|
|
||||||
import { UN_LOGGED_PARAM, YES } from '../../../account/sign-in/sign-in.service';
|
|
||||||
import { SessionService } from '../../services/session.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'global-message',
|
selector: 'global-message',
|
||||||
@ -32,48 +25,27 @@ import { SessionService } from '../../services/session.service';
|
|||||||
styleUrls: ['message.component.scss'],
|
styleUrls: ['message.component.scss'],
|
||||||
})
|
})
|
||||||
export class MessageComponent implements OnInit, OnDestroy {
|
export class MessageComponent implements OnInit, OnDestroy {
|
||||||
@Input() isAppLevel: boolean;
|
|
||||||
globalMessage: Message = new Message();
|
globalMessage: Message = new Message();
|
||||||
globalMessageOpened: boolean = false;
|
globalMessageOpened: boolean = false;
|
||||||
messageText: string = '';
|
messageText: string = '';
|
||||||
timer: any = null;
|
timer: any = null;
|
||||||
|
|
||||||
appLevelMsgSub: Subscription;
|
|
||||||
msgSub: Subscription;
|
msgSub: Subscription;
|
||||||
clearSub: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private elementRef: ElementRef,
|
private elementRef: ElementRef,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService
|
||||||
private session: SessionService
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Only subscribe application level message
|
if (!this.msgSub) {
|
||||||
if (this.isAppLevel) {
|
|
||||||
this.appLevelMsgSub =
|
|
||||||
this.messageService.appLevelAnnounced$.subscribe(message => {
|
|
||||||
this.globalMessageOpened = true;
|
|
||||||
this.globalMessage = message;
|
|
||||||
this.checkLoginStatus();
|
|
||||||
this.messageText = message.message;
|
|
||||||
|
|
||||||
this.translateMessage(message);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Only subscribe general messages
|
|
||||||
this.msgSub = this.messageService.messageAnnounced$.subscribe(
|
this.msgSub = this.messageService.messageAnnounced$.subscribe(
|
||||||
message => {
|
message => {
|
||||||
this.globalMessageOpened = true;
|
this.globalMessageOpened = true;
|
||||||
this.globalMessage = message;
|
this.globalMessage = message;
|
||||||
this.checkLoginStatus();
|
|
||||||
this.messageText = message.message;
|
this.messageText = message.message;
|
||||||
|
|
||||||
this.translateMessage(message);
|
this.translateMessage(message);
|
||||||
|
|
||||||
// Make the message alert bar dismiss after several intervals.
|
// Make the message alert bar dismiss after several intervals.
|
||||||
// Only for this case
|
// Only for this case
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
@ -84,38 +56,15 @@ export class MessageComponent implements OnInit, OnDestroy {
|
|||||||
() => this.onClose(),
|
() => this.onClose(),
|
||||||
dismissInterval
|
dismissInterval
|
||||||
);
|
);
|
||||||
|
|
||||||
// Hack the Clarity Alert style with native dom
|
|
||||||
setTimeout(() => {
|
|
||||||
let nativeDom: any = this.elementRef.nativeElement;
|
|
||||||
let queryDoms: any[] =
|
|
||||||
nativeDom.getElementsByClassName('alert');
|
|
||||||
if (queryDoms && queryDoms.length > 0) {
|
|
||||||
let hackDom: any = queryDoms[0];
|
|
||||||
hackDom.className +=
|
|
||||||
' alert-global alert-global-align';
|
|
||||||
}
|
|
||||||
}, 0);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearSub = this.messageService.clearChan$.subscribe(clear => {
|
|
||||||
this.onClose();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.appLevelMsgSub) {
|
|
||||||
this.appLevelMsgSub.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.msgSub) {
|
if (this.msgSub) {
|
||||||
this.msgSub.unsubscribe();
|
this.msgSub.unsubscribe();
|
||||||
}
|
this.msgSub = null;
|
||||||
|
|
||||||
if (this.clearSub) {
|
|
||||||
this.clearSub.unsubscribe();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,57 +86,14 @@ export class MessageComponent implements OnInit, OnDestroy {
|
|||||||
.get(key, { param: param })
|
.get(key, { param: param })
|
||||||
.subscribe((res: string) => (this.messageText = res));
|
.subscribe((res: string) => (this.messageText = res));
|
||||||
}
|
}
|
||||||
|
|
||||||
public get needAuth(): boolean {
|
|
||||||
return this.globalMessage
|
|
||||||
? this.globalMessage.statusCode === httpStatusCode.Unauthorized
|
|
||||||
: false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show message text
|
// Show message text
|
||||||
public get message(): string {
|
public get message(): string {
|
||||||
return this.messageText;
|
return this.messageText;
|
||||||
}
|
}
|
||||||
|
|
||||||
signIn(): void {
|
|
||||||
// remove queryParam UN_LOGGED_PARAM of redirect url
|
|
||||||
const url = delUrlParam(this.router.url, UN_LOGGED_PARAM);
|
|
||||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], {
|
|
||||||
queryParams: { redirect_url: url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
if (this.timer) {
|
if (this.timer) {
|
||||||
clearTimeout(this.timer);
|
clearTimeout(this.timer);
|
||||||
}
|
}
|
||||||
this.globalMessageOpened = false;
|
this.globalMessageOpened = false;
|
||||||
}
|
}
|
||||||
// if navigate from global search(un-logged users visit public project)
|
|
||||||
isFromGlobalSearch(): boolean {
|
|
||||||
return this.route.snapshot.queryParams[UN_LOGGED_PARAM] === YES;
|
|
||||||
}
|
|
||||||
checkLoginStatus() {
|
|
||||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
|
||||||
// User session timed out, then redirect to sign-in page
|
|
||||||
if (
|
|
||||||
this.session.getCurrentUser() &&
|
|
||||||
!this.isSignInUrl() &&
|
|
||||||
this.route.snapshot.queryParams[UN_LOGGED_PARAM] !== YES
|
|
||||||
) {
|
|
||||||
const url = delUrlParam(this.router.url, UN_LOGGED_PARAM);
|
|
||||||
this.session.clear(); // because of SignInGuard, must clear user session before navigating to sign-in page
|
|
||||||
this.router.navigate([CommonRoutes.EMBEDDED_SIGN_IN], {
|
|
||||||
queryParams: { redirect_url: url },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isSignInUrl(): boolean {
|
|
||||||
const url: string =
|
|
||||||
this.router.url?.indexOf('?') === -1
|
|
||||||
? this.router.url
|
|
||||||
: this.router.url?.split('?')[0];
|
|
||||||
return url === CommonRoutes.EMBEDDED_SIGN_IN;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export const dismissInterval = 10 * 1000;
|
|||||||
export const httpStatusCode = {
|
export const httpStatusCode = {
|
||||||
Unauthorized: 401,
|
Unauthorized: 401,
|
||||||
Forbidden: 403,
|
Forbidden: 403,
|
||||||
|
AppLevelWarning: 503,
|
||||||
};
|
};
|
||||||
export const enum ConfirmationTargets {
|
export const enum ConfirmationTargets {
|
||||||
EMPTY,
|
EMPTY,
|
||||||
|
@ -74,8 +74,8 @@ export class MessageHandlerService implements ErrorHandler {
|
|||||||
|
|
||||||
public handleReadOnly(): void {
|
public handleReadOnly(): void {
|
||||||
this.msgService.announceAppLevelMessage(
|
this.msgService.announceAppLevelMessage(
|
||||||
503,
|
httpStatusCode.AppLevelWarning,
|
||||||
'REPO_READ_ONLY',
|
AppLevelMessage.REPO_READ_ONLY,
|
||||||
AlertType.WARNING
|
AlertType.WARNING
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -131,3 +131,7 @@ export class MessageHandlerService implements ErrorHandler {
|
|||||||
this.showInfo(log);
|
this.showInfo(log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AppLevelMessage {
|
||||||
|
REPO_READ_ONLY = 'REPO_READ_ONLY',
|
||||||
|
}
|
||||||
|
@ -27,6 +27,7 @@ import { FlushAll } from '../units/cache-util';
|
|||||||
import { SignInCredential } from '../../account/sign-in/sign-in-credential';
|
import { SignInCredential } from '../../account/sign-in/sign-in-credential';
|
||||||
import { ProjectMemberEntity } from '../../../../ng-swagger-gen/models/project-member-entity';
|
import { ProjectMemberEntity } from '../../../../ng-swagger-gen/models/project-member-entity';
|
||||||
import { DeFaultLang } from '../entities/shared.const';
|
import { DeFaultLang } from '../entities/shared.const';
|
||||||
|
import { JobServiceDashboardHealthCheckService } from '../../base/left-side-nav/job-service-dashboard/job-service-dashboard-health-check.service';
|
||||||
|
|
||||||
const signInUrl = '/c/login';
|
const signInUrl = '/c/login';
|
||||||
const currentUserEndpoint = CURRENT_BASE_HREF + '/users/current';
|
const currentUserEndpoint = CURRENT_BASE_HREF + '/users/current';
|
||||||
@ -54,7 +55,8 @@ export class SessionService {
|
|||||||
projectMembers: ProjectMemberEntity[];
|
projectMembers: ProjectMemberEntity[];
|
||||||
constructor(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
public sessionViewmodel: SessionViewmodelFactory
|
public sessionViewmodel: SessionViewmodelFactory,
|
||||||
|
private jobServiceDashboardHealthCheckService: JobServiceDashboardHealthCheckService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// Handle the related exceptions
|
// Handle the related exceptions
|
||||||
@ -94,12 +96,12 @@ export class SessionService {
|
|||||||
*/
|
*/
|
||||||
retrieveUser(): Observable<SessionUserBackend> {
|
retrieveUser(): Observable<SessionUserBackend> {
|
||||||
return this.http.get(currentUserEndpoint, HTTP_GET_OPTIONS).pipe(
|
return this.http.get(currentUserEndpoint, HTTP_GET_OPTIONS).pipe(
|
||||||
map(
|
map((response: SessionUserBackend) => {
|
||||||
(response: SessionUserBackend) =>
|
this.currentUser = this.sessionViewmodel.getCurrentUser(
|
||||||
(this.currentUser = this.sessionViewmodel.getCurrentUser(
|
response
|
||||||
response
|
) as SessionUser;
|
||||||
) as SessionUser)
|
this.jobServiceDashboardHealthCheckService.checkHealth();
|
||||||
),
|
}),
|
||||||
catchError(error => this.handleError(error))
|
catchError(error => this.handleError(error))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -366,3 +366,11 @@ job-service-dashboard {
|
|||||||
.my-action-item:hover {
|
.my-action-item:hover {
|
||||||
background-color: $label-hover-bg-color !important;
|
background-color: $label-hover-bg-color !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert.alert-warning {
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alerts.alert-info {
|
||||||
|
background-color: $alert-info-bg-color;
|
||||||
|
}
|
||||||
|
@ -47,4 +47,5 @@ $select-all-for-dropdown-color: #4aaed9;
|
|||||||
$normal-border-color: #acbac3;
|
$normal-border-color: #acbac3;
|
||||||
$text-color-job-service-dashboard: #49aeda;
|
$text-color-job-service-dashboard: #49aeda;
|
||||||
$datagrid-numeric-filter-input-bg-color: #21333b;
|
$datagrid-numeric-filter-input-bg-color: #21333b;
|
||||||
|
$alert-info-bg-color: none;
|
||||||
@import "./common.scss";
|
@import "./common.scss";
|
||||||
|
@ -48,4 +48,5 @@ $select-all-for-dropdown-color: #0072a3;
|
|||||||
$normal-border-color: #6a7a81;
|
$normal-border-color: #6a7a81;
|
||||||
$text-color-job-service-dashboard: #0072a3;
|
$text-color-job-service-dashboard: #0072a3;
|
||||||
$datagrid-numeric-filter-input-bg-color: unset;
|
$datagrid-numeric-filter-input-bg-color: unset;
|
||||||
|
$alert-info-bg-color: #107eb0;
|
||||||
@import "./common.scss";
|
@import "./common.scss";
|
||||||
|
@ -1786,7 +1786,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSIEREN — Pausiert die Ausführung aller Pläne.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSIEREN — Pausiert die Ausführung aller Pläne.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "FORTSETZEN — Setzt die Ausführung aller Pläne fort.",
|
"SCHEDULE_RESUME_BTN_INFO": "FORTSETZEN — Setzt die Ausführung aller Pläne fort.",
|
||||||
"WORKER_FREE_BTN_INFO": "Halte den aktuell laufenden Job an um den Arbeiter zu befreien.",
|
"WORKER_FREE_BTN_INFO": "Halte den aktuell laufenden Job an um den Arbeiter zu befreien.",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
@ -1786,7 +1786,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedules to execute.",
|
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedules to execute.",
|
||||||
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
@ -1783,7 +1783,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedules to execute.",
|
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedules to execute.",
|
||||||
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
@ -1753,7 +1753,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Met en pause toutes les programmations de tâches.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Met en pause toutes les programmations de tâches.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "REPRENDRE — Reprend les files d'attente de tâches à exécuter.",
|
"SCHEDULE_RESUME_BTN_INFO": "REPRENDRE — Reprend les files d'attente de tâches à exécuter.",
|
||||||
"WORKER_FREE_BTN_INFO": "Arrête les tâches en cours pour libérer le worker",
|
"WORKER_FREE_BTN_INFO": "Arrête les tâches en cours pour libérer le worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Ouvrir",
|
"OPEN": "Ouvrir",
|
||||||
|
@ -1783,7 +1783,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
||||||
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
@ -1786,7 +1786,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
||||||
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
@ -1783,7 +1783,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "暂停 — 暂停所有定时任务,暂停中的定时任务将不会被执行。",
|
"SCHEDULE_PAUSE_BTN_INFO": "暂停 — 暂停所有定时任务,暂停中的定时任务将不会被执行。",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "重启 — 重启所有定时任务,定时任务在触发时会正常执行。",
|
"SCHEDULE_RESUME_BTN_INFO": "重启 — 重启所有定时任务,定时任务在触发时会正常执行。",
|
||||||
"WORKER_FREE_BTN_INFO": "停下选中的工作者当前正在执行的任务以便释放该工作者,被释放的工作者会继续执行其他任务。",
|
"WORKER_FREE_BTN_INFO": "停下选中的工作者当前正在执行的任务以便释放该工作者,被释放的工作者会继续执行其他任务。",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "检测到有任务已等待执行超过 24 小时。 请检查任务中心的",
|
||||||
|
"WAITING_TOO_LONG_2": "仪表盘。",
|
||||||
|
"WAITING_TOO_LONG_3": "更多详情,请参考 ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki。"
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "打开",
|
"OPEN": "打开",
|
||||||
|
@ -1776,7 +1776,11 @@
|
|||||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules to execute.",
|
||||||
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
"SCHEDULE_RESUME_BTN_INFO": "RESUME — Resume all schedule to execute.",
|
||||||
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
"WORKER_FREE_BTN_INFO": "Stop the current running job to free the worker",
|
||||||
"CRON": "Cron"
|
"CRON": "Cron",
|
||||||
|
"WAITING_TOO_LONG_1": "Certain jobs have been pending for execution for over 24 hours. Please check the job service ",
|
||||||
|
"WAITING_TOO_LONG_2": "dashboard.",
|
||||||
|
"WAITING_TOO_LONG_3": "For more details, please refer to the ",
|
||||||
|
"WAITING_TOO_LONG_4": "Wiki."
|
||||||
},
|
},
|
||||||
"CLARITY": {
|
"CLARITY": {
|
||||||
"OPEN": "Open",
|
"OPEN": "Open",
|
||||||
|
Loading…
Reference in New Issue
Block a user