import { NoSsoGuardService } from './shared/services/no-sso-guard.service';
import { UpgradeComponent } from './ceph/cluster/upgrade/upgrade.component';
import { CephfsVolumeFormComponent } from './ceph/cephfs/cephfs-form/cephfs-form.component';
+import { UpgradeProgressComponent } from './ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component';
@Injectable()
export class PerformanceCounterBreadcrumbsResolver extends BreadcrumbsResolver {
{
path: 'upgrade',
canActivate: [ModuleStatusGuardService],
- component: UpgradeComponent,
data: {
moduleStatusGuardConfig: {
uiApiPath: 'orchestrator',
header: 'Orchestrator is not available'
},
breadcrumbs: 'Cluster/Upgrade'
- }
+ },
+ children: [
+ {
+ path: '',
+ component: UpgradeComponent
+ },
+ {
+ path: 'progress',
+ component: UpgradeProgressComponent,
+ data: { breadcrumbs: 'Progress' }
+ }
+ ]
},
{
path: 'perf_counters/:type/:id',
NgbDropdownModule,
NgbNavModule,
NgbPopoverModule,
+ NgbProgressbarModule,
NgbTimepickerModule,
NgbTooltipModule,
NgbTypeaheadModule
import { TelemetryComponent } from './telemetry/telemetry.component';
import { UpgradeComponent } from './upgrade/upgrade.component';
import { UpgradeStartModalComponent } from './upgrade/upgrade-form/upgrade-start-modal.component';
+import { UpgradeProgressComponent } from './upgrade/upgrade-progress/upgrade-progress.component';
@NgModule({
imports: [
NgbDatepickerModule,
NgbPopoverModule,
NgbDropdownModule,
- NgxPipeFunctionModule
+ NgxPipeFunctionModule,
+ NgbProgressbarModule
],
declarations: [
HostsComponent,
CreateClusterComponent,
CreateClusterReviewComponent,
UpgradeComponent,
- UpgradeStartModalComponent
+ UpgradeStartModalComponent,
+ UpgradeProgressComponent
],
providers: [NgbActiveModal]
})
[formGroup]="upgradeForm"
novalidate>
<div class="modal-body">
+ <cd-alert-panel type="warning"
+ spacingClass="mb-3"
+ *ngIf="showImageField"
+ i18n>Make sure to put the correct image. Passing an incorrect image can lead the cluster into an undesired state.</cd-alert-panel>
<div class="form-group row">
- <label class="cd-col-form-label required"
+ <label class="cd-col-form-label"
+ [ngClass]="{'required': !showImageField}"
for="availableVersions"
i18n>New Version</label>
<div class="cd-col-form-input">
i18n>This field is required!</span>
</div>
</div>
+
+ <div class="form-group row">
+ <div class="cd-col-form-offset">
+ <div class="custom-control custom-checkbox">
+ <input type="checkbox"
+ class="custom-control-input"
+ id="useImage"
+ name="useImage"
+ formControlName="useImage"
+ (click)="useImage()">
+ <label class="custom-control-label"
+ for="useImage"
+ i18n>Use image</label>
+ </div>
+ </div>
+ </div>
+
+ <!-- Custom image name input-->
+ <div class="form-group row"
+ *ngIf="showImageField">
+ <label class="cd-col-form-label required"
+ for="customImageName"
+ i18n>Image</label>
+ <div class="cd-col-form-input">
+ <input type="text"
+ class="form-control"
+ id="customImageName"
+ name="customImageName"
+ formControlName="customImageName">
+ <span class="invalid-feedback"
+ *ngIf="upgradeForm.showError('customImageName', formDir, 'required')"
+ i18n>This field is required!</span>
+ </div>
+ </div>
</div>
<div class="modal-footer">
- <cd-form-button-panel *ngIf="versions"
- (submitActionEvent)="startUpgrade()"
+ <cd-form-button-panel (submitActionEvent)="startUpgrade()"
[form]="upgradeForm"
[submitText]="actionLabels.START_UPGRADE"></cd-form-button-panel>
</div>
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { SharedModule } from '~/app/shared/shared.module';
import { ToastrModule } from 'ngx-toastr';
+import { RouterTestingModule } from '@angular/router/testing';
describe('UpgradeComponent', () => {
let component: UpgradeComponent;
let fixture: ComponentFixture<UpgradeComponent>;
configureTestBed({
- imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot()],
+ imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), RouterTestingModule],
schemas: [NO_ERRORS_SCHEMA],
declarations: [UpgradeComponent],
providers: [UpgradeService]
icons = Icons;
versions: string[];
+ showImageField = false;
+
constructor(
public actionLabels: ActionLabelsI18n,
private authStorageService: AuthStorageService,
ngOnInit() {
this.upgradeForm = new CdFormGroup({
- availableVersions: new FormControl(null, [Validators.required])
+ availableVersions: new FormControl(null, [Validators.required]),
+ useImage: new FormControl(false),
+ customImageName: new FormControl(null)
});
}
startUpgrade() {
- this.upgradeService.start(this.upgradeForm.getValue('availableVersions')).subscribe({
+ const version = this.upgradeForm.getValue('availableVersions');
+ const image = this.upgradeForm.getValue('customImageName');
+ this.upgradeService.start(version, image).subscribe({
next: () => {
this.notificationService.show(
NotificationType.success,
}
});
}
+
+ useImage() {
+ this.showImageField = !this.showImageField;
+ const availableVersionsControl = this.upgradeForm.get('availableVersions');
+ const customImageNameControl = this.upgradeForm.get('customImageName');
+
+ if (this.showImageField) {
+ availableVersionsControl.disable();
+ availableVersionsControl.clearValidators();
+
+ customImageNameControl.setValidators(Validators.required);
+ customImageNameControl.updateValueAndValidity();
+ } else {
+ availableVersionsControl.enable();
+ availableVersionsControl.setValidators(Validators.required);
+ availableVersionsControl.updateValueAndValidity();
+
+ customImageNameControl.clearValidators();
+ }
+ }
}
--- /dev/null
+<div class="d-flex flex-column justify-content-center align-items-center bold"
+ *ngIf="upgradeStatus$ | async as upgradeStatus">
+ <ng-container *ngIf="upgradeStatus.in_progress && !upgradeStatus.is_paused; else upgradePaused">
+ <h3 class="text-center"
+ i18n>
+ <i [ngClass]="[icons.large, icons.spin, icons.spinner]"></i>
+ </h3>
+
+ <h3 class="text-center mt-2">
+ {{ executingTask?.description }}
+ </h3>
+
+ <h5 class="text-center mt-3"
+ i18n>{{ upgradeStatus.which }}</h5>
+ </ng-container>
+
+ <div class="w-50 row h-100 d-flex justify-content-center align-items-center mt-4">
+ <div class="text-center w-75">
+ <ng-container *ngIf="upgradeStatus.services_complete.length > 0">
+ Finished upgrading:
+ <span class="text-success">
+ {{ upgradeStatus.services_complete }}
+ </span>
+ </ng-container>
+ <div class="mt-2">
+ <ngb-progressbar type="info"
+ [value]="executingTask?.progress"
+ [striped]="true"
+ [animated]="!upgradeStatus.is_paused"></ngb-progressbar>
+ </div>
+
+ <p class="card-text text-muted">
+ <span class="float-end">
+ {{ executingTask?.progress || 0 }} %
+ </span>
+ </p>
+ </div>
+ <h4 class="text-center m-2"
+ i18n>{{ upgradeStatus.progress}}</h4>
+
+ <h5 *ngIf="upgradeStatus.in_progress"
+ class="text-center mt-2"
+ i18n>
+ {{ upgradeStatus.message }}
+ </h5>
+
+ <div class="text-center mt-3">
+ <button class="btn btn-light"
+ aria-label="Go back"
+ routerLink="/upgrade"
+ i18n>Back</button>
+ <button *ngIf="upgradeStatus.in_progress && !upgradeStatus.is_paused"
+ (click)="pauseUpgrade()"
+ class="btn btn-light m-2"
+ aria-label="Pause Upgrade"
+ i18n>Pause</button>
+ <button *ngIf="upgradeStatus.in_progress && upgradeStatus.is_paused"
+ (click)="resumeUpgrade()"
+ class="btn btn-light m-2"
+ aria-label="Resume Upgrade"
+ i18n>Resume</button>
+ <button *ngIf="upgradeStatus.in_progress"
+ (click)="stopUpgradeModal()"
+ class="btn btn-danger"
+ aria-label="Stop Upgrade"
+ i18n>Stop</button>
+ </div>
+ </div>
+</div>
+
+<legend class="cd-header"
+ i18n>Cluster logs</legend>
+ <cd-logs [showAuditLogs]="false"
+ [showDaemonLogs]="false"
+ [showNavLinks]="false"
+ [showFilterTools]="false"
+ [showDownloadCopyButton]="false"
+ defaultTab="cluster-logs"
+ [scrollable]="true"></cd-logs>
+
+<ng-template #upgradePaused>
+ <h3 class="text-center mt-3">
+ <i [ngClass]="[icons.large, icons.spinner]"></i>
+ </h3>
+
+ <h3 class="text-center mt-3 mb-4">
+ {{ executingTask?.description }}
+ </h3>
+</ng-template>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { UpgradeProgressComponent } from './upgrade-progress.component';
+import { ToastrModule } from 'ngx-toastr';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { SharedModule } from '~/app/shared/shared.module';
+import { RouterTestingModule } from '@angular/router/testing';
+import { LogsComponent } from '../../logs/logs.component';
+
+describe('UpgradeProgressComponent', () => {
+ let component: UpgradeProgressComponent;
+ let fixture: ComponentFixture<UpgradeProgressComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [UpgradeProgressComponent, LogsComponent],
+ imports: [ToastrModule.forRoot(), HttpClientTestingModule, SharedModule, RouterTestingModule]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(UpgradeProgressComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, OnDestroy, OnInit } from '@angular/core';
+
+import { Observable, ReplaySubject, Subscription } from 'rxjs';
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { Permission } from '~/app/shared/models/permissions';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { UpgradeService } from '~/app/shared/api/upgrade.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { SummaryService } from '~/app/shared/services/summary.service';
+import { ExecutingTask } from '~/app/shared/models/executing-task';
+import { shareReplay, switchMap, tap } from 'rxjs/operators';
+import { Router } from '@angular/router';
+import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
+import { UpgradeStatusInterface } from '~/app/shared/models/upgrade.interface';
+
+@Component({
+ selector: 'cd-upgrade-progress',
+ templateUrl: './upgrade-progress.component.html',
+ styleUrls: ['./upgrade-progress.component.scss']
+})
+export class UpgradeProgressComponent implements OnInit, OnDestroy {
+ permission: Permission;
+ icons = Icons;
+ modalRef: NgbModalRef;
+ interval = new Subscription();
+ executingTask: ExecutingTask;
+
+ upgradeStatus$: Observable<UpgradeStatusInterface>;
+ subject = new ReplaySubject<UpgradeStatusInterface>();
+
+ constructor(
+ private authStorageService: AuthStorageService,
+ private upgradeService: UpgradeService,
+ private notificationService: NotificationService,
+ private modalService: ModalService,
+ private summaryService: SummaryService,
+ private router: Router,
+ private refreshIntervalService: RefreshIntervalService
+ ) {
+ this.permission = this.authStorageService.getPermissions().configOpt;
+ }
+
+ ngOnInit() {
+ this.upgradeStatus$ = this.subject.pipe(
+ switchMap(() => this.upgradeService.status()),
+ tap((status: UpgradeStatusInterface) => {
+ if (!status.in_progress) {
+ this.router.navigate(['/upgrade']);
+ }
+ }),
+ shareReplay(1)
+ );
+
+ this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
+ this.fetchStatus();
+ });
+
+ this.summaryService.subscribe((summary) => {
+ this.executingTask = summary.executing_tasks.filter((tasks) =>
+ tasks.name.includes('progress/Upgrade')
+ )[0];
+ });
+ }
+
+ pauseUpgrade() {
+ this.upgradeService.pause().subscribe({
+ error: (error) => {
+ this.notificationService.show(
+ NotificationType.error,
+ $localize`Failed to pause the upgrade`,
+ error
+ );
+ },
+ complete: () => {
+ this.notificationService.show(NotificationType.success, $localize`The upgrade is paused`);
+ this.fetchStatus();
+ }
+ });
+ }
+
+ fetchStatus() {
+ this.subject.next();
+ }
+
+ resumeUpgrade(modal = false) {
+ this.upgradeService.resume().subscribe({
+ error: (error) => {
+ this.notificationService.show(
+ NotificationType.error,
+ $localize`Failed to resume the upgrade`,
+ error
+ );
+ },
+ complete: () => {
+ this.fetchStatus();
+ this.notificationService.show(NotificationType.success, $localize`Upgrade is resumed`);
+ if (modal) {
+ this.modalRef.close();
+ }
+ }
+ });
+ }
+
+ stopUpgradeModal() {
+ // pause the upgrade meanwhile we get stop confirmation from user
+ this.pauseUpgrade();
+ this.modalRef = this.modalService.show(CriticalConfirmationModalComponent, {
+ itemDescription: 'Upgrade',
+ actionDescription: 'stop',
+ submitAction: () => this.stopUpgrade(),
+ callBackAtionObservable: () => this.resumeUpgrade(true)
+ });
+ }
+
+ stopUpgrade() {
+ this.modalRef.close();
+ this.upgradeService.stop().subscribe({
+ error: (error) => {
+ this.notificationService.show(
+ NotificationType.error,
+ $localize`Failed to stop the upgrade`,
+ error
+ );
+ },
+ complete: () => {
+ this.notificationService.show(NotificationType.success, $localize`The upgrade is stopped`);
+ this.router.navigate(['/upgrade']);
+ }
+ });
+ }
+
+ ngOnDestroy() {
+ this.interval?.unsubscribe();
+ }
+}
i18n-cardTitle
aria-label="New Version"
i18n-aria-label
- id="newVersionAvailable">
- <div class="d-flex flex-column justify-content-center align-items-center"
- *ngIf="info$ | async as info; else checkingForUpgradeStatus">
- <ng-container *ngIf="info.versions.length > 0; else noUpgradesAvailable">
- <div i18n-ngbTooltip
- [ngbTooltip]="(healthData.mgr_map | mgrSummary).total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
- <button class="btn btn-accent mt-2"
- id="upgrade"
- aria-label="Upgrade now"
- (click)="upgradeNow(info.versions[info.versions.length - 1])"
- [disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
- i18n>Upgrade to {{ info.versions[info.versions.length - 1] }}</button>
- </div>
+ id="newVersionAvailable"
+ *ngIf="upgradeStatus$ | async as status">
+ <ng-container *ngIf="status.in_progress; else upgradeStatusTpl">
+ <div class="d-flex flex-column justify-content-center align-items-center mt-2">
+ <h5 i18n
+ *ngIf="status.is_paused; else inProgress">
+ <i [ngClass]="[icons.spinner]"></i>
+ Upgrade is paused {{executingTasks?.progress}}%</h5>
<a class="mt-2 link-primary mb-2"
- (click)="startUpgradeModal()"
- i18n>Select another version...</a>
- </ng-container>
- </div>
+ routerLink="/upgrade/progress"
+ i18n>View Details...</a>
+ </div>
+
+ <ng-template #inProgress>
+ <h5 i18n>
+ <i [ngClass]="[icons.spin, icons.spinner]"></i>
+ Upgrade in progress {{executingTasks?.progress}}%
+ </h5>
+ </ng-template>
+ </ng-container>
</cd-card>
<cd-card class="col-sm-3 px-3 d-flex"
[showDownloadCopyButton]="false"
defaultTab="cluster-logs"
[scrollable]="true"></cd-logs>
+
+
+ <ng-template #upgradeStatusTpl>
+ <div class="d-flex flex-column justify-content-center align-items-center"
+ *ngIf="info$ | async as info; else checkingForUpgradeStatus">
+ <ng-container *ngIf="info.versions.length > 0; else noUpgradesAvailable">
+ <div i18n-ngbTooltip
+ [ngbTooltip]="(healthData.mgr_map | mgrSummary).total <= 1 ? 'To upgrade, you need minimum 2 mgr daemons.' : ''">
+ <button class="btn btn-accent mt-2"
+ id="upgrade"
+ aria-label="Upgrade now"
+ (click)="upgradeNow(info.versions[info.versions.length - 1])"
+ [disabled]="(healthData.mgr_map | mgrSummary).total <= 1"
+ i18n>Upgrade to {{ info.versions[info.versions.length - 1] }}</button>
+ </div>
+ <a class="mt-2 link-primary mb-2"
+ (click)="startUpgradeModal()"
+ i18n>Select another version...</a>
+ </ng-container>
+ </div>
+ </ng-template>
</ng-container>
</div>
Failed to fetch registry informations
</span>
</ng-template>
+
+<ng-template #upgradeProgress>
+ <div class="d-flex flex-column justify-content-center align-items-center mt-2">
+ <h5 i18n>
+ <i [ngClass]="[icons.spin, icons.spinner]"></i>
+ Upgrade in progress {{executingTasks?.progress}}%</h5>
+ <a class="mt-2 link-primary mb-2"
+ routerLink="/upgrade/progress"
+ i18n>View Details...</a>
+ </div>
+</ng-template>
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ToastrModule } from 'ngx-toastr';
import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { RouterTestingModule } from '@angular/router/testing';
export class SummaryServiceMock {
summaryDataSource = new BehaviorSubject({
let fixture: ComponentFixture<UpgradeComponent>;
let upgradeInfoSpy: jasmine.Spy;
let getHealthSpy: jasmine.Spy;
+ let upgradeStatusSpy: jasmine.Spy;
const healthPayload: Record<string, any> = {
health: { status: 'HEALTH_OK' },
};
configureTestBed({
- imports: [HttpClientTestingModule, SharedModule, NgbNavModule, ToastrModule.forRoot()],
+ imports: [
+ HttpClientTestingModule,
+ SharedModule,
+ NgbNavModule,
+ ToastrModule.forRoot(),
+ RouterTestingModule
+ ],
declarations: [UpgradeComponent, LogsComponent],
schemas: [NO_ERRORS_SCHEMA],
providers: [UpgradeService, { provide: SummaryService, useClass: SummaryServiceMock }]
component = fixture.componentInstance;
upgradeInfoSpy = spyOn(TestBed.inject(UpgradeService), 'list').and.callFake(() => of(null));
getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
+ upgradeStatusSpy = spyOn(TestBed.inject(UpgradeService), 'status');
getHealthSpy.and.returnValue(of(healthPayload));
const upgradeInfoPayload = {
image: 'quay.io/ceph-test/ceph',
versions: ['18.1.0', '18.1.1', '18.1.2']
};
upgradeInfoSpy.and.returnValue(of(upgradeInfoPayload));
+ upgradeStatusSpy.and.returnValue(of({}));
+ component.fetchStatus();
spyOn(TestBed.inject(AuthStorageService), 'getPermissions').and.callFake(() => ({
configOpt: { read: true }
}));
-import { Component, OnInit } from '@angular/core';
-import { Observable, of } from 'rxjs';
-import { catchError, publishReplay, refCount, tap } from 'rxjs/operators';
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Observable, ReplaySubject, Subscription, of } from 'rxjs';
+import { catchError, publishReplay, refCount, shareReplay, switchMap, tap } from 'rxjs/operators';
import { DaemonService } from '~/app/shared/api/daemon.service';
import { HealthService } from '~/app/shared/api/health.service';
import { UpgradeService } from '~/app/shared/api/upgrade.service';
import { SummaryService } from '~/app/shared/services/summary.service';
import { ModalService } from '~/app/shared/services/modal.service';
import { UpgradeStartModalComponent } from './upgrade-form/upgrade-start-modal.component';
+import { ExecutingTask } from '~/app/shared/models/executing-task';
+import { Router } from '@angular/router';
+import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
@Component({
selector: 'cd-upgrade',
templateUrl: './upgrade.component.html',
styleUrls: ['./upgrade.component.scss']
})
-export class UpgradeComponent implements OnInit {
+export class UpgradeComponent implements OnInit, OnDestroy {
version: string;
info$: Observable<UpgradeInfoInterface>;
permission: Permission;
modalRef: NgbModalRef;
upgradableVersions: string[];
errorMessage: string;
+ executingTasks: ExecutingTask;
+ interval = new Subscription();
columns: CdTableColumn[] = [];
icons = Icons;
+ upgradeStatus$: Observable<any>;
+ subject = new ReplaySubject<any>();
+
constructor(
private modalService: ModalService,
private summaryService: SummaryService,
private upgradeService: UpgradeService,
private healthService: HealthService,
private daemonService: DaemonService,
- private notificationService: NotificationService
+ private notificationService: NotificationService,
+ private router: Router,
+ private refreshIntervalService: RefreshIntervalService
) {}
ngOnInit(): void {
+ this.upgradeStatus$ = this.subject.pipe(
+ switchMap(() => this.upgradeService.status()),
+ shareReplay(1)
+ );
+
this.columns = [
{
name: $localize`Daemon name`,
this.summaryService.subscribe((summary) => {
const version = summary.version.replace('ceph version ', '').split('-');
this.version = version[0];
+ this.executingTasks = summary.executing_tasks.filter((tasks) =>
+ tasks.name.includes('progress/Upgrade')
+ )[0];
});
+
+ this.interval = this.refreshIntervalService.intervalData$.subscribe(() => {
+ this.fetchStatus();
+ });
+
this.info$ = this.upgradeService.list().pipe(
tap((upgradeInfo: UpgradeInfoInterface) => (this.upgradableVersions = upgradeInfo.versions)),
publishReplay(1),
return of(null);
})
);
+
this.healthData$ = this.healthService.getMinimalHealth();
this.daemons$ = this.daemonService.list(this.upgradeService.upgradableServiceTypes);
this.fsid$ = this.healthService.getClusterFsid();
});
}
+ fetchStatus() {
+ this.subject.next();
+ }
+
upgradeNow(version: string) {
this.upgradeService.start(version).subscribe({
error: (error) => {
NotificationType.success,
$localize`Started upgrading the cluster`
);
+ this.fetchStatus();
+ this.router.navigate(['/upgrade/progress']);
}
});
}
+
+ ngOnDestroy() {
+ this.interval?.unsubscribe();
+ }
}
import { ApiClient } from './api-client';
import { map } from 'rxjs/operators';
import { SummaryService } from '../services/summary.service';
-import { UpgradeInfoInterface } from '../models/upgrade.interface';
+import { UpgradeInfoInterface, UpgradeStatusInterface } from '../models/upgrade.interface';
+import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root'
return upgradeInfo;
}
- start(version: string) {
- return this.http.post(`${this.baseURL}/start`, { version: version });
+ start(version?: string, image?: string) {
+ return this.http.post(`${this.baseURL}/start`, { image: image, version: version });
+ }
+
+ pause() {
+ return this.http.put(`${this.baseURL}/pause`, null);
+ }
+
+ resume() {
+ return this.http.put(`${this.baseURL}/resume`, null);
+ }
+
+ stop() {
+ return this.http.put(`${this.baseURL}/stop`, null);
+ }
+
+ status(): Observable<UpgradeStatusInterface> {
+ return this.http.get<UpgradeStatusInterface>(`${this.baseURL}/status`);
}
}
</div>
<div class="modal-footer">
<cd-form-button-panel (submitActionEvent)="callSubmitAction()"
+ (backActionEvent)="callBackAction()"
[form]="deletionForm"
[submitText]="(actionDescription | titlecase) + ' ' + itemDescription"></cd-form-button-panel>
</div>
bodyTemplate: TemplateRef<any>;
bodyContext: object;
submitActionObservable: () => Observable<any>;
+ callBackAtionObservable: () => Observable<any>;
submitAction: Function;
+ backAction: Function;
deletionForm: CdFormGroup;
itemDescription: 'entry';
itemNames: string[];
}
}
+ callBackAction() {
+ if (this.callBackAtionObservable) {
+ this.callBackAtionObservable().subscribe({
+ error: this.stopLoadingSpinner.bind(this),
+ complete: this.hideModal.bind(this)
+ });
+ } else {
+ this.backAction();
+ }
+ }
+
hideModal() {
this.activeModal.close();
}
registry: string;
versions: string[];
}
+
+export interface UpgradeStatusInterface {
+ target_image: string;
+ in_progress: boolean;
+ which: string;
+ services_complete: string;
+ progress: string;
+ message: string;
+ is_paused: boolean;
+}