mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-26 16:51:47 +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>
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
<navigator (showDialogModalAction)="openModal($event)"></navigator>
|
||||
<search-result></search-result>
|
||||
<div
|
||||
|
@ -11,7 +11,7 @@
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AppConfigService } from './services/app-config.service';
|
||||
@ -31,20 +31,28 @@ import {
|
||||
} from './shared/entities/shared.const';
|
||||
import { SkinableConfig } from './services/skinable-config.service';
|
||||
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({
|
||||
selector: 'harbor-app',
|
||||
templateUrl: 'app.component.html',
|
||||
})
|
||||
export class AppComponent {
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
||||
styleMode: string = this.themeArray[0].showStyle;
|
||||
interval: any;
|
||||
constructor(
|
||||
private translate: TranslateService,
|
||||
private appConfigService: AppConfigService,
|
||||
private titleService: Title,
|
||||
public theme: ThemeService,
|
||||
private skinableConfig: SkinableConfig
|
||||
private skinableConfig: SkinableConfig,
|
||||
private jobServiceDashboardHealthCheckService: JobServiceDashboardHealthCheckService,
|
||||
private sessionService: SessionService
|
||||
) {
|
||||
// init language
|
||||
this.initLanguage();
|
||||
@ -66,6 +74,26 @@ export class AppComponent {
|
||||
});
|
||||
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() {
|
||||
let styleMode = this.themeArray[0].showStyle;
|
||||
const localHasStyle =
|
||||
|
@ -22,6 +22,7 @@ import { PasswordSettingComponent } from './password-setting/password-setting.co
|
||||
import { AccountSettingsModalComponent } from './account-settings/account-settings-modal.component';
|
||||
import { ForgotPasswordComponent } from './password-setting/forgot-password/forgot-password.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 = [
|
||||
{
|
||||
@ -172,6 +173,7 @@ const routes: Routes = [
|
||||
AccountSettingsModalComponent,
|
||||
ForgotPasswordComponent,
|
||||
GlobalConfirmationDialogComponent,
|
||||
AppLevelAlertsComponent,
|
||||
],
|
||||
})
|
||||
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>
|
||||
<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 text-center">
|
||||
<clr-icon shape="info-standard" size="20"></clr-icon>
|
||||
@ -26,8 +26,8 @@
|
||||
shape="times"
|
||||
size="24"></clr-icon>
|
||||
</div>
|
||||
</div>
|
||||
<global-message [isAppLevel]="true"></global-message>
|
||||
</div>-->
|
||||
<app-app-level-alerts></app-app-level-alerts>
|
||||
<navigator
|
||||
(showAccountSettingsModal)="openModal($event)"
|
||||
(showDialogModalAction)="openModal($event)"></navigator>
|
||||
@ -39,7 +39,7 @@
|
||||
[class.content-area-override]="!shouldOverrideContent"
|
||||
[class.start-content-padding]="shouldOverrideContent"
|
||||
(scroll)="publishScrollEvent()">
|
||||
<global-message [isAppLevel]="false"></global-message>
|
||||
<global-message></global-message>
|
||||
<!-- Only appear when searching -->
|
||||
<search-result></search-result>
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -38,43 +38,3 @@ clr-vertical-nav {
|
||||
.font-size-13 {
|
||||
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 { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
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';
|
||||
|
||||
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 = {
|
||||
getCurrentUserInfo() {
|
||||
return of({});
|
||||
@ -120,7 +101,6 @@ describe('HarborShellComponent', () => {
|
||||
useValue: fakeSearchTriggerService,
|
||||
},
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: ScannerService, useValue: fakeScannerService },
|
||||
{
|
||||
provide: MessageHandlerService,
|
||||
useValue: mockMessageHandlerService,
|
||||
@ -143,7 +123,6 @@ describe('HarborShellComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HarborShellComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.showScannerInfo = true;
|
||||
component.accountSettingsModal = TestBed.createComponent(
|
||||
AccountSettingsModalComponent
|
||||
).componentInstance;
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
ChangeDetectorRef,
|
||||
} from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { forkJoin, Observable, Subscription } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
@ -34,19 +34,14 @@ import {
|
||||
CONFIG_AUTH_MODE,
|
||||
} from '../../shared/entities/shared.const';
|
||||
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 { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||
import {
|
||||
EventService,
|
||||
HarborEvent,
|
||||
} 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';
|
||||
|
||||
@Component({
|
||||
@ -73,10 +68,7 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
|
||||
searchSub: Subscription;
|
||||
searchCloseSub: Subscription;
|
||||
showScannerInfo: boolean = false;
|
||||
scannerDocUrl: string = SCANNERS_DOC;
|
||||
themeArray: ThemeInterface[] = clone(THEME_ARRAY);
|
||||
|
||||
styleMode = this.themeArray[0].showStyle;
|
||||
@ViewChild('scrollDiv') scrollDiv: ElementRef;
|
||||
scrollToPositionSub: Subscription;
|
||||
@ -86,7 +78,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
private session: SessionService,
|
||||
private searchTrigger: SearchTriggerService,
|
||||
private appConfigService: AppConfigService,
|
||||
private scannerService: ScannerService,
|
||||
public theme: ThemeService,
|
||||
private event: EventService,
|
||||
private cd: ChangeDetectorRef
|
||||
@ -117,16 +108,6 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
this.isSearchResultsOpened = false;
|
||||
}
|
||||
);
|
||||
if (
|
||||
!(
|
||||
localStorage &&
|
||||
localStorage.getItem(HAS_SHOWED_SCANNER_INFO) === YES
|
||||
)
|
||||
) {
|
||||
if (this.isSystemAdmin) {
|
||||
this.getDefaultScanner();
|
||||
}
|
||||
}
|
||||
// set local in app
|
||||
if (localStorage) {
|
||||
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 {
|
||||
if (this.searchSub) {
|
||||
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-cell>{{ j.job_type }}</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-row>
|
||||
<clr-dg-footer>
|
||||
|
@ -7,3 +7,16 @@
|
||||
.refresh-btn {
|
||||
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 { errorHandler } from '../../../../shared/units/shared.utils';
|
||||
import { JobServiceDashboardSharedDataService } from '../job-service-dashboard-shared-data.service';
|
||||
import { HEALTHY_TIME } from '../job-service-dashboard-health-check.service';
|
||||
|
||||
@Component({
|
||||
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 {
|
||||
width: 30rem;
|
||||
width: 31rem;
|
||||
height: 35rem;
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ describe('WebhookService', () => {
|
||||
expect(service).toBeTruthy();
|
||||
const eventType: string = 'REPLICATION';
|
||||
expect(service.eventTypeToText(eventType)).toEqual(
|
||||
'Replication finished'
|
||||
'Replication status changed'
|
||||
);
|
||||
const mockedEventType: string = 'TEST';
|
||||
expect(service.eventTypeToText(mockedEventType)).toEqual('TEST');
|
||||
|
@ -15,7 +15,7 @@ import { Injectable } from '@angular/core';
|
||||
import { MarkdownPipe } from 'ngx-markdown/src/markdown.pipe';
|
||||
|
||||
const EVENT_TYPES_TEXT_MAP = {
|
||||
REPLICATION: 'Replication finished',
|
||||
REPLICATION: 'Replication status changed',
|
||||
PUSH_ARTIFACT: 'Artifact pushed',
|
||||
PULL_ARTIFACT: 'Artifact pulled',
|
||||
DELETE_ARTIFACT: 'Artifact deleted',
|
||||
|
@ -1,20 +1,11 @@
|
||||
<div [class.alert-app-level]="!isAppLevel" *ngIf="globalMessageOpened">
|
||||
<div *ngIf="globalMessageOpened">
|
||||
<clr-alert
|
||||
[clrAlertType]="globalMessage.type"
|
||||
[clrAlertClosable]="!needAuth"
|
||||
[clrAlertAppLevel]="isAppLevel"
|
||||
[clrAlertAppLevel]="false"
|
||||
[(clrAlertClosed)]="!globalMessageOpened"
|
||||
(clrAlertClosedChange)="onClose()">
|
||||
<div class="alert-item">
|
||||
<clr-alert-item class="flex-center">
|
||||
<span class="alert-text">{{ message }}</span>
|
||||
<div class="alert-actions alert-style" *ngIf="needAuth">
|
||||
<button class="btn alert-action" (click)="signIn()">
|
||||
{{ 'BUTTON.LOG_IN' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</clr-alert-item>
|
||||
</clr-alert>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="globalMessageOpened && needAuth && !isFromGlobalSearch()"
|
||||
class="mask-layer"></div>
|
||||
|
@ -2,13 +2,10 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mask-layer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.alert-text {
|
||||
flex: 0 1 auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
@ -27,32 +27,4 @@ describe('MessageComponent', () => {
|
||||
it('should create', () => {
|
||||
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 { Message } from './message';
|
||||
import { MessageService } from './message.service';
|
||||
import {
|
||||
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';
|
||||
import { dismissInterval } from '../../entities/shared.const';
|
||||
|
||||
@Component({
|
||||
selector: 'global-message',
|
||||
@ -32,48 +25,27 @@ import { SessionService } from '../../services/session.service';
|
||||
styleUrls: ['message.component.scss'],
|
||||
})
|
||||
export class MessageComponent implements OnInit, OnDestroy {
|
||||
@Input() isAppLevel: boolean;
|
||||
globalMessage: Message = new Message();
|
||||
globalMessageOpened: boolean = false;
|
||||
messageText: string = '';
|
||||
timer: any = null;
|
||||
|
||||
appLevelMsgSub: Subscription;
|
||||
msgSub: Subscription;
|
||||
clearSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private elementRef: ElementRef,
|
||||
private messageService: MessageService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private translate: TranslateService,
|
||||
private session: SessionService
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Only subscribe application level message
|
||||
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
|
||||
if (!this.msgSub) {
|
||||
this.msgSub = this.messageService.messageAnnounced$.subscribe(
|
||||
message => {
|
||||
this.globalMessageOpened = true;
|
||||
this.globalMessage = message;
|
||||
this.checkLoginStatus();
|
||||
this.messageText = message.message;
|
||||
|
||||
this.translateMessage(message);
|
||||
|
||||
// Make the message alert bar dismiss after several intervals.
|
||||
// Only for this case
|
||||
if (this.timer) {
|
||||
@ -84,38 +56,15 @@ export class MessageComponent implements OnInit, OnDestroy {
|
||||
() => this.onClose(),
|
||||
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() {
|
||||
if (this.appLevelMsgSub) {
|
||||
this.appLevelMsgSub.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.msgSub) {
|
||||
this.msgSub.unsubscribe();
|
||||
}
|
||||
|
||||
if (this.clearSub) {
|
||||
this.clearSub.unsubscribe();
|
||||
this.msgSub = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,57 +86,14 @@ export class MessageComponent implements OnInit, OnDestroy {
|
||||
.get(key, { param: param })
|
||||
.subscribe((res: string) => (this.messageText = res));
|
||||
}
|
||||
|
||||
public get needAuth(): boolean {
|
||||
return this.globalMessage
|
||||
? this.globalMessage.statusCode === httpStatusCode.Unauthorized
|
||||
: false;
|
||||
}
|
||||
|
||||
// Show message text
|
||||
public get message(): string {
|
||||
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() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
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 = {
|
||||
Unauthorized: 401,
|
||||
Forbidden: 403,
|
||||
AppLevelWarning: 503,
|
||||
};
|
||||
export const enum ConfirmationTargets {
|
||||
EMPTY,
|
||||
|
@ -74,8 +74,8 @@ export class MessageHandlerService implements ErrorHandler {
|
||||
|
||||
public handleReadOnly(): void {
|
||||
this.msgService.announceAppLevelMessage(
|
||||
503,
|
||||
'REPO_READ_ONLY',
|
||||
httpStatusCode.AppLevelWarning,
|
||||
AppLevelMessage.REPO_READ_ONLY,
|
||||
AlertType.WARNING
|
||||
);
|
||||
}
|
||||
@ -131,3 +131,7 @@ export class MessageHandlerService implements ErrorHandler {
|
||||
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 { ProjectMemberEntity } from '../../../../ng-swagger-gen/models/project-member-entity';
|
||||
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 currentUserEndpoint = CURRENT_BASE_HREF + '/users/current';
|
||||
@ -54,7 +55,8 @@ export class SessionService {
|
||||
projectMembers: ProjectMemberEntity[];
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
public sessionViewmodel: SessionViewmodelFactory
|
||||
public sessionViewmodel: SessionViewmodelFactory,
|
||||
private jobServiceDashboardHealthCheckService: JobServiceDashboardHealthCheckService
|
||||
) {}
|
||||
|
||||
// Handle the related exceptions
|
||||
@ -94,12 +96,12 @@ export class SessionService {
|
||||
*/
|
||||
retrieveUser(): Observable<SessionUserBackend> {
|
||||
return this.http.get(currentUserEndpoint, HTTP_GET_OPTIONS).pipe(
|
||||
map(
|
||||
(response: SessionUserBackend) =>
|
||||
(this.currentUser = this.sessionViewmodel.getCurrentUser(
|
||||
response
|
||||
) as SessionUser)
|
||||
),
|
||||
map((response: SessionUserBackend) => {
|
||||
this.currentUser = this.sessionViewmodel.getCurrentUser(
|
||||
response
|
||||
) as SessionUser;
|
||||
this.jobServiceDashboardHealthCheckService.checkHealth();
|
||||
}),
|
||||
catchError(error => this.handleError(error))
|
||||
);
|
||||
}
|
||||
|
@ -366,3 +366,11 @@ job-service-dashboard {
|
||||
.my-action-item:hover {
|
||||
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;
|
||||
$text-color-job-service-dashboard: #49aeda;
|
||||
$datagrid-numeric-filter-input-bg-color: #21333b;
|
||||
$alert-info-bg-color: none;
|
||||
@import "./common.scss";
|
||||
|
@ -48,4 +48,5 @@ $select-all-for-dropdown-color: #0072a3;
|
||||
$normal-border-color: #6a7a81;
|
||||
$text-color-job-service-dashboard: #0072a3;
|
||||
$datagrid-numeric-filter-input-bg-color: unset;
|
||||
$alert-info-bg-color: #107eb0;
|
||||
@import "./common.scss";
|
||||
|
@ -1786,7 +1786,11 @@
|
||||
"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.",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
@ -1786,7 +1786,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause 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",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
@ -1783,7 +1783,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause 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",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
@ -1753,7 +1753,11 @@
|
||||
"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.",
|
||||
"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": {
|
||||
"OPEN": "Ouvrir",
|
||||
|
@ -1783,7 +1783,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules 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",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
@ -1786,7 +1786,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules 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",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
@ -1783,7 +1783,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "暂停 — 暂停所有定时任务,暂停中的定时任务将不会被执行。",
|
||||
"SCHEDULE_RESUME_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": {
|
||||
"OPEN": "打开",
|
||||
|
@ -1776,7 +1776,11 @@
|
||||
"SCHEDULE_PAUSE_BTN_INFO": "PAUSE — Pause all schedules 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",
|
||||
"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": {
|
||||
"OPEN": "Open",
|
||||
|
Loading…
Reference in New Issue
Block a user