Updated update settings form validation.

This commit is contained in:
root 2023-08-26 23:24:34 -05:00
parent 2d73981314
commit d3635e69cb
10 changed files with 606 additions and 1063 deletions

View file

@ -38,3 +38,4 @@ server/RdtClient.Web/wwwroot/
build/ build/
publish/data publish/data

3
.gitignore vendored
View file

@ -8,3 +8,6 @@ server/RdtClient.Web/appsettings.Development.json
data data
Dockerfile.dev Dockerfile.dev
test.bat test.bat
Dockerfile
docker-compose.yml

View file

@ -10,9 +10,11 @@ WORKDIR /appclient
RUN apk add --no-cache git RUN apk add --no-cache git
COPY . /appclient/
RUN \ RUN \
echo "**** Cloning Source Code ****" && \ # echo "**** Cloning Source Code ****" && \
git clone https://github.com/rogerfar/rdt-client.git . && \ # git clone https://github.com/rogerfar/rdt-client.git . && \
cd client && \ cd client && \
echo "**** Building Code ****" && \ echo "**** Building Code ****" && \
npm ci && \ npm ci && \

View file

@ -8,4 +8,4 @@
"secure": false, "secure": false,
"ws": true "ws": true
} }
} }

View file

@ -3,7 +3,7 @@ import { APP_BASE_HREF } from '@angular/common';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { curray } from 'curray'; import { curray } from 'curray';
import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize'; import { FileSizePipe, NgxFilesizeModule } from 'ngx-filesize';
@ -47,6 +47,7 @@ curray();
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
FormsModule, FormsModule,
ReactiveFormsModule,
HttpClientModule, HttpClientModule,
NgxFilesizeModule, NgxFilesizeModule,
FlexLayoutModule, FlexLayoutModule,

View file

@ -7,31 +7,24 @@
<thead> <thead>
<tr> <tr>
<th> <th>
<input <input type="checkbox" (click)="toggleSelectAll($event)"
type="checkbox" [checked]="selectedTorrents.length > 0 && selectedTorrents.length === torrents.length" />
(click)="toggleSelectAll($event)" </th>
[checked]="selectedTorrents.length > 0 && selectedTorrents.length === torrents.length" <th>Name</th>
/> <th>Category</th>
</th> <th>Priority</th>
<th>Name</th> <th>Files</th>
<th>Category</th> <th>Downloads</th>
<th>Priority</th> <th>Size</th>
<th>Files</th> <th>Status</th>
<th>Downloads</th> </tr>
<th>Size</th> </thead>
<th>Status</th> <tbody>
</tr> <tr *ngFor="let torrent of torrents; trackBy: trackByMethod">
</thead> <td>
<tbody> <input type="checkbox" (click)="toggleSelect(torrent)" [checked]="getChecked(torrent)" />
<tr *ngFor="let torrent of torrents; trackBy: trackByMethod">
<td>
<input
type="checkbox"
(click)="toggleSelect(torrent.torrentId)"
[checked]="selectedTorrents.contains(torrent.torrentId)"
/>
</td> </td>
<td (click)="openTorrent(torrent.torrentId)"> <td (click)="openTorrent(torrent)">
{{ torrent.rdName }} {{ torrent.rdName }}
</td> </td>
<td> <td>
@ -59,34 +52,22 @@
<div class="field is-grouped"> <div class="field is-grouped">
<div class="control"> <div class="control">
<button <button class="button is-danger" (click)="showDeleteModal()" [disabled]="selectedTorrents.length === 0"
class="button is-danger" *ngIf="torrents.length > 0">
(click)="showDeleteModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Delete Selected Delete Selected
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<button <button class="button is-primary" (click)="showRetryModal()" [disabled]="selectedTorrents.length === 0"
class="button is-primary" *ngIf="torrents.length > 0">
(click)="showRetryModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Retry Selected Retry Selected
</button> </button>
</div> </div>
<div class="control"> <div class="control">
<button <button class="button is-light" (click)="showUpdateSettingsModal()" [disabled]="selectedTorrents.length === 0"
class="button is-light" *ngIf="torrents.length > 0">
(click)="showUpdateSettingsModal()"
[disabled]="selectedTorrents.length === 0"
*ngIf="torrents.length > 0"
>
Change Selected's Settings Change Selected's Settings
</button> </button>
</div> </div>
@ -130,12 +111,8 @@
</div> </div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button <button class="button is-success" (click)="deleteOk()" [disabled]="deleting"
class="button is-success" [ngClass]="{ 'is-loading': deleting }">
(click)="deleteOk()"
[disabled]="deleting"
[ngClass]="{ 'is-loading': deleting }"
>
Delete selected Delete selected
</button> </button>
<button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }"> <button class="button" (click)="deleteCancel()" [disabled]="deleting" [ngClass]="{ 'is-loading': deleting }">
@ -153,98 +130,82 @@
<button class="delete" aria-label="close" (click)="updateSettingsCancel()"></button> <button class="delete" aria-label="close" (click)="updateSettingsCancel()"></button>
</header> </header>
<section class="modal-card-body"> <section class="modal-card-body">
<div class="field"> <form [formGroup]="updateTorrentSettingsForm" (ngSubmit)="updateSettingsOk()" id="updateTorrentSettingsForm">
<label class="label">Category</label> <div class="field">
<div class="control"> <label class="label">Category</label>
<input class="input" type="text" maxlength="100" [(ngModel)]="updateSettingsCategory" /> <div class="control">
<input class="input" type="text" formControlName="category" name="category" />
</div>
<p class="help">The category becomes a sub-folder in your main download path.</p>
</div> </div>
<p class="help">The category becomes a sub-folder in your main download path.</p> <div class="field">
</div> <label class="label">Priority</label>
<div class="field"> <div class="control">
<label class="label">Priority</label> <input class="input" inputmode="numeric" type="text" pattern="\d*" formControlName="priority"
<div class="control"> name="priority" />
<input class="input" type="number" step="1" [(ngModel)]="updateSettingsPriority" /> </div>
<p class="help">
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest
priority.
</p>
</div> </div>
<p class="help"> <div class="field">
Set the priority for this torrent where 1 is the highest. When empty it will be assigned the lowest priority. <label class="label">Automatic retry downloads</label>
</p> <div class="control">
</div> <input class="input" inputmode="numeric" type="text" pattern="\d*" formControlName="downloadRetryAttempts"
<div class="field"> name="downloadRetryAttempts" />
<label class="label">Automatic retry downloads</label> </div>
<div class="control"> <p class="help">When a single download fails it will retry it this many times before marking it as failed.</p>
<input
class="input"
type="number"
max="1000"
min="0"
step="1"
[(ngModel)]="updateSettingsDownloadRetryAttempts"
/>
</div> </div>
<p class="help">When a single download fails it will retry it this many times before marking it as failed.</p> <div class="field">
</div> <label class="label">Automatic retry torrent</label>
<div class="field"> <div class="control">
<label class="label">Automatic retry torrent</label> <input class="input" inputmode="numeric" type="text" pattern="\d*" formControlName="torrentRetryAttempts"
<div class="control"> name="torrentRetryAttempts" />
<input </div>
class="input" <p class="help">
type="number" When a single download has failed multiple times (see setting above) or when the torrent itself received an
max="1000" error it will retry the full torrent this many times before marking it failed.
min="0" </p>
step="1"
[(ngModel)]="updateSettingsTorrentRetryAttempts"
/>
</div> </div>
<p class="help"> <div class="field">
When a single download has failed multiple times (see setting above) or when the torrent itself received an <label class="label">Delete download when in error</label>
error it will retry the full torrent this many times before marking it failed. <div class="control">
</p> <input class="input" inputmode="numeric" type="text" pattern="\d*" formControlName="deleteOnError"
</div> name="deleteOnError" />
<div class="field"> </div>
<label class="label">Delete download when in error</label> <p class="help">
<div class="control"> When a download has been in error for this many minutes, delete it from the provider and the client. 0 to
<input class="input" type="number" max="1000" min="0" step="1" [(ngModel)]="updateSettingsDeleteOnError" /> disable.
</p>
</div> </div>
<p class="help"> <div class="field">
When a download has been in error for this many minutes, delete it from the provider and the client. 0 to <label class="label">Torrent maximum lifetime</label>
disable. <div class="control">
</p> <input class="input" inputmode="numeric" type="text" pattern="\d*" formControlName="lifetime"
</div> name="lifetime" />
<div class="field"> </div>
<label class="label">Torrent maximum lifetime</label> <p class="help">
<div class="control"> The maximum lifetime of a torrent in minutes. When this time has passed, mark the torrent as error. If the
<input torrent is completed and has downloads, the lifetime setting will not apply. 0 to disable.
class="input" </p>
type="number"
max="100000"
min="0"
step="1"
[(ngModel)]="updateSettingsTorrentLifetime"
/>
</div> </div>
<p class="help"> </form>
The maximum lifetime of a torrent in minutes. When this time has passed, mark the torrent as error. If the
torrent is completed and has downloads, the lifetime setting will not apply. 0 to disable.
</p>
</div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button <button class="button is-success" type="submit" form="updateTorrentSettingsForm"
class="button is-success" [disabled]="updateTorrentSettingsForm.pristine || updateTorrentSettingsForm.invalid || updating ? true : false"
(click)="updateSettingsOk()" [ngClass]="{ 'is-loading': updating }">
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Save Save
</button> </button>
<button <button class="button" (click)="updateSettingsCancel()" [disabled]="updating" [ngClass]="{ }">
class="button"
(click)="updateSettingsCancel()"
[disabled]="updating"
[ngClass]="{ 'is-loading': updating }"
>
Cancel Cancel
</button> </button>
<div style="width: 100%; max-width: 100%;"></div>
<button class="button is-danger" (click)="updateSettingsResetAll()"
[disabled]="updateTorrentSettingsForm.pristine || updating ? true : false" [ngClass]="{ }">
Reset All
</button>
</footer> </footer>
</div> </div>
</div> </div>
@ -267,12 +228,8 @@
</div> </div>
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button <button class="button is-success" (click)="retryOk()" [disabled]="retrying"
class="button is-success" [ngClass]="{ 'is-loading': retrying }">
(click)="retryOk()"
[disabled]="retrying"
[ngClass]="{ 'is-loading': retrying }"
>
Retry Retry
</button> </button>
<button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }"> <button class="button" (click)="retryCancel()" [disabled]="retrying" [ngClass]="{ 'is-loading': retrying }">
@ -280,4 +237,4 @@
</button> </button>
</footer> </footer>
</div> </div>
</div> </div>

View file

@ -3,3 +3,11 @@ table {
cursor: pointer; cursor: pointer;
} }
} }
.control > .ng-dirty.ng-valid {
border-color: green;
border-width: 2px;
}
.control > .ng-dirty.ng-invalid {
border-color: red;
border-width: 2px;
}

View file

@ -3,6 +3,7 @@ import { Router } from '@angular/router';
import { Torrent } from '../models/torrent.model'; import { Torrent } from '../models/torrent.model';
import { TorrentService } from '../torrent.service'; import { TorrentService } from '../torrent.service';
import { forkJoin, Observable } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({ @Component({
selector: 'app-torrent-table', selector: 'app-torrent-table',
@ -12,8 +13,7 @@ import { forkJoin, Observable } from 'rxjs';
export class TorrentTableComponent implements OnInit { export class TorrentTableComponent implements OnInit {
public torrent: Torrent; public torrent: Torrent;
public torrents: Torrent[] = []; public torrents: Torrent[] = [];
public selectedTorrents: string[] = []; public selectedTorrents: Torrent[] = [];
public selectedTorrentsAsObjects: Torrent[] = []
public error: string; public error: string;
public isDeleteModalActive: boolean; public isDeleteModalActive: boolean;
@ -22,23 +22,53 @@ export class TorrentTableComponent implements OnInit {
public deleteData: boolean; public deleteData: boolean;
public deleteRdTorrent: boolean; public deleteRdTorrent: boolean;
public deleteLocalFiles: boolean; public deleteLocalFiles: boolean;
public isRetryModalActive: boolean; public isRetryModalActive: boolean;
public retryError: string; public retryError: string;
public retrying: boolean; public retrying: boolean;
public isUpdateSettingsModalActive: boolean; public isUpdateSettingsModalActive: boolean;
public updateSettingsCategory: string;
public updateSettingsPriority: number;
public updateSettingsDownloadRetryAttempts: number;
public updateSettingsTorrentRetryAttempts: number;
public updateSettingsDeleteOnError: number;
public updateSettingsTorrentLifetime: number;
public updateError: string; public updateError: string;
public updating: boolean; public updating: boolean;
constructor(private router: Router, private torrentService: TorrentService) {} public getSameValue(key: string): any {
let previousValue: any = null;
this.selectedTorrents.forEach((torrent: Torrent, i: number) => {
type ObjectKey = keyof typeof torrent;
const str = key as ObjectKey;
const currentValue = torrent[str];
if (i == 0) {
previousValue = currentValue;
} else {
if (previousValue != currentValue) {
previousValue = null;
}
}
});
return previousValue;
}
public updateTorrentSettingsForm: FormGroup = this.fb.group({
category: [this.getSameValue('category') || '', [Validators.maxLength(100)]],
priority: [this.getSameValue('priority') || '', [Validators.min(0), Validators.min(0)]],
downloadRetryAttempts: [
this.getSameValue('downloadRetryAttempts') || '0',
[Validators.required, Validators.min(0), Validators.max(1000)],
],
torrentRetryAttempts: [
this.getSameValue('torrentRetryAttempts') || '0',
[Validators.required, Validators.min(0), Validators.max(1000)],
],
deleteOnError: [
this.getSameValue('deleteOnError') || '0',
[Validators.required, Validators.min(0), Validators.max(1000)],
],
lifetime: [this.getSameValue('lifetime') || '0', [Validators.required, Validators.min(0), Validators.max(100000)]],
});
constructor(private router: Router, private torrentService: TorrentService, private fb: FormBuilder) {}
ngOnInit(): void { ngOnInit(): void {
this.torrentService.getList().subscribe( this.torrentService.getList().subscribe(
@ -55,61 +85,71 @@ export class TorrentTableComponent implements OnInit {
); );
} }
public openTorrent(torrentId: string): void { public openTorrent(torrent: Torrent): void {
this.router.navigate([`/torrent/${torrentId}`]); this.router.navigate([`/torrent/${torrent.torrentId}`]);
} }
public trackByMethod(index: number, el: Torrent): string { public trackByMethod(index: number, el: Torrent): string {
return el.torrentId; return el.torrentId;
} }
public getChecked(torrent: Torrent) {
const index = this.selectedTorrents.findIndex((selected) => selected.torrentId === torrent.torrentId);
if (index > -1) {
return true;
}
return false;
}
public toggleSelectAll(event: any) { public toggleSelectAll(event: any) {
this.selectedTorrents = []; this.selectedTorrents = [];
if (event.target.checked) { if (event.target.checked) {
this.torrents.map((torrent) => { this.torrents.map((torrent) => {
this.selectedTorrents.push(torrent.torrentId); this.selectedTorrents.push(torrent);
}); });
} }
} }
public toggleSelect(torrentId: string) { public toggleSelect(torrent: Torrent) {
const index = this.selectedTorrents.indexOf(torrentId); const index = this.selectedTorrents.findIndex((selected) => selected.torrentId === torrent.torrentId);
if (index > -1) { if (index > -1) {
this.selectedTorrents.splice(index, 1); this.selectedTorrents.splice(index, 1);
} else { } else {
this.selectedTorrents.push(torrentId); this.selectedTorrents.push(torrent);
} }
} }
public showDeleteModal(): void { public showDeleteModal(): void {
this.deleteData = false; this.deleteData = false;
this.deleteRdTorrent = false; this.deleteRdTorrent = false;
this.deleteLocalFiles = false; this.deleteLocalFiles = false;
this.deleteError = null; this.deleteError = null;
this.isDeleteModalActive = true; this.isDeleteModalActive = true;
} }
public deleteCancel(): void { public deleteCancel(): void {
this.isDeleteModalActive = false; this.isDeleteModalActive = false;
} }
public deleteOk(): void { public deleteOk(): void {
this.deleting = true; this.deleting = true;
let calls: Observable<void>[] = []; let calls: Observable<void>[] = [];
this.selectedTorrents.forEach((torrentId) => { this.selectedTorrents.forEach((torrent) => {
calls.push(this.torrentService.delete(torrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles)); calls.push(
this.torrentService.delete(torrent.torrentId, this.deleteData, this.deleteRdTorrent, this.deleteLocalFiles)
);
}); });
forkJoin(calls).subscribe({ forkJoin(calls).subscribe({
complete: () => { complete: () => {
this.isDeleteModalActive = false; this.isDeleteModalActive = false;
this.deleting = false; this.deleting = false;
this.selectedTorrents = []; this.selectedTorrents = [];
}, },
error: (err) => { error: (err) => {
@ -121,28 +161,28 @@ export class TorrentTableComponent implements OnInit {
public showRetryModal(): void { public showRetryModal(): void {
this.retryError = null; this.retryError = null;
this.isRetryModalActive = true; this.isRetryModalActive = true;
} }
public retryCancel(): void { public retryCancel(): void {
this.isRetryModalActive = false; this.isRetryModalActive = false;
} }
public retryOk(): void { public retryOk(): void {
this.retrying = true; this.retrying = true;
let calls: Observable<void>[] = []; let calls: Observable<void>[] = [];
this.selectedTorrents.forEach((torrentId) => { this.selectedTorrents.forEach((torrent) => {
calls.push(this.torrentService.retry(torrentId)); calls.push(this.torrentService.retry(torrent.torrentId));
}) });
forkJoin(calls).subscribe({ forkJoin(calls).subscribe({
complete: () => { complete: () => {
this.isRetryModalActive = false; this.isRetryModalActive = false;
this.retrying = false; this.retrying = false;
this.selectedTorrents = []; this.selectedTorrents = [];
}, },
error: (err) => { error: (err) => {
@ -151,64 +191,63 @@ export class TorrentTableComponent implements OnInit {
}, },
}); });
} }
public getSameValueOrNull(key: string): any { public updateSettingsResetAll(): void {
let newValue: any = null this.updateTorrentSettingsForm.reset();
this.updateTorrentSettingsForm.patchValue({
this.selectedTorrentsAsObjects.forEach((t, i) => { category: this.getSameValue('category'),
type ObjectKey = keyof typeof t; priority: this.getSameValue('priority'),
const myVar = key as ObjectKey; downloadRetryAttempts: this.getSameValue('downloadRetryAttempts'),
const currentValue = t[myVar] torrentRetryAttempts: this.getSameValue('torrentRetryAttempts'),
deleteOnError: this.getSameValue('deleteOnError'),
if (i == 0) { lifetime: this.getSameValue('lifetime'),
newValue = currentValue });
} else {
if (newValue != currentValue) {
newValue = null
}
}
})
return newValue
} }
public showUpdateSettingsModal(): void { public showUpdateSettingsModal(): void {
this.selectedTorrentsAsObjects = this.torrents.filter(t => this.selectedTorrents.includes(t.torrentId)) this.updateSettingsResetAll();
this.updateSettingsCategory = this.getSameValueOrNull('category')
this.updateSettingsPriority = this.getSameValueOrNull('priority');
this.updateSettingsDownloadRetryAttempts = this.getSameValueOrNull('downloadRetryAttempts');
this.updateSettingsTorrentRetryAttempts = this.getSameValueOrNull('torrentRetryAttempts');
this.updateSettingsDeleteOnError = this.getSameValueOrNull('deleteOnError');
this.updateSettingsTorrentLifetime = this.getSameValueOrNull('lifetime');
this.isUpdateSettingsModalActive = true; this.isUpdateSettingsModalActive = true;
} }
public updateSettingsCancel(): void { public updateSettingsCancel(): void {
this.isUpdateSettingsModalActive = false; this.isUpdateSettingsModalActive = false;
this.updateTorrentSettingsForm.reset();
} }
public getUpdatedValue(original: any, key: string): string {
type ObjectKey = keyof typeof original;
const str: any = key as ObjectKey;
const fieldIsPristine = this.updateTorrentSettingsForm.controls[str].pristine;
const originalValue = original[str];
const updatedValue = this.updateTorrentSettingsForm.value[str];
if (fieldIsPristine) {
return originalValue;
}
return updatedValue;
}
public updateSettingsOk(): void { public updateSettingsOk(): void {
this.updating = true; this.updating = true;
let calls: Observable<void>[] = []; let calls: Observable<void>[] = [];
this.selectedTorrentsAsObjects.forEach((torrent) => { this.selectedTorrents.forEach((torrent) => {
torrent.category = this.updateSettingsCategory; (torrent.category = this.getUpdatedValue(torrent, 'category')),
torrent.priority = this.updateSettingsPriority; (torrent.priority = parseInt(this.getUpdatedValue(torrent, 'priority'))),
torrent.downloadRetryAttempts = this.updateSettingsDownloadRetryAttempts; (torrent.downloadRetryAttempts = parseInt(this.getUpdatedValue(torrent, 'downloadRetryAttempts'))),
torrent.torrentRetryAttempts = this.updateSettingsTorrentRetryAttempts; (torrent.torrentRetryAttempts = parseInt(this.getUpdatedValue(torrent, 'torrentRetryAttempts'))),
torrent.deleteOnError = this.updateSettingsDeleteOnError; (torrent.deleteOnError = parseInt(this.getUpdatedValue(torrent, 'deleteOnError'))),
torrent.lifetime = this.updateSettingsTorrentLifetime; (torrent.lifetime = parseInt(this.getUpdatedValue(torrent, 'lifetime')));
calls.push(this.torrentService.update(torrent)); calls.push(this.torrentService.update(torrent));
}) });
forkJoin(calls).subscribe({ forkJoin(calls).subscribe({
complete: () => { complete: () => {
this.isUpdateSettingsModalActive = false; this.isUpdateSettingsModalActive = false;
this.updating = false; this.updating = false;
this.updateTorrentSettingsForm.reset();
this.selectedTorrents = []; this.selectedTorrents = [];
}, },

View file

@ -1,28 +1,28 @@
version: '3.3' version: "3.3"
services: services:
rdtclient: rdtclient:
image: rogerfar/rdtclient # image: rogerfar/rdtclient
container_name: rdtclient container_name: rdtclient
# build: build:
# context: . context: .
# dockerfile: Dockerfile dockerfile: Dockerfile
environment: environment:
- PUID=1001 - PUID=1001
- PGID=1001 - PGID=1001
volumes: volumes:
- ${PWD}/data/downloads:/data/downloads - /mnt/internal_1tb/Downloading/Downloads/rdtclient:/mnt/internal_1tb/Downloading/Downloads/rdtclient:ro
- ${PWD}/data/db:/data/db - /docker_mnt/rdtclient/data/db:/data/db
restart: always restart: always
logging: logging:
driver: json-file driver: json-file
options: options:
max-size: 10m max-size: 10m
ports: ports:
- '6500:6500' - "6500:6500"
healthcheck: healthcheck:
test: curl --fail http://localhost:6500 || exit 1 test: curl --fail http://localhost:6500 || exit 1
interval: 30s interval: 30s
retries: 3 retries: 3
start_period: 30s start_period: 30s
timeout: 30s timeout: 30s

1172
package-lock.json generated

File diff suppressed because it is too large Load diff