diff --git a/src/ui_ng/src/app/shared/gauge/gauge.component.css b/src/ui_ng/src/app/shared/gauge/gauge.component.css new file mode 100644 index 000000000..3eda1b1d0 --- /dev/null +++ b/src/ui_ng/src/app/shared/gauge/gauge.component.css @@ -0,0 +1,284 @@ +.esxc-gauge-container { + position: relative; + overflow: hidden; +} + + +/* + * Gauge track color + * + * TODO: we should make this configurable in the directive + */ + +.esxc-gauge-circle-bg { + background-color: #EEE; +} + + +/* + * Gauge bar + */ + +.esxc-gauge-circle-fill { + position: absolute; + top: 0; + left: 0; + right: 0; + background: transparent; + transform: rotate(0deg); +} + + +/* + * Gauge center area + */ + +.esxc-gauge-circle-inner { + position: absolute; + top: 0; + left: 0; +} + + +/* + * Gauge caption + */ + +.esxc-gauge-circle-caption { + text-align: center; +} + +.esxc-gauge-bar-one { + opacity: 0.5; +} + + +/* + * Small size gauge sizing + */ + +.esxc-gauge-small .esxc-gauge-container { + width: 100px; + height: 50px; +} + +.esxc-gauge-small .esxc-gauge-circle-fill { + height: 100px; + border-radius: 50px; + clip: rect(50px, 100px, 100px, 0); +} + +.esxc-gauge-small .esxc-gauge-circle-bg { + height: 100px; + border-radius: 50px; +} + +.esxc-gauge-small .esxc-gauge-circle-inner { + width: 80px; + height: 80px; + border-radius: 40px; + margin: 10px; +} + +.esxc-gauge-small .esxc-gauge-circle-caption { + margin-top: 20px; +} + + +/* + * Medium size gauge sizing + */ + +.esxc-gauge-medium .esxc-gauge-container { + width: 130px; + height: 65px; +} + +.esxc-gauge-medium .esxc-gauge-circle-fill { + height: 130px; + border-radius: 130px; + clip: rect(65px, 130px, 130px, 0); +} + +.esxc-gauge-medium .esxc-gauge-circle-bg { + height: 130px; + border-radius: 65px; +} + +.esxc-gauge-medium .esxc-gauge-circle-inner { + height: 120px; + width: 120px; + border-radius: 60px; + margin: 5px; +} + + +/* + * Large size gauge sizing + */ + +.esxc-gauge-large .esxc-gauge-container { + width: 260px; + height: 130px; +} + +.esxc-gauge-large .esxc-gauge-circle-fill { + height: 260px; + border-radius: 130px; + clip: rect(130px, 260px, 260px, 0); +} + +.esxc-gauge-large .esxc-gauge-circle-bg { + height: 260px; + border-radius: 130px; +} + +.esxc-gauge-large .esxc-gauge-circle-inner { + width: 240px; + height: 240px; + border-radius: 120px; + margin: 10px; +} + +.esxc-gauge-large .esxc-gauge-circle-caption { + margin-top: 95px; +} + +.esxc-gauge-small .esxc-gauge-circle-caption .esxc-value { + font-size: 24px; + font-weight: bold; +} + +.esxc-gauge-small .esxc-gauge-circle-caption .esxc-unit { + font-size: 10px; +} + +.esxc-gauge-small .esxc-gauge-circle-caption .esxc-loading { + font-size: 12px; +} + +.esxc-gauge-small .esxc-title { + font-size: 16px; + text-align: center; + margin-top: 3px; +} + +.esxc-gauge-small .esxc-title .esxc-bar-title { + float: left; + text-align: right; + width: 70px; + margin-top: 0; +} + +.esxc-gauge-small .esxc-limit { + text-align: center; +} + +.esxc-gauge-small .esxc-limit .esxc-value { + font-size: 10px; +} + +.esxc-gauge-small .esxc-limit .esxc-unit { + font-size: 10px; +} + +.esxc-gauge-small .esxc-limit .esxc-label { + font-size: 10px; +} + +.esxc-gauge-small .esxc-limit .esxc-bar-limit { + text-align: right; +} + +.esxc-gauge-medium .esxc-gauge-circle-caption { + margin-top: 40px; + color: #000; +} + +.esxc-gauge-medium .esxc-gauge-circle-caption .esxc-value { + font-size: 22px; +} + +.esxc-gauge-medium .esxc-gauge-circle-caption .esxc-unit { + font-size: 14px; +} + +.esxc-gauge-medium .esxc-gauge-circle-caption .esxc-loading { + font-size: 25px; +} + +.esxc-gauge-medium .esxc-limit { + text-align: center; + color: #565656; +} + +.esxc-gauge-medium .esxc-limit .esxc-value { + font-size: 12px; +} + +.esxc-gauge-medium .esxc-limit .esxc-unit { + font-size: 12px; +} + +.esxc-gauge-medium .esxc-limit .esxc-label { + font-size: 12px; +} + +.esxc-gauge-medium .esxc-title { + font-size: 13px; + font-weight: 600; + color: #565656; + text-align: center; + margin-top: -8px; +} + +.esxc-gauge-large .esxc-gauge-circle-caption .esxc-value { + font-size: 40px; +} + +.esxc-gauge-large .esxc-gauge-circle-caption .esxc-unit { + font-size: 18px; +} + +.esxc-gauge-large .esxc-gauge-circle-caption .esxc-loading { + font-size: 30px; +} + +.esxc-gauge-large .esxc-limit { + text-align: center; + margin-top: 4px; +} + +.esxc-gauge-large .esxc-limit .esxc-value { + font-size: 14px; + color: #565656; + line-height: 18px; +} + +.esxc-gauge-large .esxc-limit .esxc-unit { + font-size: 14px; +} + +.esxc-gauge-large .esxc-limit .esxc-label { + font-size: 14px; +} + +.esxc-gauge-large .esxc-limit .esxc-bar-limit { + text-align: right; +} + +.esxc-gauge-large .esxc-title { + font-size: 18px; + font-weight: 600; + color: #565656; + line-height: 18px; + text-align: center; + margin-top: -2px; +} + +.esxc-gauge-large .esxc-title .esxc-bar-title { + float: left; + text-align: right; + width: 70px; + margin-top: 0; +} \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/gauge/gauge.component.html b/src/ui_ng/src/app/shared/gauge/gauge.component.html new file mode 100644 index 000000000..372bf8f41 --- /dev/null +++ b/src/ui_ng/src/app/shared/gauge/gauge.component.html @@ -0,0 +1,18 @@ +
+
+
+
+
+
+
+ {{used}} +
+
+
+
+ {{threasHold}} + GB + {{'STATISTICS.LIMIT' | translate}} +
+
{{title | translate}}
+
\ No newline at end of file diff --git a/src/ui_ng/src/app/shared/gauge/gauge.component.ts b/src/ui_ng/src/app/shared/gauge/gauge.component.ts new file mode 100644 index 000000000..cdde6616c --- /dev/null +++ b/src/ui_ng/src/app/shared/gauge/gauge.component.ts @@ -0,0 +1,272 @@ +import { + Component, + Input, + AfterViewInit, + ViewChild, + ElementRef +} from '@angular/core'; + +import * as $ from 'jquery'; + +const RESOURCE_COLOR_GREEN_NORMAL: string = '#5DB700'; +const RESOURCE_COLOR_ORANGE_NORMAL: string = '#FBBF00'; +const RESOURCE_COLOR_RED_NORMAL: string = '#EA400D'; +const RESOURCE_COLOR_GREY500: string = '#D7DEE2'; +const RESOURCE_COLOR_GREY600: string = '#C7D1D6'; + +/** + * Guage to visualize percent usage. + */ +@Component({ + selector: 'esxc-gauge', + templateUrl: 'gauge.component.html', + styleUrls: ['gauge.component.css'] +}) + +export class GaugeComponent implements AfterViewInit { + private _backgroundColor: string; + private _colorOne: string; + private _colorTwo: string; + private _size: string = "small"; //Support small, medium, large + private _title: string = "UNKNOWN"; //Lang key + private _used: number = 0; + private _threasHold: number = 0; + + /** + * Background color of the component. Default is white. + */ + @Input() + get backgroundColor() { + if (this._backgroundColor) { + return this._backgroundColor; + } + return '#FAFAFA'; + } + + set backgroundColor(value: string) { + this._backgroundColor = value; + } + + private _positionOne: number; + /** + * Keep these two properties + * Percentage of the total width for the first portion of the bar. + * Bar one is rendered above bar two, so bar two's position should always + * be greater than bar one if you want bar two to be visible. + */ + @Input() + get positionOne(): number { + return this._positionOne; + } + + set positionOne(value: number) { + this._positionOne = value; + this.setBars(); + } + + private _positionTwo: number; + /** + * Percentage of the total width for the second portion of the bar + */ + @Input() + get positionTwo(): number { + return this._positionTwo; + } + + set positionTwo(value: number) { + this._positionTwo = this._positionOne + value; + this.setBars(); + } + + private _animate: boolean; + /** + * Whether to animate transitions in the bars + */ + @Input() + get animate(): boolean { + return this._animate; + } + + set animate(value: boolean) { + if (typeof value !== 'undefined') { + this._animate = value; + } + this.setAnimate(); + } + + //Define the gauge size + @Input() + get size(): string { + return this._size; + } + + set size(sz: string) { + if (typeof sz !== 'undefined') { + if (sz === 'small' || sz === 'medium' || sz === 'large') { + this._size = sz; + return; + } + } + + this._size = "small"; + } + + get sizeClass(): string { + return "esxc-gauge-" + this._size; + } + + @Input() + get title(): string { + return this._title; + } + + set title(t: string) { + if (typeof t !== 'undefined') { + this._title = t; + } + } + + @Input() + get used(): number { + return this._used; + } + + set used(u: number) { + this._used = u; + this.determineColors(); + } + + @Input() + get threasHold(): number { + return this._threasHold; + } + + set threasHold(th: number) { + this._threasHold = th; + this.determineColors(); + } + + ngAfterViewInit() { + this.determineColors(); + } + + @ViewChild('barOne') private barOne: ElementRef; + @ViewChild('barTwo') private barTwo: ElementRef; + + private determineColors() { + console.info(this._used, this._threasHold); + let percent: number = 0; + if (this._threasHold !== 0) { + percent = (this._used / this._threasHold) * 100; + } + + while (percent > 100) { + percent = percent - 100; + } + + if (percent <= 70) { + this._colorOne = RESOURCE_COLOR_GREEN_NORMAL; + } else if (percent > 70 && percent <= 90) { + this._colorOne = RESOURCE_COLOR_ORANGE_NORMAL; + } else if (percent > 90 && percent <= 100) { + this._colorOne = RESOURCE_COLOR_RED_NORMAL; + } else { + this._colorOne = RESOURCE_COLOR_GREY600; + } + + this._positionOne = percent; + this.setBars(); + this.setColors(); + this.setAnimate(); + } + + private setBars() { + if (!this.barOne || !this.barTwo) { + return; + } + + let barOne = $(this.barOne.nativeElement); + let barTwo = $(this.barTwo.nativeElement); + + if (!barOne || !barTwo) { + return; + } + + let posOne, posTwo; + + if (isNaN(this.positionOne)) { + posOne = posTwo = 0; + } else { + posOne = (this.positionOne / 100) * 180; + posTwo = (this.positionTwo / 100) * 180; + } + + barOne.css({ + '-webkit-transform': 'rotate(' + posOne + 'deg)', + '-moz-transform': 'rotate(' + posOne + 'deg)', + '-ms-transform': 'rotate(' + posOne + 'deg)', + '-o-transform': 'rotate(' + posOne + 'deg)', + 'transform': 'rotate(' + posOne + 'deg)' + }); + + barTwo.css({ + '-webkit-transform': 'rotate(' + posTwo + 'deg)', + '-moz-transform': 'rotate(' + posTwo + 'deg)', + '-ms-transform': 'rotate(' + posTwo + 'deg)', + '-o-transform': 'rotate(' + posTwo + 'deg)', + 'transform': 'rotate(' + posTwo + 'deg)' + }); + } + + private setColors() { + if (!this.barOne || !this.barTwo) { + return; + } + + let barOne = $(this.barOne.nativeElement); + let barTwo = $(this.barTwo.nativeElement); + + if (!barOne || !barTwo) { + return; + } + + barOne.css({ + 'background-color': this._colorOne + }); + + barTwo.css({ + 'background-color': this._colorTwo + }); + } + + private setAnimate() { + if (!this.barOne || !this.barTwo) { + return; + } + + let barOne = $(this.barOne.nativeElement); + let barTwo = $(this.barTwo.nativeElement); + + if (!barOne || !barTwo) { + return; + } + + let transition = 'transform 1s ease'; + let prefixes = ['webkit', 'moz', 'ms', 'o']; + let css = { + 'transition': transition + }; + + if (!this._animate) { + transition = 'none'; + }; + + for (let prefix of prefixes) { + css['-' + prefix + '-transition'] = transition; + } + + barOne.css(css); + barTwo.css(css); + } + +} \ No newline at end of file diff --git a/src/ui_ng/src/app/shared/shared.module.ts b/src/ui_ng/src/app/shared/shared.module.ts index 662bb3d57..b3806a7cc 100644 --- a/src/ui_ng/src/app/shared/shared.module.ts +++ b/src/ui_ng/src/app/shared/shared.module.ts @@ -39,6 +39,7 @@ import { ListRepositoryROComponent } from './list-repository-ro/list-repository- import { MessageHandlerService } from './message-handler/message-handler.service'; import { EmailValidatorDirective } from './email.directive'; +import { GaugeComponent } from './gauge/gauge.component'; @NgModule({ imports: [ @@ -62,7 +63,8 @@ import { EmailValidatorDirective } from './email.directive'; StatisticsPanelComponent, ListProjectROComponent, ListRepositoryROComponent, - EmailValidatorDirective + EmailValidatorDirective, + GaugeComponent ], exports: [ CoreModule, @@ -82,7 +84,8 @@ import { EmailValidatorDirective } from './email.directive'; StatisticsPanelComponent, ListProjectROComponent, ListRepositoryROComponent, - EmailValidatorDirective + EmailValidatorDirective, + GaugeComponent ], providers: [ SessionService, diff --git a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html index 5d9b91aff..ad42bcc70 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html +++ b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.html @@ -35,10 +35,10 @@ -
-
-
{{freeStorage}}GB | {{totalStorage}}GB
-
[STORAGE]
+
+
+ +
\ No newline at end of file diff --git a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.ts b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.ts index 06165686c..c2cfe37dd 100644 --- a/src/ui_ng/src/app/shared/statictics/statistics-panel.component.ts +++ b/src/ui_ng/src/app/shared/statictics/statistics-panel.component.ts @@ -64,6 +64,10 @@ export class StatisticsPanelComponent implements OnInit { return user && user.has_admin_role > 0; } + public get isValidStorage(): boolean { + return this.volumesInfo.storage.total != 0; + } + private getGBFromBytes(bytes: number): number { return Math.round((bytes / (1024 * 1024 * 1024))); } diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 867367095..d2badf012 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -413,7 +413,9 @@ "REPO_ITEM": "REPOSITORIES", "INDEX_MY": "MY", "INDEX_PUB": "PUBLIC", - "INDEX_TOTAL": "TOTAL" + "INDEX_TOTAL": "TOTAL", + "STORAGE": "STORAGE", + "LIMIT": "Limit" }, "SEARCH": { "IN_PROGRESS": "Search...", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index f22f80d9c..507e28e79 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -413,7 +413,9 @@ "REPO_ITEM": "镜像库", "INDEX_MY": "私有", "INDEX_PUB": "公开", - "INDEX_TOTAL": "总计" + "INDEX_TOTAL": "总计", + "STORAGE": "存储", + "LIMIT": "容量" }, "SEARCH": { "IN_PROGRESS": "搜索中...",