Merge pull request #12817 from AllForNothing/test-2

Fix issues with label target 2.1 and RC
This commit is contained in:
Will Sun 2020-08-20 15:13:27 +08:00 committed by GitHub
commit 446ae4c173
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 95 additions and 52 deletions

View File

@ -402,6 +402,7 @@
<clr-checkbox-wrapper> <clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox name="oidcAutoOnboard" id="oidcAutoOnboard" <input type="checkbox" clrCheckbox name="oidcAutoOnboard" id="oidcAutoOnboard"
[disabled]="disabled(currentConfig.oidc_auto_onboard)" [disabled]="disabled(currentConfig.oidc_auto_onboard)"
(change)="changeAutoOnBoard()"
[(ngModel)]="currentConfig.oidc_auto_onboard.value" /> [(ngModel)]="currentConfig.oidc_auto_onboard.value" />
</clr-checkbox-wrapper> </clr-checkbox-wrapper>
</clr-checkbox-container> </clr-checkbox-container>
@ -414,9 +415,9 @@
</clr-tooltip-content> </clr-tooltip-content>
</clr-tooltip> </clr-tooltip>
</label> </label>
<input clrInput name="oidcUserClaim" type="text" #oidcUserClaimInput="ngModel" <input autocomplete="off" clrInput name="oidcUserClaim" type="text" #oidcUserClaimInput="ngModel"
[(ngModel)]="currentConfig.oidc_user_claim.value" id="oidcUserClaim" size="40" [(ngModel)]="currentConfig.oidc_user_claim.value" id="oidcUserClaim" size="40"
[disabled]="disabled(currentConfig.oidc_user_claim)" pattern="^[a-zA-Z0-9_-]*$"> [disabled]="!currentConfig.oidc_auto_onboard.value || disabled(currentConfig.oidc_user_claim)" pattern="^[a-zA-Z0-9_-]*$">
</clr-input-container> </clr-input-container>
<div class="oidc-tip">{{ 'CONFIG.OIDC.OIDC_REDIREC_URL' | translate}} <div class="oidc-tip">{{ 'CONFIG.OIDC.OIDC_REDIREC_URL' | translate}}
<span>{{redirectUrl}}/c/oidc/callback</span> <span>{{redirectUrl}}/c/oidc/callback</span>
@ -431,4 +432,4 @@
<button type="button" id="ping-test" class="btn btn-outline" (click)="pingTestServer()" *ngIf="showTestingServerBtn" <button type="button" id="ping-test" class="btn btn-outline" (click)="pingTestServer()" *ngIf="showTestingServerBtn"
[disabled]="!isConfigValidForTesting()">{{(showLdap?'BUTTON.TEST_LDAP':'BUTTON.TEST_OIDC') | translate}}</button> [disabled]="!isConfigValidForTesting()">{{(showLdap?'BUTTON.TEST_LDAP':'BUTTON.TEST_OIDC') | translate}}</button>
<span id="forTestingLDAP" class="spinner spinner-inline" [hidden]="hideTestingSpinner"></span> <span id="forTestingLDAP" class="spinner spinner-inline" [hidden]="hideTestingSpinner"></span>
</div> </div>

View File

@ -263,5 +263,9 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
console.error('Nothing changed'); console.error('Nothing changed');
} }
} }
changeAutoOnBoard() {
if (!this.currentConfig.oidc_auto_onboard.value) {
this.currentConfig.oidc_user_claim.value = null;
}
}
} }

View File

@ -80,33 +80,6 @@
<clr-dg-cell>{{ instance.endpoint }}</clr-dg-cell> <clr-dg-cell>{{ instance.endpoint }}</clr-dg-cell>
<clr-dg-cell class="no-wrapper"> <clr-dg-cell class="no-wrapper">
<span>{{ instance.vendor }}</span> <span>{{ instance.vendor }}</span>
<clr-signpost *ngIf="providerMap[instance.vendor]">
<clr-signpost-content *clrIfOpen>
<div>
<span>
<img (error)="showDefaultIcon($event, instance.vendor)" class="height-24" [src]="providerMap[instance.vendor].icon">
</span>
</div>
<div class="margin-top-5px">
<span>{{'DISTRIBUTION.NAME' | translate}}:</span>
<span class="ml-1">{{providerMap[instance.vendor].name}}</span>
</div>
<div class="margin-top-5px">
<span class="no-wrapper">
<span>{{'DISTRIBUTION.MAINTAINER' | translate}}:</span>
<span class="ml-1">{{providerMap[instance.vendor].maintainers?.join(',')}}</span>
</span>
</div>
<div class="margin-top-5px">
<span>{{'DISTRIBUTION.SOURCE' | translate}}:</span>
<a target="_blank" href="{{providerMap[instance.vendor].source}}" class="ml-1">{{providerMap[instance.vendor].source}}</a>
</div>
<div class="margin-top-5px">
<span>{{'DISTRIBUTION.VERSION' | translate}}:</span>
<span class="ml-1">{{providerMap[instance.vendor].version}}</span>
</div>
</clr-signpost-content>
</clr-signpost>
</clr-dg-cell> </clr-dg-cell>
<clr-dg-cell> <clr-dg-cell>
<span *ngIf="!instance.hasCheckHealth;else elseBlockLoading" class="spinner spinner-inline ml-2"></span> <span *ngIf="!instance.hasCheckHealth;else elseBlockLoading" class="spinner spinner-inline ml-2"></span>

View File

@ -7,7 +7,7 @@
<clr-icon shape="download"></clr-icon> <clr-icon shape="download"></clr-icon>
{{'CONFIG.REGISTRY_CERTIFICATE' | translate | uppercase}} {{'CONFIG.REGISTRY_CERTIFICATE' | translate | uppercase}}
</a> </a>
<hbr-push-image-button class="push-image-button" *ngIf="hasCreateRepositoryPermission" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button> <hbr-push-image-button class="push-image-button" *ngIf="hasCreateRepositoryPermission && !isProxyCacheProject" [registryUrl]="registryUrl" [projectName]="projectName"></hbr-push-image-button>
<hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" [currentValue]="lastFilteredRepoName"></hbr-filter> <hbr-filter [withDivider]="true" filterPlaceholder="{{'REPOSITORY.FILTER_FOR_REPOSITORIES' | translate}}" [currentValue]="lastFilteredRepoName"></hbr-filter>
<span class="card-btn" (click)="showCard(true)" (mouseenter) ="mouseEnter('card') " (mouseleave) ="mouseLeave('card')"> <span class="card-btn" (click)="showCard(true)" (mouseenter) ="mouseEnter('card') " (mouseleave) ="mouseLeave('card')">
<clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon> <clr-icon [ngClass]="{'is-highlight': isCardView || isHovering('card') }" shape="view-cards"></clr-icon>

View File

@ -93,6 +93,7 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy
@ViewChild(FilterComponent, {static: true}) @ViewChild(FilterComponent, {static: true})
filterComponent: FilterComponent; filterComponent: FilterComponent;
searchSub: Subscription; searchSub: Subscription;
isProxyCacheProject: boolean = false;
constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig, constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig,
private errorHandlerService: ErrorHandler, private errorHandlerService: ErrorHandler,
@ -139,6 +140,9 @@ export class RepositoryGridviewComponent implements OnChanges, OnInit, OnDestroy
let pro: Project = <Project>resolverData['projectResolver']; let pro: Project = <Project>resolverData['projectResolver'];
this.hasProjectAdminRole = pro.has_project_admin_role; this.hasProjectAdminRole = pro.has_project_admin_role;
this.projectName = pro.name; this.projectName = pro.name;
if (pro.registry_id) {
this.isProxyCacheProject = true;
}
} }
this.hasSignedIn = this.session.getCurrentUser() !== null; this.hasSignedIn = this.session.getCurrentUser() !== null;
// Get system info for tag views // Get system info for tag views

View File

@ -29,7 +29,8 @@
</clr-checkbox-wrapper> </clr-checkbox-wrapper>
</div> </div>
<div [class.visibility-hidden]="!isError" class="error active"> <div [class.visibility-hidden]="!isError" class="error active">
{{ 'SIGN_IN.INVALID_MSG' | translate }} <span *ngIf="isCoreServiceAvailable">{{ 'SIGN_IN.INVALID_MSG' | translate }}</span>
<span *ngIf="!isCoreServiceAvailable">{{ 'SIGN_IN.CORE_SERVICE_NOT_AVAILABLE' | translate }}</span>
</div> </div>
<button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button> <button [disabled]="isOnGoing || !isValid" type="submit" class="btn btn-primary" (click)="signIn()" id="log_in">{{ 'BUTTON.LOG_IN' | translate }}</button>
<a href="javascript:void(0)" class="signup" (click)="signUp()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a> <a href="javascript:void(0)" class="signup" (click)="signUp()" *ngIf="selfSignUp">{{ 'BUTTON.SIGN_UP_LINK' | translate }}</a>
@ -44,4 +45,4 @@
</div> </div>
</clr-main-container> </clr-main-container>
<sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up> <sign-up #signupDialog (userCreation)="handleUserCreation($event)"></sign-up>
<about-dialog></about-dialog> <about-dialog></about-dialog>

View File

@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, getTestBed, inject, TestBed } from '@angular/core/testing';
import { SignInComponent } from './sign-in.component'; import { SignInComponent } from './sign-in.component';
import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
@ -10,11 +10,18 @@ import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
import { ClarityModule } from "@clr/angular"; import { ClarityModule } from "@clr/angular";
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { of } from "rxjs"; import { of } from "rxjs";
import { throwError as observableThrowError } from 'rxjs/internal/observable/throwError';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
describe('SignInComponent', () => { describe('SignInComponent', () => {
let component: SignInComponent; let component: SignInComponent;
let fixture: ComponentFixture<SignInComponent>; let fixture: ComponentFixture<SignInComponent>;
const mockedSessionService = {
signIn() {
return of(true);
}
};
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
@ -22,12 +29,13 @@ describe('SignInComponent', () => {
RouterTestingModule, RouterTestingModule,
ClarityModule, ClarityModule,
FormsModule, FormsModule,
ReactiveFormsModule ReactiveFormsModule,
HttpClientTestingModule
], ],
declarations: [SignInComponent], declarations: [SignInComponent],
providers: [ providers: [
TranslateService, TranslateService,
{ provide: SessionService, useValue: null }, { provide: SessionService, useValue: mockedSessionService},
{ {
provide: AppConfigService, useValue: { provide: AppConfigService, useValue: {
load: function () { load: function () {
@ -68,4 +76,44 @@ describe('SignInComponent', () => {
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should show core service is not available', async () => {
expect(component).toBeTruthy();
const sessionService = TestBed.get<SessionService>(SessionService);
const spy: jasmine.Spy = spyOn(sessionService, "signIn").and.returnValue(observableThrowError( new HttpErrorResponse({
error: 'test 501 error',
status: 501
})));
signIn();
fixture.detectChanges();
await fixture.whenStable();
expect(spy.calls.count()).toEqual(1);
const errorSpan: HTMLSpanElement = fixture.nativeElement.querySelector(".error>span");
expect(errorSpan.innerText).toEqual('SIGN_IN.CORE_SERVICE_NOT_AVAILABLE');
});
it('should show invalid username or password', async () => {
expect(component).toBeTruthy();
const sessionService = TestBed.get<SessionService>(SessionService);
const spy: jasmine.Spy = spyOn(sessionService, "signIn").and.returnValue(observableThrowError( new HttpErrorResponse({
error: 'test 404 error',
status: 404,
statusText: 'Not Found'
})));
signIn();
fixture.detectChanges();
await fixture.whenStable();
expect(spy.calls.count()).toEqual(1);
const errorSpan: HTMLSpanElement = fixture.nativeElement.querySelector(".error>span");
expect(errorSpan.innerText).toEqual('SIGN_IN.INVALID_MSG');
});
function signIn() {
const nameInput: HTMLInputElement = fixture.nativeElement.querySelector("#login_username");
nameInput.value = "admin";
nameInput.dispatchEvent(new Event('input'));
const passwordInput: HTMLInputElement = fixture.nativeElement.querySelector("#login_password");
passwordInput.value = "Harbor12345";
passwordInput.dispatchEvent(new Event('input'));
const signButton: HTMLAnchorElement = fixture.nativeElement.querySelector("#log_in");
signButton.click();
}
}); });

View File

@ -66,7 +66,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
principal: "", principal: "",
password: "" password: ""
}; };
isCoreServiceAvailable: boolean = true;
constructor( constructor(
private router: Router, private router: Router,
private session: SessionService, private session: SessionService,
@ -254,6 +254,7 @@ export class SignInComponent implements AfterViewChecked, OnInit {
} else { } else {
this.router.navigateByUrl(this.redirectUrl); this.router.navigateByUrl(this.redirectUrl);
} }
this.isCoreServiceAvailable = true;
}, error => { }, error => {
// 403 oidc login no body; // 403 oidc login no body;
if (this.isOidcLoginMode && error && error.status === 403) { if (this.isOidcLoginMode && error && error.status === 403) {
@ -265,6 +266,10 @@ export class SignInComponent implements AfterViewChecked, OnInit {
return; return;
} catch (error) { } } catch (error) { }
} }
// core service is not available for error code 5xx
if (error && /5[0-9][0-9]/.test(error.status)) {
this.isCoreServiceAvailable = false;
}
this.handleError(error); this.handleError(error);
}); });
} }

View File

@ -13,7 +13,8 @@
"REMEMBER": "Remember me", "REMEMBER": "Remember me",
"INVALID_MSG": "Invalid user name or password.", "INVALID_MSG": "Invalid user name or password.",
"FORGOT_PWD": "Forgot password", "FORGOT_PWD": "Forgot password",
"HEADER_LINK": "Sign In" "HEADER_LINK": "Sign In",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "Sign Up" "TITLE": "Sign Up"
@ -247,7 +248,7 @@
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.", "QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },

View File

@ -13,7 +13,8 @@
"REMEMBER": "Recordarme", "REMEMBER": "Recordarme",
"INVALID_MSG": "Nombre o contraseña no válidos.", "INVALID_MSG": "Nombre o contraseña no válidos.",
"FORGOT_PWD": "Olvidé mi contraseña", "FORGOT_PWD": "Olvidé mi contraseña",
"HEADER_LINK": "Identificarse" "HEADER_LINK": "Identificarse",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "Registrarse" "TITLE": "Registrarse"
@ -248,7 +249,7 @@
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.", "QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },

View File

@ -13,7 +13,8 @@
"REMEMBER": "Se souvenir de moi", "REMEMBER": "Se souvenir de moi",
"INVALID_MSG": "Nom d'utilisateur ou mot de passe invalide.", "INVALID_MSG": "Nom d'utilisateur ou mot de passe invalide.",
"FORGOT_PWD": "Mot de passe oublié", "FORGOT_PWD": "Mot de passe oublié",
"HEADER_LINK": "S'identifier" "HEADER_LINK": "S'identifier",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "S'inscrire" "TITLE": "S'inscrire"
@ -241,7 +242,7 @@
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.", "QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },

View File

@ -13,7 +13,8 @@
"REMEMBER": "Lembrar-se de mim", "REMEMBER": "Lembrar-se de mim",
"INVALID_MSG": "Usuário ou senha inválidos", "INVALID_MSG": "Usuário ou senha inválidos",
"FORGOT_PWD": "Esqueci a senha", "FORGOT_PWD": "Esqueci a senha",
"HEADER_LINK": "Logar-se" "HEADER_LINK": "Logar-se",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "Registrar-se" "TITLE": "Registrar-se"
@ -245,7 +246,7 @@
"QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.", "QUOTA_UNLIMIT_TIP": "For unlimited quota enter '-1'.",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },

View File

@ -13,7 +13,8 @@
"REMEMBER": "Beni Hatırla", "REMEMBER": "Beni Hatırla",
"INVALID_MSG": "Geçersiz kullanıcı adı veya şifre.", "INVALID_MSG": "Geçersiz kullanıcı adı veya şifre.",
"FORGOT_PWD": "Parolamı Unuttum", "FORGOT_PWD": "Parolamı Unuttum",
"HEADER_LINK": "Oturum aç" "HEADER_LINK": "Oturum aç",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "Kayıt ol" "TITLE": "Kayıt ol"
@ -247,7 +248,7 @@
"QUOTA_UNLIMIT_TIP": "Bu kotayı sınırsız istiyorsanız, lütfen -1 girin.", "QUOTA_UNLIMIT_TIP": "Bu kotayı sınırsız istiyorsanız, lütfen -1 girin.",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },

View File

@ -13,7 +13,8 @@
"REMEMBER": "记住我", "REMEMBER": "记住我",
"INVALID_MSG": "用户名或者密码不正确。", "INVALID_MSG": "用户名或者密码不正确。",
"FORGOT_PWD": "忘记密码", "FORGOT_PWD": "忘记密码",
"HEADER_LINK": "登录" "HEADER_LINK": "登录",
"CORE_SERVICE_NOT_AVAILABLE": "核心服务不可用。"
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "注册" "TITLE": "注册"

View File

@ -13,7 +13,8 @@
"REMEMBER": "記住我", "REMEMBER": "記住我",
"INVALID_MSG": "用戶名或者密碼不正確。", "INVALID_MSG": "用戶名或者密碼不正確。",
"FORGOT_PWD": "忘記密碼", "FORGOT_PWD": "忘記密碼",
"HEADER_LINK": "登錄" "HEADER_LINK": "登錄",
"CORE_SERVICE_NOT_AVAILABLE": "Core service is not available."
}, },
"SIGN_UP": { "SIGN_UP": {
"TITLE": "註冊" "TITLE": "註冊"
@ -244,7 +245,7 @@
"QUOTA_UNLIMIT_TIP": "如果你想要對存儲不設置上限,請輸入-1。", "QUOTA_UNLIMIT_TIP": "如果你想要對存儲不設置上限,請輸入-1。",
"TYPE": "Type", "TYPE": "Type",
"PROXY_CACHE": "Proxy Cache", "PROXY_CACHE": "Proxy Cache",
"PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular namespace within a target registry. Harbor can only act a proxy for DockerHub and Harbor registries.", "PROXY_CACHE_TOOLTIP": "Enable this to allow this project to act as a pull-through cache for a particular target registry instance. Harbor can only act a proxy for DockerHub and Harbor registries.",
"ENDPOINT": "Endpoint", "ENDPOINT": "Endpoint",
"PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint" "PROXY_CACHE_ENDPOINT": "Proxy Cache Endpoint"
}, },