diff --git a/app/src/app/account/account.component.ts b/app/src/app/account/account.component.ts index 9797498..a2e5133 100644 --- a/app/src/app/account/account.component.ts +++ b/app/src/app/account/account.component.ts @@ -32,7 +32,7 @@ export class AccountComponent implements OnInit, OnDestroy private readonly titleService: Title, private readonly translationService: TranslateService) { - translationService.get('account.title').pipe(first()).subscribe(x => titleService.setTitle(`Joyent - ${x}`)); + translationService.get('account.title').pipe(first()).subscribe(x => titleService.setTitle(`Spearhead - ${x}`)); //accountService.getUsers().subscribe(x => console.log(x)); diff --git a/app/src/app/catalog/helpers/catalog.service.ts b/app/src/app/catalog/helpers/catalog.service.ts index 770ddbe..9066d0d 100644 --- a/app/src/app/catalog/helpers/catalog.service.ts +++ b/app/src/app/catalog/helpers/catalog.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Cacheable } from 'ts-cacheable'; -import { delay, filter, map, repeatWhen, take, tap } from 'rxjs/operators'; +import { delay, filter, map, mergeMap, repeatWhen, take, tap } from 'rxjs/operators'; import { CatalogPackage } from '../models/package'; import { CatalogImage } from '../models/image'; @@ -38,7 +38,16 @@ export class CatalogService }) getPackages(): Observable { - return this.httpClient.get(`/api/my/packages`); + return this.httpClient.get(`/api/my/packages`) + .pipe(mergeMap(packages => + { + return this.httpClient.get(`./assets/data/packages.json`).pipe(map(prices => + { + packages.forEach(x => x.price = prices[x.id]) + + return packages; + })) + })); } // ---------------------------------------------------------------------------------------------------------------- @@ -47,7 +56,16 @@ export class CatalogService }) getPackage(packageId: string): Observable { - return this.httpClient.get(`/api/my/packages/${packageId}`); + return this.httpClient.get(`/api/my/packages/${packageId}`) + .pipe(mergeMap(pkg => + { + return this.httpClient.get(`./assets/data/packages.json`).pipe(map(prices => + { + pkg.price = prices[pkg.id]; + + return pkg; + })) + })); } // ---------------------------------------------------------------------------------------------------------------- @@ -56,7 +74,16 @@ export class CatalogService }) getImages(allStates = false): Observable { - return this.httpClient.get(`/api/my/images?${allStates ? 'state=all' : ''}`); + return this.httpClient.get(`/api/my/images?${allStates ? 'state=all' : ''}`) + .pipe(mergeMap(images => + { + return this.httpClient.get(`./assets/data/images.json`).pipe(map(prices => + { + images.forEach(x => x.price = prices[x.id]) + + return images; + })) + })); } // ---------------------------------------------------------------------------------------------------------------- @@ -65,7 +92,16 @@ export class CatalogService }) getImage(id: string): Observable { - return this.httpClient.get(`/api/my/images/${id}`); + return this.httpClient.get(`/api/my/images/${id}`) + .pipe(mergeMap(image => + { + return this.httpClient.get(`./assets/data/images.json`).pipe(map(prices => + { + image.price = prices[image.id]; + + return image; + })) + })); } // ---------------------------------------------------------------------------------------------------------------- @@ -141,7 +177,7 @@ export class CatalogService // ---------------------------------------------------------------------------------------------------------------- cloneImage(imageId: string): Observable { - // https://apidocs.joyent.com/cloudapi/#CloneImage + // https://apidocs.Spearhead.com/cloudapi/#CloneImage return this.httpClient.post(`/api/my/images/${imageId}?action=clone`, {}) .pipe(tap(() => imagesCacheBuster$.next())); } diff --git a/app/src/app/catalog/images/images.component.html b/app/src/app/catalog/images/images.component.html index b3671b2..6b26370 100644 --- a/app/src/app/catalog/images/images.component.html +++ b/app/src/app/catalog/images/images.component.html @@ -15,11 +15,11 @@
-
+
, +
+
Get an estimated montly cost based on the machine's running time
+ +
+ + + hours + × {{ editorForm.get('package').value.price | currency: 'USD': 'symbol': '1.2-4' }} + (package hourly rate) + + + + {{ editorForm.get('image').value.price | currency: 'USD': 'symbol': '1.2-4' }} + (Image monthly price) + + + = {{ estimatedCost | currency: 'USD': 'symbol': '1.2-4' }} + (per month) + +
+
- {{ editorForm.get('package').value.description }} + - - having - {{ editorForm.get('package').value.vcpus || 1 }} vCPUs, - {{ editorForm.get('package').value.memory*1024*1024 | fileSize }} RAM and - {{ editorForm.get('package').value.disk*1024*1024 | fileSize }} storage - , - - named {{ editorForm.get('name').value }}, - - based on the {{ editorForm.get('image').value.description }} -

+

diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.scss b/app/src/app/instances/instance-wizard/instance-wizard.component.scss index 2beda83..4150208 100644 --- a/app/src/app/instances/instance-wizard/instance-wizard.component.scss +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.scss @@ -130,7 +130,7 @@ p float: none; width: 1.4em; max-width: 1rem; - margin-bottom: .25rem; + margin-bottom: .75rem; cursor: inherit; background-color: #0dc3e9; border-color: #0dc3e9; @@ -213,6 +213,7 @@ p a { color: #0dc3e9; + margin-left: .25rem; } } } @@ -337,3 +338,24 @@ p.lead b } } } + +.input-group-cost +{ + .form-control + { + border-top-left-radius: .25rem; + border-bottom-left-radius: .25rem; + } + + .input-group-text + { + background: transparent; + color: #3d5e8e; + border-color: #3d5e8e; + + b + { + color: #ff9c07; + } + } +} \ No newline at end of file diff --git a/app/src/app/instances/instance-wizard/instance-wizard.component.ts b/app/src/app/instances/instance-wizard/instance-wizard.component.ts index 755cfb8..f1eb039 100644 --- a/app/src/app/instances/instance-wizard/instance-wizard.component.ts +++ b/app/src/app/instances/instance-wizard/instance-wizard.component.ts @@ -12,6 +12,7 @@ import { NetworkingService } from '../../networking/helpers/networking.service'; import { ToastrService } from 'ngx-toastr'; import { VolumesService } from '../../volumes/helpers/volumes.service'; import { AuthService } from '../../helpers/auth.service'; +import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'app-instance-wizard', @@ -49,6 +50,8 @@ export class InstanceWizardComponent implements OnInit, OnDestroy steps: any[]; preselectedPackage: string; kvmRequired: boolean; + estimatedCost: number; + readyText: string; private destroy$ = new Subject(); private userId: string; @@ -63,7 +66,8 @@ export class InstanceWizardComponent implements OnInit, OnDestroy private readonly catalogService: CatalogService, private readonly networkingService: NetworkingService, private readonly volumesService: VolumesService, - private readonly toastr: ToastrService) + private readonly toastr: ToastrService, + private readonly translateService: TranslateService) { // When the user navigates away from this route, hide the modal router.events @@ -134,7 +138,8 @@ export class InstanceWizardComponent implements OnInit, OnDestroy }), dataCenter: [], tags, - metadata + metadata, + estimatedMinutesRan: [24] }); this.configureForm(); @@ -257,6 +262,8 @@ export class InstanceWizardComponent implements OnInit, OnDestroy this.kvmRequired = x?.requirements['brand'] === 'kvm' || x?.type === 'zvol' || false; this.loadingPackages = true; + + this.computeEstimatedCost(); }); this.editorForm.get('package').valueChanges @@ -269,7 +276,9 @@ export class InstanceWizardComponent implements OnInit, OnDestroy this.kvmRequired = this.editorForm.get('image').value?.requirements['brand'] === 'kvm' || this.editorForm.get('image').value?.type === 'zvol' || x?.brand === 'kvm' || false; - }); + + this.computeEstimatedCost(); + }); this.editorForm.get(['affinity', 'farFrom']).valueChanges.pipe(startWith(null)) .pipe(takeUntil(this.destroy$)) @@ -278,7 +287,21 @@ export class InstanceWizardComponent implements OnInit, OnDestroy this.editorForm.get(['affinity', 'closeTo']).valueChanges.pipe(startWith(null)) .pipe(takeUntil(this.destroy$)) .subscribe(this.setAffinity.bind(this)); - } + + this.editorForm.get('estimatedMinutesRan').valueChanges + .pipe(takeUntil(this.destroy$)) + .subscribe(this.computeEstimatedCost.bind(this)) + } + + // ---------------------------------------------------------------------------------------------------------------- + private computeEstimatedCost() + { + const estimatedMinutesRan = this.editorForm.get('estimatedMinutesRan').value || 1; + const imagePrice = this.editorForm.get('image').value?.price || 0; + const packagePrice = this.editorForm.get('package').value?.price || 0; + + this.estimatedCost = imagePrice + packagePrice * estimatedMinutesRan; +} // ---------------------------------------------------------------------------------------------------------------- private atLeastOneSelectionValidator: ValidatorFn = (array: FormArray): ValidationErrors | null => @@ -373,6 +396,20 @@ export class InstanceWizardComponent implements OnInit, OnDestroy nextStep() { this.currentStep = this.currentStep < this.steps.length ? this.currentStep + 1 : this.steps.length; + + if (this.currentStep < this.steps.length) return; + + this.readyText = this.translateService.instant('dashboard.wizard.ready', { + imageType: this.editorForm.get('imageType').value == 1 + ? this.translateService.instant('dashboard.wizard.readyImageTypeContainer') + : this.translateService.instant('dashboard.wizard.readyImageTypeVm'), + packageDescription: this.editorForm.get('package').value.description || + `${this.editorForm.get('package').value.vcpus || 1} vCPUs, ` + + `${this.fileSizePipe.transform(this.editorForm.get('package').value.memory * 1024 * 1024)} RAM, ` + + `${this.fileSizePipe.transform(this.editorForm.get('package').value.disk * 1024 * 1024)} storage`, + machineName: this.editorForm.get('name').value, + imageDescription: this.editorForm.get('image').value.description + }) } // ---------------------------------------------------------------------------------------------------------------- diff --git a/app/src/app/instances/instances.component.html b/app/src/app/instances/instances.component.html index 3f7cddd..600a093 100644 --- a/app/src/app/instances/instances.component.html +++ b/app/src/app/instances/instances.component.html @@ -83,8 +83,8 @@ -
-
+
+

{{ 'dashboard.list.noResults' | translate }}

@@ -159,19 +159,13 @@
- - -