Update storage display (#14807)

Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
Will Sun 2021-05-07 11:15:13 +08:00 committed by GitHub
parent dce3522b4e
commit 3322716bc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 243 additions and 457 deletions

View File

@ -18,7 +18,6 @@ import { ListProjectComponent } from "./list-project/list-project.component";
import { CreateProjectComponent } from "./create-project/create-project.component";
import { RouterModule, Routes } from "@angular/router";
import { StatisticsPanelComponent } from "./statictics/statistics-panel.component";
import { StatisticsComponent } from "./statictics/statistics.component";
const routes: Routes = [
{
@ -36,8 +35,7 @@ const routes: Routes = [
ProjectsComponent,
ListProjectComponent,
CreateProjectComponent,
StatisticsPanelComponent,
StatisticsComponent
StatisticsPanelComponent
],
providers: []
})

View File

@ -1,44 +1,46 @@
<div class="row flex-items-xs-between flex-items-xs-middle">
<div></div>
<div id="right_statistic_panel">
<div class="statistic-block">
<div class="statistic-column-block">
<div>
<span class="statistic-column-title statistic-column-title-pro">{{'STATISTICS.PRO_ITEM' | translate }}</span>
</div>
<div>
<span class="statistic-column-title statistic-column-title-repo">{{'STATISTICS.REPO_ITEM' | translate }}</span>
</div>
<div class="clr-row flex-end">
<div class="card">
<div class="card-block">
<h4 class="head">{{'STATISTICS.PRO_ITEM' | translate }}</h4>
<div class="clr-row" *ngIf="isValidSession">
<div class="clr-col">{{"STATISTICS.INDEX_PRIVATE" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.private_project_count}}</div>
</div>
<div class="statistic-column-block">
<div>
<statistics [data]='originalCopy.private_project_count' [label]='"STATISTICS.INDEX_PRIVATE" | translate'></statistics>
</div>
<div>
<statistics [data]='originalCopy.private_repo_count' [label]='"STATISTICS.INDEX_PRIVATE" | translate'></statistics>
</div>
<div class="clr-row">
<div class="clr-col">{{"STATISTICS.INDEX_PUB" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.public_project_count}}</div>
</div>
<div class="statistic-column-block">
<div>
<statistics [data]='originalCopy.public_project_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
<div>
<statistics [data]='originalCopy.public_repo_count' [label]='"STATISTICS.INDEX_PUB" | translate'></statistics>
</div>
</div>
<div class="statistic-column-block">
<div>
<statistics [data]='originalCopy.total_project_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
<div>
<statistics [data]='originalCopy.total_repo_count' [label]='"STATISTICS.INDEX_TOTAL" | translate' *ngIf="isValidSession"></statistics>
</div>
<div class="clr-row" *ngIf="isValidSession">
<div class="clr-col">{{"STATISTICS.INDEX_TOTAL" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.total_project_count}}</div>
</div>
</div>
<div class="statistic-item-divider" [hidden]="!isValidSession || !isValidStorage"></div>
<div class="statistic-block" [hidden]="!isValidSession || !isValidStorage">
<esxc-gauge [free]="freeStorage" [threasHold]="totalStorage" [title]='"STATISTICS.STORAGE"' [animate]="true">
</esxc-gauge>
</div>
<div class="card">
<div class="card-block">
<h4 class="head">{{'STATISTICS.REPO_ITEM' | translate }}</h4>
<div class="clr-row" *ngIf="isValidSession">
<div class="clr-col">{{"STATISTICS.INDEX_PRIVATE" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.private_repo_count}}</div>
</div>
<div class="clr-row">
<div class="clr-col">{{"STATISTICS.INDEX_PUB" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.public_repo_count}}</div>
</div>
<div class="clr-row" *ngIf="isValidSession">
<div class="clr-col">{{"STATISTICS.INDEX_TOTAL" | translate}}</div>
<div class="clr-col font-weight-700">{{originalCopy?.total_repo_count}}</div>
</div>
</div>
</div>
<div class="card" *ngIf="isValidSession">
<div class="card-block container">
<h4 class="head">{{'STATISTICS.STORAGE_USED' | translate }}</h4>
<div class="storage-used font-weight-700">
<div>
<span class="size-number margin-right-5px">{{getSizeNumber()}}</span><span *ngIf="getSizeNumber()">{{getSizeUnit()}}</span>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,37 @@
.head {
margin-top: 0;
margin-bottom: 0.25rem;
}
.card{
width: 10rem;
margin-right: 0.5rem;
margin-top: 0;
}
.card:last-child{
width: 10rem;
margin-right: 0;
}
.flex-end {
justify-content: flex-end;
}
.font-weight-700 {
font-weight: 700;
}
.storage-used {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.size-number {
font-size: 1.5rem;
}
.container {
display: flex;
flex-direction: column;
height: 100%;
}
.margin-right-5px {
margin-right: 5px;
}

View File

@ -1,80 +1,89 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { StatisticsPanelComponent } from './statistics-panel.component';
import { StatisticsComponent } from './statistics.component';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of } from 'rxjs';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { StatisticsService } from "./statistics.service";
import { SessionService } from "../../../../shared/services/session.service";
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
import { StatisticHandler } from "./statistic-handler.service";
import { AppConfigService } from "../../../../services/app-config.service";
import { Statistics } from './statistics';
import { Volumes } from './volumes';
import { Statistic } from "../../../../../../ng-swagger-gen/models/statistic";
import { SharedTestingModule } from "../../../../shared/shared.module";
import { StatisticService } from "../../../../../../ng-swagger-gen/services/statistic.service";
describe('StatisticsPanelComponent', () => {
let component: StatisticsPanelComponent;
let fixture: ComponentFixture<StatisticsPanelComponent>;
const mockStatisticsService = {
getStatistics: () => of(new Statistics()),
getVolumes: () => of(new Volumes()),
};
const mockSessionService = {
getCurrentUser: () => { }
};
const mockAppConfigService = {
getConfig: () => {
return {
registry_storage_provider_name : ""
};
}
};
const mockMessageHandlerService = {
handleError: () => { }
};
const mockStatisticHandler = {
refreshChan$: of(null)
};
const mockRouter = {
navigate: () => { }
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
BrowserAnimationsModule,
ClarityModule,
TranslateModule.forRoot(),
FormsModule,
RouterTestingModule,
NoopAnimationsModule,
HttpClientTestingModule
],
declarations: [StatisticsPanelComponent, StatisticsComponent],
providers: [
TranslateService,
{ provide: SessionService, useValue: mockSessionService },
{ provide: AppConfigService, useValue: mockAppConfigService },
{ provide: StatisticsService, useValue: mockStatisticsService },
{ provide: StatisticHandler, useValue: mockStatisticHandler },
{ provide: MessageHandlerService, useValue: mockMessageHandlerService }
]
}).compileComponents();
}));
const mockedStatistic: Statistic = {
"private_project_count": 2,
"private_repo_count": 0,
"public_project_count": 3,
"public_repo_count": 1,
"total_project_count": 5,
"total_repo_count": 1,
"total_storage_consumption": 4564
};
let component: StatisticsPanelComponent;
let fixture: ComponentFixture<StatisticsPanelComponent>;
const mockStatisticsService = {
getStatistic: () => of(mockedStatistic),
};
const mockSessionService = {
getCurrentUser: () => {
return {
has_admin_role: true
};
}
};
const mockAppConfigService = {
getConfig: () => {
return {
registry_storage_provider_name: ""
};
}
};
const mockMessageHandlerService = {
handleError: () => {
}
};
const mockStatisticHandler = {
refreshChan$: of(null)
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
schemas: [
CUSTOM_ELEMENTS_SCHEMA
],
imports: [
SharedTestingModule
],
declarations: [StatisticsPanelComponent],
providers: [
{provide: SessionService, useValue: mockSessionService},
{provide: AppConfigService, useValue: mockAppConfigService},
{provide: StatisticService, useValue: mockStatisticsService},
{provide: StatisticHandler, useValue: mockStatisticHandler},
{provide: MessageHandlerService, useValue: mockMessageHandlerService}
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatisticsPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
beforeEach(() => {
fixture = TestBed.createComponent(StatisticsPanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have 3 cards', async () => {
fixture.detectChanges();
await fixture.whenStable();
const cards = fixture.nativeElement.querySelectorAll('.card');
expect(cards.length).toEqual(3);
});
it('should display right size number', async () => {
fixture.detectChanges();
await fixture.whenStable();
const sizeHtml: HTMLSpanElement = fixture.nativeElement.querySelector('.size-number');
expect(sizeHtml.innerText).toEqual('4.46');
});
});

View File

@ -13,35 +13,28 @@
// limitations under the License.
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subscription } from "rxjs";
import { StatisticsService } from "./statistics.service";
import { Statistics } from "./statistics";
import { SessionService } from "../../../../shared/services/session.service";
import { Volumes } from "./volumes";
import { MessageHandlerService } from "../../../../shared/services/message-handler.service";
import { StatisticHandler } from "./statistic-handler.service";
import { AppConfigService } from "../../../../services/app-config.service";
import { Statistic } from "../../../../../../ng-swagger-gen/models/statistic";
import { StatisticService } from "../../../../../../ng-swagger-gen/services/statistic.service";
import { getSizeNumber, getSizeUnit } from "../../../../shared/units/utils";
@Component({
selector: "statistics-panel",
templateUrl: "statistics-panel.component.html",
styleUrls: ["statistics.component.scss"],
providers: [StatisticsService]
})
styleUrls: ["statistics-panel.component.scss"],
})
export class StatisticsPanelComponent implements OnInit, OnDestroy {
originalCopy: Statistics = new Statistics();
volumesInfo: Volumes = new Volumes();
originalCopy: Statistic;
refreshSub: Subscription;
constructor(
private statistics: StatisticsService,
private statistics: StatisticService,
private msgHandler: MessageHandlerService,
private session: SessionService,
private appConfigService: AppConfigService,
private statisticHandler: StatisticHandler) {
}
@ -54,10 +47,6 @@ export class StatisticsPanelComponent implements OnInit, OnDestroy {
if (this.session.getCurrentUser()) {
this.getStatistics();
}
if (this.isValidSession) {
this.getVolumes();
}
}
ngOnDestroy() {
@ -65,60 +54,27 @@ export class StatisticsPanelComponent implements OnInit, OnDestroy {
this.refreshSub.unsubscribe();
}
}
public get totalStorage(): number {
let count: number = 0;
if (this.volumesInfo && this.volumesInfo.storage && this.volumesInfo.storage.length) {
this.volumesInfo.storage.forEach(item => {
count += item.total ? item.total : 0;
});
}
return this.getGBFromBytes(count);
}
public get freeStorage(): number {
let count: number = 0;
if (this.volumesInfo && this.volumesInfo.storage && this.volumesInfo.storage.length) {
this.volumesInfo.storage.forEach(item => {
count += item.free ? item.free : 0;
});
}
return this.getGBFromBytes(count);
}
public getStatistics(): void {
this.statistics.getStatistics()
getStatistics(): void {
this.statistics.getStatistic()
.subscribe(statistics => this.originalCopy = statistics
, error => {
this.msgHandler.handleError(error);
});
}
public getVolumes(): void {
this.statistics.getVolumes()
.subscribe(volumes => this.volumesInfo = volumes
, error => {
this.msgHandler.handleError(error);
});
}
public get isValidSession(): boolean {
get isValidSession(): boolean {
let user = this.session.getCurrentUser();
return user && user.has_admin_role;
}
public get isValidStorage(): boolean {
let count: number = 0;
if (this.volumesInfo && this.volumesInfo.storage && this.volumesInfo.storage.length) {
this.volumesInfo.storage.forEach(item => {
count += item.total ? item.total : 0;
});
getSizeNumber(): number | string {
if (this.originalCopy) {
return getSizeNumber(this.originalCopy.total_storage_consumption);
}
return count !== 0 &&
this.appConfigService.getConfig().registry_storage_provider_name === "filesystem";
return null;
}
getGBFromBytes(bytes: number): number {
return Math.round((bytes / (1024 * 1024 * 1024)));
getSizeUnit(): number | string {
if (this.originalCopy) {
return getSizeUnit(this.originalCopy.total_storage_consumption);
}
return null;
}
}

View File

@ -1,4 +0,0 @@
<div class="statistic-wrapper">
<span class="statistic-data">{{data}}</span>
<span class="statistic-text">{{label}}</span>
</div>

View File

@ -1,75 +0,0 @@
.statistic-wrapper {
padding: 4px;
margin: 4px;
text-align: right;
vertical-align: middle;
height: 30px;
display: inline-block;
}
.statistic-data {
font-size: 24px;
font-weight: 600;
font-family: Metropolis, "Avenir Next", "Helvetica Neue", Arial, sans-serif;
line-height: 24px;
margin-right: 10px;
}
.statistic-text {
font-size: 14px;
line-height: 14px;
text-transform: uppercase;
font-family: Metropolis, "Avenir Next", "Helvetica Neue", Arial, sans-serif;
}
.statistic-column-block {
display: inline-block;
text-align: right;
}
.statistic-column-title {
position: relative;
text-transform: uppercase;
font-size: 14px;
font-family: Metropolis, "Avenir Next", "Helvetica Neue", Arial, sans-serif;
}
.statistic-column-title-pro {
top: -10px;
}
.statistic-column-title-repo {
top: 3px;
}
.statistic-item-divider {
height: 54px;
display: inline-block;
width: 2px;
background-color: #bdbdbd;
opacity: 0.55;
margin-left: 4px;
margin-right: 12px;
position: relative;
top: 3px;
}
.statistic-block {
display: inline-block;
}
#right_statistic_panel {
margin-right: 18px;
margin-top: 8px;
display: flex;
justify-content: flex-end;
div:nth-of-type(2) {
margin-left: 16px;
}
div:nth-of-type(3) {
margin-left: 28px;
}
div:nth-of-type(4) {
margin-left: 28px;
}
}

View File

@ -1,25 +0,0 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { StatisticsComponent } from './statistics.component';
describe('StatisticsComponent', () => {
let component: StatisticsComponent;
let fixture: ComponentFixture<StatisticsComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [StatisticsComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatisticsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,25 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Component, Input } from '@angular/core';
@Component({
selector: 'statistics',
templateUrl: "statistics.component.html",
styleUrls: ['statistics.component.scss']
})
export class StatisticsComponent {
@Input() label: string;
@Input() data: number = 0;
}

View File

@ -1,18 +0,0 @@
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { StatisticsService } from './statistics.service';
describe('StatisticsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
providers: [StatisticsService]
});
});
it('should be created', inject([StatisticsService], (service: StatisticsService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -1,48 +0,0 @@
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, catchError } from "rxjs/operators";
import { Observable, throwError as observableThrowError } from "rxjs";
import { Statistics } from './statistics';
import { Volumes } from './volumes';
import { CURRENT_BASE_HREF, HTTP_GET_OPTIONS } from "../../../../shared/units/utils";
const statisticsEndpoint = CURRENT_BASE_HREF + "/statistics";
const volumesEndpoint = CURRENT_BASE_HREF + "/systeminfo/volumes";
/**
* Declare service to handle the top repositories
*
*
**
* class GlobalSearchService
*/
@Injectable()
export class StatisticsService {
constructor(private http: HttpClient) { }
getStatistics(): Observable<Statistics> {
return this.http.get(statisticsEndpoint, HTTP_GET_OPTIONS)
.pipe(map(response => response as Statistics)
, catchError(error => observableThrowError(error)));
}
getVolumes(): Observable<Volumes> {
return this.http.get(volumesEndpoint, HTTP_GET_OPTIONS)
.pipe(map(response => response as Volumes)
, catchError(error => observableThrowError(error)));
}
}

View File

@ -1,23 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export class Statistics {
constructor() {}
private_project_count: number;
private_repo_count: number;
public_project_count: number;
public_repo_count: number;
total_project_count: number;
total_repo_count: number;
}

View File

@ -1,30 +0,0 @@
// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export class Volumes {
constructor() {
this.storage = [new Storage()];
}
storage: Storage[];
}
export class Storage {
constructor() {
this.total = 0;
this.free = 0;
}
total: number;
free: number;
}

View File

@ -1,4 +1,4 @@
import { delUrlParam, getQueryString, getSortingString, isSameArrayValue, isSameObject } from "./utils";
import { delUrlParam, getQueryString, getSizeNumber, getSizeUnit, getSortingString, isSameArrayValue, isSameObject } from "./utils";
import { ClrDatagridStateInterface } from "@clr/angular";
describe('functions in utils.ts should work', () => {
@ -53,4 +53,20 @@ describe('functions in utils.ts should work', () => {
};
expect(getQueryString(state)).toEqual(encodeURIComponent('name=~test,url=~http://test.com'));
});
it('function getSizeNumber() should work', () => {
expect(getSizeNumber).toBeTruthy();
expect(getSizeNumber(4564)).toEqual('4.46');
expect(getSizeNumber(10)).toEqual(10);
expect(getSizeNumber(456400)).toEqual('445.70');
expect(getSizeNumber(45640000)).toEqual('43.53');
});
it('function getSizeUnit() should work', () => {
expect(getSizeUnit).toBeTruthy();
expect(getSizeUnit(4564)).toEqual('KB');
expect(getSizeUnit(10)).toEqual('B');
expect(getSizeUnit(4564000)).toEqual('MB');
expect(getSizeUnit(4564000000)).toEqual('GB');
});
});

View File

@ -579,6 +579,38 @@ export function formatSize(tagSize: string): string {
}
}
/**
* get size number of target size (in byte)
* @param size
*/
export function getSizeNumber(size: number): string | number {
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
return (size / Math.pow(1024, 1)).toFixed(2);
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
return (size / Math.pow(1024, 2)).toFixed(2);
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
return (size / Math.pow(1024, 3)).toFixed(2);
} else {
return size;
}
}
/**
* get size unit of target size (in byte)
* @param size
*/
export function getSizeUnit(size: number): string {
if (Math.pow(1024, 1) <= size && size < Math.pow(1024, 2)) {
return "KB";
} else if (Math.pow(1024, 2) <= size && size < Math.pow(1024, 3)) {
return "MB";
} else if (Math.pow(1024, 3) <= size && size < Math.pow(1024, 4)) {
return "GB";
} else {
return "B";
}
}
/**
* Simple object check.
* @param item

View File

@ -991,16 +991,14 @@
},
"TOP_REPO": "Beliebte Repositories",
"STATISTICS": {
"TITLE": "STATISTIKEN",
"PRO_ITEM": "PROJEKTE",
"REPO_ITEM": "REPOSITORIES",
"INDEX_PRIVATE": "PRIVAT",
"INDEX_MY_PROJECTS": "MEINE PROJEKTE",
"INDEX_MY_REPOSITORIES": "MEINE REPOSITORIES",
"INDEX_PUB": "ÖFFENTLICH",
"INDEX_TOTAL": "GESAMT",
"STORAGE": "SPEICHER",
"LIMIT": "Limit"
"LIMIT": "Limit",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Suche...",

View File

@ -991,16 +991,14 @@
},
"TOP_REPO": "Popular Repositories",
"STATISTICS": {
"TITLE": "STATISTICS",
"PRO_ITEM": "PROJECTS",
"REPO_ITEM": "REPOSITORIES",
"INDEX_PRIVATE": "PRIVATE",
"INDEX_MY_PROJECTS": "MY PROJECTS",
"INDEX_MY_REPOSITORIES": "MY REPOSITORIES",
"INDEX_PUB": "PUBLIC",
"INDEX_TOTAL": "TOTAL",
"PRO_ITEM": "Projects",
"REPO_ITEM": "Repositories",
"INDEX_PRIVATE": "Private",
"INDEX_PUB": "Public",
"INDEX_TOTAL": "Total",
"STORAGE": "STORAGE",
"LIMIT": "Limit"
"LIMIT": "Limit",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Search...",

View File

@ -991,16 +991,14 @@
},
"TOP_REPO": "Repositorios Populares",
"STATISTICS": {
"TITLE": "ESTADÍSTICAS",
"PRO_ITEM": "PROYECTOS",
"REPO_ITEM": "REPOSITORIOS",
"INDEX_PRIVATE": "PRIVADO",
"INDEX_MY_PROJECTS": "MY PROJECTS",
"INDEX_MY_REPOSITORIES": "MY REPOSITORIES",
"INDEX_PUB": "PÚBLICO",
"INDEX_TOTAL": "TOTAL",
"STORAGE": "ALMACENAMIENTO",
"LIMIT": "Límite"
"LIMIT": "Límite",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Buscar...",

View File

@ -964,16 +964,14 @@
},
"TOP_REPO": "Dépôts Populaires",
"STATISTICS": {
"TITLE": "STATISTIQUES",
"PRO_ITEM": "PROJETS",
"REPO_ITEM": "DÉPÔTS",
"INDEX_PRIVATE": "PRIVÉ",
"INDEX_MY_PROJECTS": "MES PROJETS",
"INDEX_MY_REPOSITORIES": "MES DÉPÔTS",
"INDEX_PUB": "PUBLIC",
"INDEX_TOTAL": "TOTAL",
"STORAGE": "STOCKAGE",
"LIMIT": "Limite"
"LIMIT": "Limite",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Rechercher...",

View File

@ -987,16 +987,14 @@
},
"TOP_REPO": "Repositórios Populares",
"STATISTICS": {
"TITLE": "ESTATÍSTICAS",
"PRO_ITEM": "PROJETOS",
"REPO_ITEM": "REPOSITÓRIOS",
"INDEX_PRIVATE": "PRIVADO",
"INDEX_MY_PROJECTS": "MEUS PROJETOS",
"INDEX_MY_REPOSITORIES": "MEUS REPOSITÓRIOS",
"INDEX_PUB": "PUBLICO",
"INDEX_TOTAL": "TOTAL",
"STORAGE": "ARMAZENAMENTO",
"LIMIT": "Limite"
"LIMIT": "Limite",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Buscando...",

View File

@ -991,16 +991,14 @@
},
"TOP_REPO": "Popüler Depolar",
"STATISTICS": {
"TITLE": "İSTATİSTİK",
"PRO_ITEM": "PROJELER",
"REPO_ITEM": "DEPOLAR",
"INDEX_PRIVATE": "ÖZEL",
"INDEX_MY_PROJECTS": "BENİM PROJELERİM",
"INDEX_MY_REPOSITORIES": "BENİM DEPOLARIM",
"INDEX_PUB": "GENEL",
"INDEX_TOTAL": "TOPLAM",
"STORAGE": "DEPOLAMA",
"LIMIT": "Limit"
"LIMIT": "Limit",
"STORAGE_USED": "Storage used"
},
"SEARCH": {
"IN_PROGRESS": "Ara...",

View File

@ -991,16 +991,14 @@
},
"TOP_REPO": "受欢迎的镜像仓库",
"STATISTICS": {
"TITLE": "统计",
"PRO_ITEM": "项目",
"REPO_ITEM": "镜像仓库",
"INDEX_PRIVATE": "私有",
"INDEX_MY_PROJECTS": "我的项目",
"INDEX_MY_REPOSITORIES": "我的镜像仓库",
"INDEX_PUB": "公开",
"INDEX_TOTAL": "总计",
"STORAGE": "存储",
"LIMIT": "容量"
"LIMIT": "容量",
"STORAGE_USED": "已使用的存储空间"
},
"SEARCH": {
"IN_PROGRESS": "搜索中...",

View File

@ -987,16 +987,14 @@
},
"TOP_REPO": "受歡迎的鏡像倉庫",
"STATISTICS":{
"TITLE": "統計",
"PRO_ITEM": "項目",
"REPO_ITEM": "鏡像倉庫",
"INDEX_PRIVATE": "私有",
"INDEX_MY_PROJECTS": "我的項目",
"INDEX_MY_REPOSITORIES": "我的鏡像倉庫",
"INDEX_PUB": "公開",
"INDEX_TOTAL": "總計",
"STORAGE": "存儲",
"LIMIT": "容量"
"LIMIT": "容量",
"STORAGE_USED": "Storage used"
},
"SEARCH":{
"IN_PROGRESS": "搜索中...",