mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 09:38:09 +01:00
Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
6b55bf488a
51
docs/use_make.md
Normal file
51
docs/use_make.md
Normal file
@ -0,0 +1,51 @@
|
||||
### Variables
|
||||
Variable | Description
|
||||
-------------------|-------------
|
||||
BASEIMAGE | Container base image, default: photon
|
||||
DEVFLAG | Build model flag, default: dev
|
||||
COMPILETAG | Compile model flag, default: compile_normal (local golang build)
|
||||
GOBUILDIMAGE | Golang image to compile harbor go source code.
|
||||
CLARITYIMAGE | Clarity image that based on Node to compile UI.
|
||||
NOTARYFLAG | Whether to enable notary in harbor, default:false
|
||||
HTTPPROXY | Clarity proxy to build UI.
|
||||
|
||||
|
||||
### Targets
|
||||
Target | Description
|
||||
--------------------|-------------
|
||||
all | prepare env, compile binaries, build images and install images
|
||||
prepare | prepare env
|
||||
compile | compile ui and jobservice code
|
||||
compile_ui | compile ui binary
|
||||
compile_jobservice | compile jobservice binary
|
||||
compile_clarity | compile clarity ui binary
|
||||
compile_adminserver | compile admin server binary
|
||||
build | build Harbor docker images (default: using build_photon)
|
||||
build_photon | build Harbor docker images from Photon OS base image
|
||||
install | compile binaries, build images, prepare specific version of compose file and startup Harbor instance
|
||||
start | startup Harbor instance
|
||||
down | shutdown Harbor instance
|
||||
package_online | prepare online install package
|
||||
package_offline | prepare offline install package
|
||||
pushimage | push Harbor images to specific registry server
|
||||
clean all | remove binary, Harbor images, specific version docker-compose file, specific version tag and online/offline install package
|
||||
cleanbinary | remove ui and jobservice binary
|
||||
cleanimage | remove Harbor images
|
||||
cleandockercomposefile | remove specific version docker-compose
|
||||
cleanversiontag | remove specific version tag
|
||||
cleanpackage | remove online/offline install package
|
||||
version | set harbor version
|
||||
|
||||
#### EXAMPLE:
|
||||
|
||||
#### Build and run harbor from source code.
|
||||
make install GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128
|
||||
|
||||
### Package offline installer
|
||||
make package_offline GOBUILDIMAGE=golang:1.7.3 COMPILETAG=compile_golangimage CLARITYIMAGE=danieljt/harbor-clarity-base:0.8.4 NOTARYFLAG=true HTTPPROXY=http://proxy.vmware.com:3128
|
||||
|
||||
### Start harbor with notary
|
||||
make -e NOTARYFLAG=true start
|
||||
|
||||
### Stop harbor with notary
|
||||
make -e NOTARYFLAG=true down
|
@ -120,15 +120,8 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
//Handle the global search event and then let the result page to trigger api
|
||||
doSearch(event: string): void {
|
||||
if (event === "") {
|
||||
if (!this.isSearchResultsOpened) {
|
||||
//Will not open search result panel if term is empty
|
||||
return;
|
||||
} else {
|
||||
//If opened, then close the search result panel
|
||||
this.isSearchResultsOpened = false;
|
||||
this.searchResultComponet.close();
|
||||
return;
|
||||
}
|
||||
//Do nothing
|
||||
return;
|
||||
}
|
||||
//Once this method is called
|
||||
//the search results page must be opened
|
||||
|
62
src/ui_ng/src/app/config/config.component.1.html
Normal file
62
src/ui_ng/src/app/config/config.component.1.html
Normal file
@ -0,0 +1,62 @@
|
||||
<div class="config-container">
|
||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]='isCurrentTabLink("config-auth")'>{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-replication'" [clrTabLinkActive]='isCurrentTabLink("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-email'" [clrTabLinkActive]='isCurrentTabLink("config-email")'>{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-system'" [clrTabLinkActive]='isCurrentTabLink("config-system")'>{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
||||
|
||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]='isCurrentTabContent("authentication")'>
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'replication'" [clrTabContentActive]='isCurrentTabContent("replication")'>
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'email'" [clrTabContentActive]='isCurrentTabContent("email")'>
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'system_settings'" [clrTabContentActive]='isCurrentTabContent("system_settings")'>
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
id="tokenExpiration"
|
||||
size="40" [disabled]="disabled(allConfig.token_expiration)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
</clr-tabs>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="testMailServer()" *ngIf="showTestServerBtn" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
|
||||
<span class="spinner spinner-inline" [hidden]="!testingInProgress"></span>
|
||||
</div>
|
||||
</div>
|
@ -1,39 +1,47 @@
|
||||
<div class="config-container">
|
||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]="true">{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-replication'">{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-email'">{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
||||
<clr-tab-link [clrTabLinkId]="'config-system'">{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
||||
|
||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]="true">
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'replication'">
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'email'">
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</clr-tab-content>
|
||||
<clr-tab-content [clrTabContentId]="'system_settings'">
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<ul id="configTabs" class="nav" role="tablist">
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-auth" class="btn btn-link nav-link active" aria-controls="authentication" [class.active]='isCurrentTabLink("config-auth")' type="button" (click)='tabLinkClick("config-auth")'>{{'CONFIG.AUTH' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-replication" class="btn btn-link nav-link" aria-controls="replication" [class.active]='isCurrentTabLink("config-replication")' type="button" (click)='tabLinkClick("config-replication")'>{{'CONFIG.REPLICATION' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-email" class="btn btn-link nav-link" aria-controls="email" [class.active]='isCurrentTabLink("config-email")' type="button" (click)='tabLinkClick("config-email")'>{{'CONFIG.EMAIL' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-system" class="btn btn-link nav-link" aria-controls="system_settings" [class.active]='isCurrentTabLink("config-system")' type="button" (click)='tabLinkClick("config-system")'>{{'CONFIG.SYSTEM' | translate }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</section>
|
||||
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</section>
|
||||
<section id="system_settings" role="tabpanel" aria-labelledby="config-system" [hidden]='!isCurrentTabContent("system_settings")'>
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
@ -43,15 +51,14 @@
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</clr-tab-content>
|
||||
</clr-tabs>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
|
@ -16,8 +16,15 @@ import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
const fakePass = "fakepassword";
|
||||
const TabLinkContentMap = {
|
||||
"config-auth": "authentication",
|
||||
"config-replication": "replication",
|
||||
"config-email": "email",
|
||||
"config-system": "system_settings"
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'config',
|
||||
@ -27,7 +34,7 @@ const fakePass = "fakepassword";
|
||||
export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private onGoing: boolean = false;
|
||||
allConfig: Configuration = new Configuration();
|
||||
private currentTabId: string = "";
|
||||
private currentTabId: string = "config-auth";//default tab
|
||||
private originalCopy: Configuration;
|
||||
private confirmSub: Subscription;
|
||||
private testingOnGoing: boolean = false;
|
||||
@ -41,17 +48,76 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private msgService: MessageService,
|
||||
private configService: ConfigurationService,
|
||||
private confirmService: ConfirmationDialogService,
|
||||
private appConfigService: AppConfigService) { }
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService) { }
|
||||
|
||||
private isCurrentTabLink(tabId: string): boolean {
|
||||
return this.currentTabId === tabId;
|
||||
}
|
||||
|
||||
private isCurrentTabContent(contentId: string): boolean {
|
||||
return TabLinkContentMap[this.currentTabId] === contentId;
|
||||
}
|
||||
|
||||
private hasUnsavedChangesOfCurrentTab(): any {
|
||||
let allChanges = this.getChanges();
|
||||
if (this.isEmpty(allChanges)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let properties = [];
|
||||
switch (this.currentTabId) {
|
||||
case "config-auth":
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith("ldap_")) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
properties = ["auth_mode", "project_creation_restriction", "self_registration"];
|
||||
break;
|
||||
case "config-email":
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith("email_")) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case "config-replication":
|
||||
properties = ["verify_remote_cert"];
|
||||
break;
|
||||
case "config-system":
|
||||
properties = ["token_expiration"];
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let prop in allChanges) {
|
||||
if (properties.indexOf(prop) != -1) {
|
||||
return allChanges;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
//First load
|
||||
this.retrieveConfig();
|
||||
//Double confirm the current use has admin role
|
||||
let currentUser = this.session.getCurrentUser();
|
||||
if (currentUser && currentUser.has_admin_role > 0) {
|
||||
this.retrieveConfig();
|
||||
}
|
||||
|
||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED &&
|
||||
confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
if (confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
} else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) {
|
||||
this.reset(confirmation.data["changes"]);
|
||||
this.currentTabId = confirmation.data["tabId"];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -104,8 +170,15 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
return this.authConfig && this.authConfig.isValid();
|
||||
}
|
||||
|
||||
public tabLinkChanged(tabLink: any) {
|
||||
this.currentTabId = tabLink.id;
|
||||
public tabLinkClick(tabLink: string) {
|
||||
//Whether has unsave changes in current tab
|
||||
let changes = this.hasUnsavedChangesOfCurrentTab();
|
||||
if (!changes) {
|
||||
this.currentTabId = tabLink;
|
||||
return;
|
||||
}
|
||||
|
||||
this.confirmUnsavedTabChanges(changes, tabLink);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -154,14 +227,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
this.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
//Inprop situation, should not come here
|
||||
console.error("Nothing changed");
|
||||
@ -218,6 +284,33 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private confirmUnsavedChanges(changes: any) {
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
private confirmUnsavedTabChanges(changes: any, tabId: string){
|
||||
let msg = new ConfirmationMessage(
|
||||
"CONFIG.CONFIRM_TITLE",
|
||||
"CONFIG.CONFIRM_SUMMARY",
|
||||
"",
|
||||
{
|
||||
"changes": changes,
|
||||
"tabId": tabId
|
||||
},
|
||||
ConfirmationTargets.CONFIG_TAB
|
||||
);
|
||||
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
|
||||
private retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
|
@ -18,6 +18,7 @@ export class MessageComponent implements OnInit {
|
||||
globalMessage: Message = new Message();
|
||||
globalMessageOpened: boolean;
|
||||
messageText: string = "";
|
||||
private timer: any = null;
|
||||
|
||||
constructor(
|
||||
private messageService: MessageService,
|
||||
@ -48,7 +49,7 @@ export class MessageComponent implements OnInit {
|
||||
|
||||
// Make the message alert bar dismiss after several intervals.
|
||||
//Only for this case
|
||||
setInterval(() => this.onClose(), dismissInterval);
|
||||
this.timer = setTimeout(() => this.onClose(), dismissInterval);
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -56,15 +57,9 @@ export class MessageComponent implements OnInit {
|
||||
|
||||
//Translate or refactor the message shown to user
|
||||
translateMessage(msg: Message): void {
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = "";
|
||||
if (!msg.message) {
|
||||
key = "UNKNOWN_ERROR";
|
||||
} else {
|
||||
key = typeof msg.message === "string" ? msg.message.trim() : msg.message;
|
||||
let key = "UNKNOWN_ERROR", param = "";
|
||||
if (msg && msg.message) {
|
||||
key = (typeof msg.message === "string" ? msg.message.trim() : msg.message);
|
||||
if (key === "") {
|
||||
key = "UNKNOWN_ERROR";
|
||||
}
|
||||
@ -73,13 +68,11 @@ export class MessageComponent implements OnInit {
|
||||
//Override key for HTTP 401 and 403
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||
key = "UNAUTHORIZED_ERROR";
|
||||
}
|
||||
|
||||
if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
} else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||
key = "FORBIDDEN_ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
this.translate.get(key).subscribe((res: string) => this.messageText = res);
|
||||
this.translate.get(key, { 'param': param }).subscribe((res: string) => this.messageText = res);
|
||||
}
|
||||
|
||||
public get needAuth(): boolean {
|
||||
@ -98,6 +91,9 @@ export class MessageComponent implements OnInit {
|
||||
}
|
||||
|
||||
onClose() {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.globalMessageOpened = false;
|
||||
}
|
||||
}
|
@ -33,6 +33,8 @@ import { AuthCheckGuard } from './shared/route/auth-user-activate.service';
|
||||
import { SignInGuard } from './shared/route/sign-in-guard-activate.service';
|
||||
import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service';
|
||||
|
||||
import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||
|
||||
const harborRoutes: Routes = [
|
||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||
{ path: 'password-reset', component: ResetPasswordComponent },
|
||||
@ -79,6 +81,7 @@ const harborRoutes: Routes = [
|
||||
{
|
||||
path: 'projects/:id',
|
||||
component: ProjectDetailComponent,
|
||||
canActivate: [MemberGuard],
|
||||
resolve: {
|
||||
projectResolver: ProjectRoutingResolver
|
||||
},
|
||||
@ -89,7 +92,8 @@ const harborRoutes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'replication',
|
||||
component: ReplicationComponent
|
||||
component: ReplicationComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'member',
|
||||
|
@ -44,5 +44,4 @@ export class AuditLogService extends BaseService {
|
||||
.map(response => response.json() as AuditLog[])
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
}
|
@ -2,25 +2,18 @@
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
|
||||
.filter-log {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.action-head-pos {
|
||||
position: relative;
|
||||
top: 20px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
position: absolute;
|
||||
right: -4px;
|
||||
top: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
color: #00bfff;
|
||||
}
|
||||
|
||||
.custom-lines-button {
|
||||
padding: 0px !important;
|
||||
min-width: 25px !important;
|
||||
@ -29,4 +22,21 @@
|
||||
.lines-button-toggole {
|
||||
font-size: 16px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.log-select {
|
||||
width: 180px;
|
||||
display: inline-block;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.item-divider {
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
background-color: #ccc;
|
||||
opacity: 0.55;
|
||||
margin-left: 12px;
|
||||
top: 8px;
|
||||
position: relative;
|
||||
}
|
@ -1,20 +1,24 @@
|
||||
<div>
|
||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}</h2>
|
||||
<div class="action-head-pos">
|
||||
<span>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE' | translate}} </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 10" (click)="setLines(10)">10</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 25" (click)="setLines(25)">25</button>
|
||||
<label> | </label>
|
||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 50" (click)="setLines(50)">50</button>
|
||||
<label>{{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</label>
|
||||
</span>
|
||||
<grid-filter class="filter-log" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}
|
||||
<span class="badge badge-info">{{logNumber}}</span>
|
||||
</h2>
|
||||
<div class="row flex-items-xs-between flex-items-xs-bottom">
|
||||
<div></div>
|
||||
<div class="action-head-pos">
|
||||
<div class="select log-select">
|
||||
<select id="log_display_num" (change)="handleOnchange($event)">
|
||||
<option value="10">{{'RECENT_LOG.SUB_TITLE' | translate}} 10 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
<option value="25">{{'RECENT_LOG.SUB_TITLE' | translate}} 25 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
<option value="50">{{'RECENT_LOG.SUB_TITLE' | translate}} 50 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="item-divider"></div>
|
||||
<grid-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||
<span (click)="refresh()" class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid>
|
||||
|
@ -34,17 +34,22 @@ export class RecentLogComponent implements OnInit {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
private handleOnchange($event: any) {
|
||||
if (event && event.target && event.srcElement["value"]) {
|
||||
this.lines = event.srcElement["value"];
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
this.retrieveLogs();
|
||||
}
|
||||
}
|
||||
|
||||
public setLines(lines: number): void {
|
||||
this.lines = lines;
|
||||
if (this.lines < 10) {
|
||||
this.lines = 10;
|
||||
}
|
||||
public get logNumber(): number {
|
||||
return this.recentLogs?this.recentLogs.length:0;
|
||||
}
|
||||
|
||||
this.retrieveLogs();
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
public doFilter(terms: string): void {
|
||||
@ -60,7 +65,7 @@ export class RecentLogComponent implements OnInit {
|
||||
this.retrieveLogs();
|
||||
}
|
||||
|
||||
public formatDateTime(dateTime: string){
|
||||
public formatDateTime(dateTime: string) {
|
||||
let dt: Date = new Date(dateTime);
|
||||
return dt.toLocaleString();
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'create-project',
|
||||
templateUrl: 'create-project.component.html',
|
||||
@ -50,6 +49,7 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||
.subscribe(
|
||||
status=>{
|
||||
this.create.emit(true);
|
||||
this.messageService.announceMessage(status, 'PROJECT.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
this.createProjectOpened = false;
|
||||
},
|
||||
error=>{
|
||||
|
@ -5,8 +5,8 @@
|
||||
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'PROJECT.DESCRIPTION' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<button class="action-item" (click)="newReplicationRule(p)">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
||||
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1">
|
||||
<button class="action-item" (click)="newReplicationRule(p)" [hidden]="!isSystemAdmin">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
||||
<button class="action-item" (click)="toggleProject(p)">{{'PROJECT.MAKE' | translate}} {{(p.public === 0 ? 'PROJECT.PUBLIC' : 'PROJECT.PRIVATE') | translate}} </button>
|
||||
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
|
@ -43,6 +43,11 @@ export class ListProjectComponent implements OnInit {
|
||||
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
|
||||
}
|
||||
|
||||
public get isSystemAdmin(): boolean {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null && account.has_admin_role > 0;
|
||||
}
|
||||
|
||||
goToLink(proId: number): void {
|
||||
this.searchTrigger.closeSearch(false);
|
||||
|
||||
|
@ -19,15 +19,15 @@
|
||||
<div class="form-group">
|
||||
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" value="1" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_project_admin" [value]="1" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" value="2" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_developer" [value]="2" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" value="3" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="roleRadios" id="checkrads_guest" [value]="3" [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
@ -36,6 +36,6 @@
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!memberForm.form.valid" (click)="onSubmit()">{{'BUTTON.OK' | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
||||
|
@ -51,6 +51,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'MEMBER.ADDED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Added member successfully.');
|
||||
this.added.emit(true);
|
||||
this.addMemberOpened = false;
|
||||
@ -112,9 +113,11 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.memberForm.reset();
|
||||
this.member = new Member();
|
||||
this.addMemberOpened = true;
|
||||
this.hasChanged = false;
|
||||
this.member.role_id = 1;
|
||||
}
|
||||
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="flex-xs-middle option-left">
|
||||
<button class="btn btn-primary" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.MEMBER' | translate }}</button>
|
||||
<button *ngIf="hasProjectAdminRole" class="btn btn-primary" (click)="openAddMemberModal()"><clr-icon shape="add"></clr-icon> {{'MEMBER.MEMBER' | translate }}</button>
|
||||
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||
</div>
|
||||
<div class="flex-xs-middle option-right">
|
||||
@ -18,7 +18,7 @@
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let u of members">
|
||||
<clr-dg-action-overflow [hidden]="u.user_id === currentUser.user_id">
|
||||
<clr-dg-action-overflow [hidden]="u.user_id === currentUser.user_id || !hasProjectAdminRole">
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 1)">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | translate}}</button>
|
||||
|
@ -40,12 +40,22 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
constructor(private route: ActivatedRoute, private router: Router,
|
||||
private memberService: MemberService, private messageService: MessageService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
session: SessionService) {
|
||||
//Get current user from registered resolver.
|
||||
this.currentUser = session.getCurrentUser();
|
||||
let projectMembers: Member[] = session.getProjectMembers();
|
||||
if(this.currentUser && projectMembers) {
|
||||
let currentMember = projectMembers.find(m=>m.user_id === this.currentUser.user_id);
|
||||
if(currentMember) {
|
||||
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
|
||||
}
|
||||
}
|
||||
|
||||
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
@ -54,7 +64,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
.deleteMember(this.projectId, message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
console.log('Successful change role with user ' + message.data);
|
||||
this.messageService.announceMessage(response, 'MEMBER.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete member: ' + message.data);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
|
||||
@ -85,7 +96,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
//Get projectId from route params snapshot.
|
||||
this.projectId = +this.route.snapshot.parent.params['id'];
|
||||
console.log('Get projectId from route params snapshot:' + this.projectId);
|
||||
|
||||
|
||||
this.retrieve(this.projectId, '');
|
||||
}
|
||||
|
||||
@ -102,6 +113,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
.changeMemberRole(this.projectId, userId, roleId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||
this.retrieve(this.projectId, '');
|
||||
},
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||
<div class="row flex-items-xs-between">
|
||||
<div class="option-left">
|
||||
<button class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
|
||||
<button *ngIf="projectCreationRestriction" class="btn btn-primary" (click)="openModal()"><clr-icon shape="add"></clr-icon> {{'PROJECT.PROJECT' | translate}}</button>
|
||||
<create-project (create)="createProject($event)"></create-project>
|
||||
</div>
|
||||
<div class="option-right">
|
||||
|
@ -23,6 +23,10 @@ import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { State } from 'clarity-angular';
|
||||
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
|
||||
|
||||
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
|
||||
@Component({
|
||||
@ -59,6 +63,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
private projectService: ProjectService,
|
||||
private messageService: MessageService,
|
||||
private appConfigService: AppConfigService,
|
||||
private sessionService: SessionService,
|
||||
private deletionDialogService: ConfirmationDialogService) {
|
||||
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||
if (message &&
|
||||
@ -69,6 +75,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
.deleteProject(projectId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'PROJECT.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete project with ID:' + projectId);
|
||||
this.retrieve();
|
||||
},
|
||||
@ -76,11 +83,13 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.projectName = '';
|
||||
this.isPublic = 0;
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -89,6 +98,19 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
get projectCreationRestriction(): boolean {
|
||||
let account = this.sessionService.getCurrentUser();
|
||||
if(account) {
|
||||
switch(this.appConfigService.getConfig().project_creation_restriction) {
|
||||
case 'adminonly':
|
||||
return (account.has_admin_role === 1);
|
||||
case 'everyone':
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
retrieve(state?: State): void {
|
||||
if (state) {
|
||||
this.page = state.page.to + 1;
|
||||
@ -135,7 +157,10 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||
this.projectService
|
||||
.toggleProjectPublic(p.project_id, p.public)
|
||||
.subscribe(
|
||||
response => console.log('Successful toggled project_id:' + p.project_id),
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'PROJECT.TOGGLED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful toggled project_id:' + p.project_id);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||
);
|
||||
}
|
||||
|
@ -70,4 +70,11 @@ export class ProjectService {
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
checkProjectMember(projectId: number): Observable<any> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/members`)
|
||||
.map(response=>response.json())
|
||||
.catch(error=>Observable.throw(error));
|
||||
}
|
||||
|
||||
}
|
@ -108,6 +108,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.createTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'DESTINATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful added target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -129,7 +130,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
||||
this.inlineAlert.showInlineError(errorMessageKey);
|
||||
this.inlineAlert.showInlineError(res);
|
||||
});
|
||||
}
|
||||
);
|
||||
@ -139,6 +140,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
.updateTarget(this.target)
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'DESTINATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful updated target.');
|
||||
this.createEditDestinationOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -158,7 +160,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||
this.translateService
|
||||
.get(errorMessageKey)
|
||||
.subscribe(res=>{
|
||||
this.inlineAlert.showInlineError(errorMessageKey);
|
||||
this.inlineAlert.showInlineError(res);
|
||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
||||
});
|
||||
}
|
||||
|
@ -43,14 +43,15 @@ export class DestinationComponent implements OnInit {
|
||||
.deleteTarget(targetId)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'DESTINATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful deleted target with ID:' + targetId);
|
||||
this.reload();
|
||||
},
|
||||
error => this.messageService
|
||||
.announceMessage(error.status,
|
||||
'Failed to delete target with ID:' + targetId + ', error:' + error,
|
||||
AlertType.DANGER)
|
||||
);
|
||||
error => {
|
||||
this.messageService
|
||||
.announceMessage(error.status,'DESTINATION.DELETED_FAILED', AlertType.DANGER);
|
||||
console.log('Failed to delete target with ID:' + targetId + ', error:' + error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<clr-dg-column>{{'REPOSITORY.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COUNT' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let r of repositories" [clrDgItem]='r'>
|
||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
||||
<clr-dg-action-overflow *ngIf="listFullMode && hasProjectAdminRole">
|
||||
<button class="action-item">{{'REPOSITORY.COPY_ID' | translate}}</button>
|
||||
<button class="action-item">{{'REPOSITORY.COPY_PARENT_ID' | translate}}</button>
|
||||
<button class="action-item" (click)="deleteRepo(r.name)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
|
@ -7,6 +7,9 @@ import { SearchTriggerService } from '../../base/global-search/search-trigger.se
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ListMode } from '../../shared/shared.const';
|
||||
|
||||
import { SessionUser } from '../../shared/session-user';
|
||||
import { Member } from '../../project/member/member';
|
||||
|
||||
@Component({
|
||||
selector: 'list-repository',
|
||||
templateUrl: 'list-repository.component.html'
|
||||
@ -25,10 +28,22 @@ export class ListRepositoryComponent {
|
||||
|
||||
pageOffset: number = 1;
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private searchTrigger: SearchTriggerService,
|
||||
private session: SessionService) { }
|
||||
private session: SessionService) {
|
||||
//Get current user from registered resolver.
|
||||
let currentUser = session.getCurrentUser();
|
||||
let projectMembers: Member[] = session.getProjectMembers();
|
||||
if(currentUser && projectMembers) {
|
||||
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
|
||||
if(currentMember) {
|
||||
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteRepo(repoName: string) {
|
||||
this.delete.emit(repoName);
|
||||
|
@ -60,6 +60,7 @@ export class RepositoryComponent implements OnInit {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.refresh();
|
||||
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_REPO_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful deleted repo:' + repoName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
||||
|
@ -3,19 +3,19 @@
|
||||
<clr-datagrid>
|
||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column *ngIf="withNotary">{{'REPOSITORY.SIGNED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.AUTHOR' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.CREATED' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'REPOSITORY.OS' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *ngFor="let t of tags" [clrDgItem]='t'>
|
||||
<clr-dg-action-overflow>
|
||||
<clr-dg-action-overflow *ngIf="hasProjectAdminRole">
|
||||
<button class="action-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||
</clr-dg-action-overflow>
|
||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{t.pullCommand}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="withNotary">
|
||||
<clr-icon shape="check" *ngIf="t.signed" style="color: #1D5100;"></clr-icon>
|
||||
<clr-icon shape="close" *ngIf="!t.signed" style="color: #C92100;"></clr-icon>
|
||||
</clr-dg-cell>
|
||||
|
@ -10,10 +10,14 @@ import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmati
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Tag } from '../tag';
|
||||
import { TagView } from '../tag-view';
|
||||
|
||||
import { AppConfigService } from '../../app-config.service';
|
||||
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { Member } from '../../project/member/member';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'tag-repository',
|
||||
@ -25,8 +29,11 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
projectId: number;
|
||||
repoName: string;
|
||||
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
tags: TagView[];
|
||||
registryUrl: string;
|
||||
withNotary: boolean;
|
||||
|
||||
private subscription: Subscription;
|
||||
|
||||
@ -35,7 +42,18 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
private messageService: MessageService,
|
||||
private deletionDialogService: ConfirmationDialogService,
|
||||
private repositoryService: RepositoryService,
|
||||
private appConfigService: AppConfigService) {
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService){
|
||||
|
||||
let currentUser = session.getCurrentUser();
|
||||
let projectMembers: Member[] = session.getProjectMembers();
|
||||
if(currentUser && projectMembers) {
|
||||
let currentMember = projectMembers.find(m=>m.user_id === currentUser.user_id);
|
||||
if(currentMember) {
|
||||
this.hasProjectAdminRole = (currentMember.role_name === 'projectAdmin');
|
||||
}
|
||||
}
|
||||
|
||||
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (message &&
|
||||
@ -52,6 +70,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
.subscribe(
|
||||
response => {
|
||||
this.retrieve();
|
||||
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_TAG_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete tag:' + tagName + ' under repo:' + this.repoName, AlertType.DANGER)
|
||||
@ -68,6 +87,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
this.repoName = this.route.snapshot.params['repo'];
|
||||
this.tags = [];
|
||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||
this.withNotary = this.appConfigService.getConfig().with_notary;
|
||||
this.retrieve();
|
||||
}
|
||||
|
||||
@ -79,25 +99,35 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||
|
||||
retrieve() {
|
||||
this.tags = [];
|
||||
if(this.withNotary) {
|
||||
this.repositoryService
|
||||
.listTagsWithVerifiedSignatures(this.repoName)
|
||||
.subscribe(
|
||||
items => {
|
||||
items.forEach(t => {
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.signed = t.signed;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
},
|
||||
items => this.listTags(items),
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
} else {
|
||||
this.repositoryService
|
||||
.listTags(this.repoName)
|
||||
.subscribe(
|
||||
items => this.listTags(items),
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
||||
}
|
||||
}
|
||||
|
||||
private listTags(tags: Tag[]): void {
|
||||
tags.forEach(t => {
|
||||
let tag = new TagView();
|
||||
tag.tag = t.tag;
|
||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||
tag.architecture = data['architecture'];
|
||||
tag.author = data['author'];
|
||||
tag.signed = t.signed;
|
||||
tag.created = data['created'];
|
||||
tag.dockerVersion = data['docker_version'];
|
||||
tag.pullCommand = 'docker pull ' + this.registryUrl + '/' + t.manifest.name + ':' + t.tag;
|
||||
tag.os = data['os'];
|
||||
this.tags.push(tag);
|
||||
});
|
||||
}
|
||||
|
||||
deleteTag(tag: TagView) {
|
||||
|
@ -183,6 +183,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||
.createPolicy(this.getPolicyByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'REPLICATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful created policy: ' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
@ -199,6 +200,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
||||
.subscribe(
|
||||
response=>{
|
||||
this.messageService.announceMessage(response, 'REPLICATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful created policy and target:' + response);
|
||||
this.createEditPolicyOpened = false;
|
||||
this.reload.emit(true);
|
||||
|
@ -50,7 +50,10 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
this.replicationService
|
||||
.enablePolicy(policy.id, policy.enabled)
|
||||
.subscribe(
|
||||
res => console.log('Successful toggled policy status'),
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'REPLICATION.TOGGLED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful toggled policy status')
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, "Failed to toggle policy status.", AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
@ -67,10 +70,11 @@ export class ListPolicyComponent implements OnDestroy {
|
||||
.deletePolicy(message.data)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.messageService.announceMessage(response, 'REPLICATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||
console.log('Successful delete policy with ID:' + message.data);
|
||||
this.reload.emit(true);
|
||||
},
|
||||
error => this.messageService.announceMessage(error.status, 'Failed to delete policy with ID:' + message.data, AlertType.DANGER)
|
||||
error => this.messageService.announceMessage(error.status, 'REPLICATION.DELETED_FAILED', AlertType.DANGER)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {
|
||||
CanActivate, Router,
|
||||
ActivatedRouteSnapshot,
|
||||
RouterStateSnapshot,
|
||||
CanActivateChild
|
||||
} from '@angular/router';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
import { ProjectService } from '../../project/project.service';
|
||||
import { CommonRoutes } from '../../shared/shared.const';
|
||||
|
||||
@Injectable()
|
||||
export class MemberGuard implements CanActivate, CanActivateChild {
|
||||
constructor(
|
||||
private sessionService: SessionService,
|
||||
private projectService: ProjectService,
|
||||
private router: Router) {}
|
||||
|
||||
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
let projectId: number = route.params['id'];
|
||||
return new Promise((resolve, reject) => {
|
||||
this.projectService.checkProjectMember(projectId)
|
||||
.subscribe(
|
||||
res=>{
|
||||
this.sessionService.setProjectMembers(res);
|
||||
return resolve(true)
|
||||
},
|
||||
error => {
|
||||
this.router.navigate([CommonRoutes.HARBOR_DEFAULT]);
|
||||
return resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean> | boolean {
|
||||
return this.canActivate(route, state);
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ import { Headers, Http, URLSearchParams } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { SessionUser } from './session-user';
|
||||
import { Member } from '../project/member/member';
|
||||
|
||||
import { SignInCredential } from './sign-in-credential';
|
||||
import { enLang } from '../shared/shared.const'
|
||||
|
||||
@ -27,6 +29,8 @@ const langMap = {
|
||||
export class SessionService {
|
||||
currentUser: SessionUser = null;
|
||||
|
||||
projectMembers: Member[];
|
||||
|
||||
private headers = new Headers({
|
||||
"Content-Type": 'application/json'
|
||||
});
|
||||
@ -143,4 +147,12 @@ export class SessionService {
|
||||
})
|
||||
.catch(error => this.handleError(error));
|
||||
}
|
||||
|
||||
setProjectMembers(projectMembers: Member[]): void {
|
||||
this.projectMembers = projectMembers;
|
||||
}
|
||||
|
||||
getProjectMembers(): Member[] {
|
||||
return this.projectMembers;
|
||||
}
|
||||
}
|
@ -24,7 +24,8 @@ export const enum ConfirmationTargets {
|
||||
REPOSITORY,
|
||||
TAG,
|
||||
CONFIG,
|
||||
CONFIG_ROUTE
|
||||
CONFIG_ROUTE,
|
||||
CONFIG_TAB
|
||||
};
|
||||
|
||||
export const enum ActionType {
|
||||
|
@ -32,6 +32,7 @@ import { StatisticsComponent } from './statictics/statistics.component';
|
||||
import { StatisticsPanelComponent } from './statictics/statistics-panel.component';
|
||||
import { SignInGuard } from './route/sign-in-guard-activate.service';
|
||||
import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service';
|
||||
import { MemberGuard } from './route/member-guard-activate.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -79,7 +80,8 @@ import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.
|
||||
SystemAdminGuard,
|
||||
AuthCheckGuard,
|
||||
SignInGuard,
|
||||
LeavingConfigRouteDeactivate
|
||||
LeavingConfigRouteDeactivate,
|
||||
MemberGuard
|
||||
]
|
||||
})
|
||||
export class SharedModule {
|
||||
|
@ -132,7 +132,10 @@
|
||||
"DELETION_TITLE": "Confirm project deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project {{param}}?",
|
||||
"FILTER_PLACEHOLDER": "Filter Projects",
|
||||
"REPLICATION_RULE": "Replication Rule"
|
||||
"REPLICATION_RULE": "Replication Rule",
|
||||
"CREATED_SUCCESS": "Created project successfully.",
|
||||
"DELETED_SUCCESS": "Deleted project successfully.",
|
||||
"TOGGLED_SUCCESS": "Toggled project successfully."
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "Repositories",
|
||||
@ -159,7 +162,10 @@
|
||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||
"FILTER_PLACEHOLDER": "Filter Members",
|
||||
"DELETION_TITLE": "Confirm project member deletion",
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?"
|
||||
"DELETION_SUMMARY": "Do you want to delete project member {{param}}?",
|
||||
"ADDED_SUCCESS": "Added member successfully.",
|
||||
"DELETED_SUCCESS": "Deleted member successfully.",
|
||||
"SWITCHED_SUCCESS": "Switched member role successfully."
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
@ -235,7 +241,12 @@
|
||||
"TOGGLE_ENABLE_TITLE": "Enable Policy",
|
||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "After enabling the replication policy, all repositories under the project will be replicated to the destination registry. Please confirm to continue.",
|
||||
"TOGGLE_DISABLE_TITLE": "Disable Policy",
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue."
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "After disabling the policy, all unfinished replication jobs of this policy will be stopped and canceled. Please confirm to continue.",
|
||||
"CREATED_SUCCESS": "Created policy successfully.",
|
||||
"UPDATED_SUCCESS": "Updated policy successfully.",
|
||||
"DELETED_SUCCESS": "Deleted policy successfully.",
|
||||
"DELETED_FAILED": "Deleted policy failed.",
|
||||
"TOGGLED_SUCCESS": "Toggled policy status successfully."
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "New Endpoint",
|
||||
@ -257,7 +268,11 @@
|
||||
"INVALID_NAME": "Invalid destination name.",
|
||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||
"CREATION_TIME": "Creation Time",
|
||||
"ITEMS": "item(s)"
|
||||
"ITEMS": "item(s)",
|
||||
"CREATED_SUCCESS": "Created destination successfully.",
|
||||
"UPDATED_SUCCESS": "Updated destination successfully.",
|
||||
"DELETED_SUCCESS": "Deleted destination successfully.",
|
||||
"DELETED_FAILED": "Deleted destination failed."
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "Copy ID",
|
||||
@ -286,7 +301,9 @@
|
||||
"SHOW_DETAILS": "Show Details",
|
||||
"REPOSITORIES": "Repositories",
|
||||
"ITEMS": "item(s)",
|
||||
"POP_REPOS": "Popular Repositories"
|
||||
"POP_REPOS": "Popular Repositories",
|
||||
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
||||
"DELETED_TAG_SUCCESS": "Deleted tag successfully."
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
|
||||
@ -310,7 +327,7 @@
|
||||
"EMAIL": "Email",
|
||||
"SYSTEM": "System Settings",
|
||||
"CONFIRM_TITLE": "Confirm to cancel",
|
||||
"CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to leave?",
|
||||
"CONFIRM_SUMMARY": "Some changes are not saved yet, do you really want to discard?",
|
||||
"SAVE_SUCCESS": "Configurations have been successfully saved",
|
||||
"MAIL_SERVER": "Email Server",
|
||||
"MAIL_SERVER_PORT": "Email Server Port",
|
||||
@ -386,7 +403,8 @@
|
||||
"IN_PROGRESS": "Search...",
|
||||
"BACK": "Back"
|
||||
},
|
||||
"UNKNOWN_ERROR": "Some unknown errors HAVE occurred. Please try again later",
|
||||
"UNKNOWN_ERROR": "Unknown errors have occurred. Please try again later",
|
||||
"UNAUTHORIZED_ERROR": "Your session is invalid or has expired. You need to sign in to continue the operation",
|
||||
"FORBIDDEN_ERROR": "You are not allowed to perform this operation"
|
||||
"FORBIDDEN_ERROR": "You are not allowed to perform this operation",
|
||||
"GENERAL_ERROR": "Errors have occurred when performing service call: {{param}}"
|
||||
}
|
@ -132,7 +132,10 @@
|
||||
"DELETION_TITLE": "删除项目确认",
|
||||
"DELETION_SUMMARY": "你确认删除项目 {{param}}?",
|
||||
"FILTER_PLACEHOLDER": "过滤项目",
|
||||
"REPLICATION_RULE": "复制策略"
|
||||
"REPLICATION_RULE": "复制策略",
|
||||
"CREATED_SUCCESS": "创建项目成功。",
|
||||
"DELETED_SUCCESS": "删除项目成功。",
|
||||
"TOGGLED_SUCCESS": "切换状态成功。"
|
||||
},
|
||||
"PROJECT_DETAIL": {
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
@ -159,7 +162,10 @@
|
||||
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
||||
"FILTER_PLACEHOLDER": "过滤成员",
|
||||
"DELETION_TITLE": "删除项目成员确认",
|
||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?"
|
||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
|
||||
"ADDED_SUCCESS": "新增成员成功。",
|
||||
"DELETED_SUCCESS": "删除成员成功",
|
||||
"SWITCHED_SUCCESS": "切换角色成功"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
@ -235,7 +241,12 @@
|
||||
"TOGGLE_ENABLE_TITLE": "启用策略",
|
||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。",
|
||||
"TOGGLE_DISABLE_TITLE": "停用策略",
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。"
|
||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。",
|
||||
"CREATED_SUCCESS": "创建复制策略成功。",
|
||||
"UPDATED_SUCCESS": "更新复制策略成功。",
|
||||
"DELETED_SUCCESS": "删除复制策略成功。",
|
||||
"DELETED_FAILED": "删除复制策略失败。",
|
||||
"TOGGLED_SUCCESS": "切换复制策略状态成功。"
|
||||
},
|
||||
"DESTINATION": {
|
||||
"NEW_ENDPOINT": "新建目标",
|
||||
@ -257,7 +268,11 @@
|
||||
"INVALID_NAME": "无效的目标名称。",
|
||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||
"CREATION_TIME": "创建时间",
|
||||
"ITEMS": "条记录"
|
||||
"ITEMS": "条记录",
|
||||
"CREATED_SUCCESS": "创建目标成功。",
|
||||
"UPDATED_SUCCESS": "更新目标成功。",
|
||||
"DELETED_SUCCESS": "删除目标成功。",
|
||||
"DELETED_FAILED": "删除目标失败。"
|
||||
},
|
||||
"REPOSITORY": {
|
||||
"COPY_ID": "复制ID",
|
||||
@ -286,7 +301,9 @@
|
||||
"SHOW_DETAILS": "显示详细",
|
||||
"REPOSITORIES": "镜像仓库",
|
||||
"ITEMS": "条记录",
|
||||
"POP_REPOS": "受欢迎的镜像库"
|
||||
"POP_REPOS": "受欢迎的镜像库",
|
||||
"DELETED_REPO_SUCCESS": "删除镜像仓库成功。",
|
||||
"DELETED_TAG_SUCCESS": "删除镜像标签成功。"
|
||||
},
|
||||
"ALERT": {
|
||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?"
|
||||
@ -388,5 +405,6 @@
|
||||
},
|
||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试",
|
||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续",
|
||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限"
|
||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限",
|
||||
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}"
|
||||
}
|
Loading…
Reference in New Issue
Block a user