mirror of
https://github.com/DerDavidBohl/dirigent-spring.git
synced 2025-12-30 16:12:13 -06:00
Implemented Check for Updates Button
This commit is contained in:
@@ -27,4 +27,9 @@ public class DeploymentUpdateController {
|
||||
deploymentUpdateService.updateDeployment(deploymentName);
|
||||
}
|
||||
|
||||
@PostMapping("check")
|
||||
public void podtDeploymentUpdateCheck() {
|
||||
deploymentUpdateService.checkAllDeploymentForUpdates();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,6 +56,20 @@ public class DeploymentUpdateService {
|
||||
|
||||
@Value("${dirigent.compose.command}")
|
||||
private String composeCommand;
|
||||
|
||||
@Transactional
|
||||
public void updateDeployment(String deploymentName) {
|
||||
|
||||
File deploymentDir = new File("deployments/" + deploymentName);
|
||||
|
||||
String command = this.composeCommand + " pull";
|
||||
|
||||
processRunner.executeCommand(Arrays.asList(command.split(" ")), deploymentDir);
|
||||
|
||||
this.applicationEventPublisher.publishEvent(new NamedDeploymentStartRequestedEvent(this, deploymentName, true));
|
||||
|
||||
this.deploymentUpdateRepository.deleteAllByDeploymentName(deploymentName);
|
||||
}
|
||||
|
||||
@Scheduled(fixedRateString = "${dirigent.update.rate:3}", timeUnit = TimeUnit.HOURS)
|
||||
public void checkAllDeploymentForUpdates() {
|
||||
@@ -71,20 +85,6 @@ public class DeploymentUpdateService {
|
||||
checkIfImageUpdatesExistForDeployment(deployment);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateDeployment(String deploymentName) {
|
||||
|
||||
File deploymentDir = new File("deployments/" + deploymentName);
|
||||
|
||||
String command = this.composeCommand + " pull";
|
||||
|
||||
processRunner.executeCommand(Arrays.asList(command.split(" ")), deploymentDir);
|
||||
|
||||
this.applicationEventPublisher.publishEvent(new NamedDeploymentStartRequestedEvent(this, deploymentName, true));
|
||||
|
||||
this.deploymentUpdateRepository.deleteAllByDeploymentName(deploymentName);
|
||||
}
|
||||
|
||||
@Async
|
||||
public void checkIfImageUpdatesExistForDeployment(Deployment deployment) {
|
||||
@@ -129,7 +129,6 @@ public class DeploymentUpdateService {
|
||||
|
||||
List<DeploymentUpdateEntity> deploymentUpdates = deploymentUpdateRepository
|
||||
.findAllByDeploymentNameAndServiceAndImage(deployment.name(), service, container.getImage());
|
||||
;
|
||||
|
||||
if (deploymentUpdates.size() > 0)
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {Observable, ReplaySubject, tap} from 'rxjs';
|
||||
import {Deployment} from './deployment';
|
||||
import {DeploymentState} from './deployment-state';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Secret} from './secret';
|
||||
import {SystemInformation} from './system-information';
|
||||
@@ -11,13 +11,14 @@ import { DeploymentUpdate } from './deployment-update';
|
||||
})
|
||||
export class ApiService {
|
||||
|
||||
private _deploymentStates: ReplaySubject<Array<Deployment>> = new ReplaySubject<Array<Deployment>>(1);
|
||||
private _deploymentStates: ReplaySubject<Array<DeploymentState>> = new ReplaySubject<Array<DeploymentState>>(1);
|
||||
private _secrets: ReplaySubject<Array<Secret>> = new ReplaySubject<Array<Secret>>(1);
|
||||
private _deploymentUpdates: ReplaySubject<Array<DeploymentUpdate>> = new ReplaySubject<Array<DeploymentUpdate>>;
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
get deploymentStates$(): Observable<Array<Deployment>> {
|
||||
get deploymentStates$(): Observable<Array<DeploymentState>> {
|
||||
return this._deploymentStates.asObservable();
|
||||
}
|
||||
|
||||
@@ -25,10 +26,22 @@ export class ApiService {
|
||||
return this._secrets.asObservable();
|
||||
}
|
||||
|
||||
updateDeployment(deployment: Deployment): Observable<void> {
|
||||
get deploymentUpdates$(): Observable<Array<DeploymentUpdate>> {
|
||||
return this._deploymentUpdates.asObservable();
|
||||
}
|
||||
|
||||
reloadDeployementUpdates(): void {
|
||||
this.getDeploymentUpdates().subscribe(r => this._deploymentUpdates.next(r))
|
||||
}
|
||||
|
||||
updateDeployment(deployment: DeploymentState): Observable<void> {
|
||||
return this.http.post<void>(`api/v1/deployment-updates/${deployment.name}/run`, {});
|
||||
}
|
||||
|
||||
checkForUpdates() {
|
||||
return this.http.post<void>(`api/v1/deployment-updates/check`, {});
|
||||
}
|
||||
|
||||
getDeploymentUpdates(): Observable<Array<DeploymentUpdate>> {
|
||||
return this.http.get<Array<DeploymentUpdate>>('api/v1/deployment-updates');
|
||||
}
|
||||
@@ -37,19 +50,19 @@ export class ApiService {
|
||||
this.getAllDeploymentStates().subscribe(r => this._deploymentStates.next(r));
|
||||
}
|
||||
|
||||
getAllDeploymentStates(): Observable<Array<Deployment>> {
|
||||
return this.http.get<Array<Deployment>>('api/v1/deployments');
|
||||
getAllDeploymentStates(): Observable<Array<DeploymentState>> {
|
||||
return this.http.get<Array<DeploymentState>>('api/v1/deployments');
|
||||
}
|
||||
|
||||
getSystemInformation(): Observable<SystemInformation> {
|
||||
return this.http.get<SystemInformation>('api/v1/system-information');
|
||||
}
|
||||
|
||||
stopDeployment(deploymentState: Deployment): Observable<void> {
|
||||
stopDeployment(deploymentState: DeploymentState): Observable<void> {
|
||||
return this.http.post<void>(`api/v1/deployments/${deploymentState.name}/stop`, {});
|
||||
}
|
||||
|
||||
startDeployment(deploymentState: Deployment, force: boolean): Observable<void> {
|
||||
startDeployment(deploymentState: DeploymentState, force: boolean): Observable<void> {
|
||||
return this.http.post<void>(`api/v1/deployments/${deploymentState.name}/start?forceRecreate=${force}`, {});
|
||||
}
|
||||
|
||||
|
||||
9
frontend/src/app/api/deployment-state.d.ts
vendored
Normal file
9
frontend/src/app/api/deployment-state.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ImageUpdate } from "./image-update";
|
||||
|
||||
export interface DeploymentState {
|
||||
name: string;
|
||||
state: string;
|
||||
message: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
12
frontend/src/app/api/deployment.d.ts
vendored
12
frontend/src/app/api/deployment.d.ts
vendored
@@ -1,12 +0,0 @@
|
||||
export interface Deployment {
|
||||
name: string;
|
||||
state: string;
|
||||
message: string;
|
||||
source: string;
|
||||
imageUpdates: Array<ImageUpdate>
|
||||
}
|
||||
|
||||
export interface ImageUpdate {
|
||||
service: string;
|
||||
image: string;
|
||||
}
|
||||
5
frontend/src/app/api/image-update.d.ts
vendored
Normal file
5
frontend/src/app/api/image-update.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
export interface ImageUpdate {
|
||||
service: string;
|
||||
image: string;
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
<div class="space-y-4">
|
||||
|
||||
<h2 class="text-2xl">Deployments@{{ (systemInformation$ | async)?.instanceName }} (<strong>{{ (deployments$ | async)?.length}}</strong>)</h2>
|
||||
<div>
|
||||
<a [href]="(systemInformation$ | async)?.gitUrl" target="_blank">View Source</a>
|
||||
</div>
|
||||
<h2 class="text-2xl">Deployments@{{ (systemInformation$ | async)?.instanceName }} (<strong>{{ (deploymentStates$ | async)?.length}}</strong>)</h2>
|
||||
<mat-form-field class="md:col-span-2 w-3xl">
|
||||
<mat-label>Search...</mat-label>
|
||||
<input matInput (keyup)="search($event)" >
|
||||
@@ -16,6 +13,14 @@
|
||||
</mat-chip-option>
|
||||
}
|
||||
</mat-chip-listbox>
|
||||
<button [disabled]="isCheckingForUpdates" (click)="checkForUpdates()" mat-button>
|
||||
@if(isCheckingForUpdates) {
|
||||
checking for updates...
|
||||
} @else {
|
||||
Check for updates
|
||||
}
|
||||
</button>
|
||||
<a mat-button [href]="(systemInformation$ | async)?.gitUrl" target="_blank">View Source</a>
|
||||
<table [dataSource]="tableDataSource$" class="mat-elevation-z8" mat-table matSort (matSortChange)="announceSortChange($event)" >
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
@@ -25,14 +30,14 @@
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef mat-header-cell> Actions</th>
|
||||
<td *matCellDef="let element" mat-cell>
|
||||
<div matBadge="Update" matBadgeSize="small" [matBadgeHidden]="!(isUpdateAvailable(element.name) | async)" [matBadgePosition]="'above before'" class="h-full">
|
||||
<div matBadge="Update" matBadgeSize="small" [matBadgeHidden]="!(element.updates.length && element.updates.length > 0)" [matBadgePosition]="'above before'" class="h-full">
|
||||
<button [matMenuTriggerFor]="menu" mat-icon-button [matBadge]="element.e" matBadgeColor="accent">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-menu #menu="matMenu">
|
||||
|
||||
@if (isUpdateAvailable(element.name) | async) {
|
||||
@if (element.updates.length && element.updates.length > 0) {
|
||||
<button (click)="updateDeployment(element)" mat-menu-item>
|
||||
Update
|
||||
</button>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { AsyncPipe } from '@angular/common';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatBadge } from '@angular/material/badge';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatIconButton, MatButton, MatAnchor } from '@angular/material/button';
|
||||
import { MatChip, MatChipListbox, MatChipListboxChange, MatChipOption } from '@angular/material/chips';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
@@ -21,10 +21,10 @@ import {
|
||||
MatRowDef,
|
||||
MatTable
|
||||
} from '@angular/material/table';
|
||||
import { interval, Observable, ReplaySubject } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, switchMap } from 'rxjs/operators';
|
||||
import { combineLatest, interval, Observable, ReplaySubject } from 'rxjs';
|
||||
import { distinctUntilChanged, map, shareReplay, switchMap, tap } from 'rxjs/operators';
|
||||
import { ApiService } from '../api/api.service';
|
||||
import { Deployment } from '../api/deployment';
|
||||
import { DeploymentState } from '../api/deployment-state';
|
||||
import { DeploymentUpdate } from '../api/deployment-update';
|
||||
import { SystemInformation } from '../api/system-information';
|
||||
import { StartDialogComponent } from './start-dialog/start-dialog.component';
|
||||
@@ -57,8 +57,10 @@ import { StartDialogComponent } from './start-dialog/start-dialog.component';
|
||||
MatSort,
|
||||
MatLabel,
|
||||
MatInput,
|
||||
MatFormField
|
||||
],
|
||||
MatFormField,
|
||||
MatButton,
|
||||
MatAnchor
|
||||
],
|
||||
templateUrl: './deployments.component.html',
|
||||
styleUrl: './deployments.component.css',
|
||||
})
|
||||
@@ -68,31 +70,40 @@ export class DeploymentsComponent implements OnInit {
|
||||
sort$ = new ReplaySubject<Sort>(1);
|
||||
search$ = new ReplaySubject<string>(1);
|
||||
|
||||
deployments$: Observable<Array<Deployment>>;
|
||||
tableDataSource$: Observable<Array<Deployment>>;
|
||||
deploymentStates$: Observable<Array<DeploymentState>>;
|
||||
tableDataSource$: Observable<Array<DeploymentState>>;
|
||||
filterValues$: Observable<string[]>;
|
||||
systemInformation$: Observable<SystemInformation>;
|
||||
deploymentUpdates$: Observable<Array<DeploymentUpdate>>;
|
||||
statesAndUpdates$: Observable<Array<DeploymentState & {updates: Array<DeploymentUpdate>}>>;
|
||||
|
||||
displayedColumns = ['actions', 'name', 'state', 'message'];
|
||||
isCheckingForUpdates = false;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
|
||||
this.deploymentUpdates$ = apiService.getDeploymentUpdates().pipe(shareReplay(1));
|
||||
this.deploymentUpdates$ = this.apiService.deploymentUpdates$;
|
||||
|
||||
this.deploymentStates$ = this.apiService.deploymentStates$;
|
||||
|
||||
this.systemInformation$ = apiService.getSystemInformation();
|
||||
|
||||
this.systemInformation$.subscribe(i => document.title = `Dirigent@${i.instanceName}`)
|
||||
|
||||
this.deployments$ = this.apiService.deploymentStates$.pipe(
|
||||
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr))
|
||||
);
|
||||
this.filterValues$ = this.deployments$.pipe(map(ds => [...new Set(ds.map(ds => ds.state))]));
|
||||
this.statesAndUpdates$ = combineLatest([this.deploymentStates$, this.deploymentUpdates$])
|
||||
.pipe(
|
||||
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)),
|
||||
tap(a => console.log('triggered', a)),
|
||||
map(([states, updates]) =>
|
||||
states.map(s => ({...s, updates: updates.filter(u => u.deploymentName === s.name)})
|
||||
)));
|
||||
|
||||
this.filterValues$ = this.statesAndUpdates$.pipe(map(ds => [...new Set(ds.map(ds => ds.state))]));
|
||||
|
||||
this.tableDataSource$ = this.selectedFilterValues$.pipe(
|
||||
switchMap(selectedFilterValues => this.deployments$.pipe(
|
||||
switchMap(selectedFilterValues => this.statesAndUpdates$.pipe(
|
||||
map(ds => ds.filter(ds => selectedFilterValues.includes(ds.state))),
|
||||
)),
|
||||
switchMap(ds => this.search$.pipe(
|
||||
@@ -121,7 +132,10 @@ export class DeploymentsComponent implements OnInit {
|
||||
);
|
||||
this.apiService.reloadDeploymentStates();
|
||||
|
||||
interval(2000).subscribe(() => this.apiService.reloadDeploymentStates());
|
||||
interval(2000).subscribe(() => {
|
||||
this.apiService.reloadDeploymentStates();
|
||||
this.apiService.reloadDeployementUpdates();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -131,19 +145,17 @@ export class DeploymentsComponent implements OnInit {
|
||||
this.search$.next('');
|
||||
}
|
||||
|
||||
isUpdateAvailable(deploymentName: string): Observable<boolean> {
|
||||
return this.deploymentUpdates$.pipe(
|
||||
map(dus => dus.find(du => du.deploymentName === deploymentName)),
|
||||
map(Boolean)
|
||||
);
|
||||
checkForUpdates() {
|
||||
this.isCheckingForUpdates = true;
|
||||
this.apiService.checkForUpdates().subscribe(() => this.isCheckingForUpdates = false)
|
||||
}
|
||||
|
||||
updateDeployment(deployment: Deployment) {
|
||||
updateDeployment(deployment: DeploymentState) {
|
||||
this.apiService.updateDeployment(deployment)
|
||||
.subscribe(() => this.apiService.reloadDeploymentStates());
|
||||
}
|
||||
|
||||
startDeployment(deploymentState: Deployment) {
|
||||
startDeployment(deploymentState: DeploymentState) {
|
||||
const dialogRef = this.dialog.open(StartDialogComponent);
|
||||
|
||||
dialogRef.afterClosed().subscribe((result) => {
|
||||
@@ -154,7 +166,7 @@ export class DeploymentsComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
stopDeployment(element: Deployment) {
|
||||
stopDeployment(element: DeploymentState) {
|
||||
|
||||
this.apiService.stopDeployment(element).subscribe(() => this.apiService.reloadDeploymentStates());
|
||||
|
||||
@@ -187,7 +199,7 @@ export class DeploymentsComponent implements OnInit {
|
||||
}
|
||||
|
||||
countDeploymentsByState(state: string): Observable<number> {
|
||||
return this.deployments$.pipe(
|
||||
return this.deploymentStates$.pipe(
|
||||
map(deployments => deployments.filter(deployment => deployment.state === state).length)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user