mirror of
https://github.com/goharbor/harbor
synced 2025-04-12 23:24:59 +00: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
|
//Handle the global search event and then let the result page to trigger api
|
||||||
doSearch(event: string): void {
|
doSearch(event: string): void {
|
||||||
if (event === "") {
|
if (event === "") {
|
||||||
if (!this.isSearchResultsOpened) {
|
//Do nothing
|
||||||
//Will not open search result panel if term is empty
|
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
//If opened, then close the search result panel
|
|
||||||
this.isSearchResultsOpened = false;
|
|
||||||
this.searchResultComponet.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//Once this method is called
|
//Once this method is called
|
||||||
//the search results page must be opened
|
//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,16 +1,24 @@
|
||||||
<div class="config-container">
|
<div class="config-container">
|
||||||
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
<h2 style="display: inline-block;" class="custom-h2">{{'CONFIG.TITLE' | translate }}</h2>
|
||||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||||
<clr-tabs (clrTabsCurrentTabLinkChanged)="tabLinkChanged($event)">
|
<ul id="configTabs" class="nav" role="tablist">
|
||||||
<clr-tab-link [clrTabLinkId]="'config-auth'" [clrTabLinkActive]="true">{{'CONFIG.AUTH' | translate }}</clr-tab-link>
|
<li role="presentation" class="nav-item">
|
||||||
<clr-tab-link [clrTabLinkId]="'config-replication'">{{'CONFIG.REPLICATION' | translate }}</clr-tab-link>
|
<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>
|
||||||
<clr-tab-link [clrTabLinkId]="'config-email'">{{'CONFIG.EMAIL' | translate }}</clr-tab-link>
|
</li>
|
||||||
<clr-tab-link [clrTabLinkId]="'config-system'">{{'CONFIG.SYSTEM' | translate }}</clr-tab-link>
|
<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>
|
||||||
<clr-tab-content [clrTabContentId]="'authentication'" [clrTabContentActive]="true">
|
</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>
|
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||||
</clr-tab-content>
|
</section>
|
||||||
<clr-tab-content [clrTabContentId]="'replication'">
|
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
|
||||||
<form #repoConfigFrom="ngForm" class="form">
|
<form #repoConfigFrom="ngForm" class="form">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -24,11 +32,11 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</clr-tab-content>
|
</section>
|
||||||
<clr-tab-content [clrTabContentId]="'email'">
|
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
|
||||||
<config-email [mailConfig]="allConfig"></config-email>
|
<config-email [mailConfig]="allConfig"></config-email>
|
||||||
</clr-tab-content>
|
</section>
|
||||||
<clr-tab-content [clrTabContentId]="'system_settings'">
|
<section id="system_settings" role="tabpanel" aria-labelledby="config-system" [hidden]='!isCurrentTabContent("system_settings")'>
|
||||||
<form #systemConfigFrom="ngForm" class="form">
|
<form #systemConfigFrom="ngForm" class="form">
|
||||||
<section class="form-block">
|
<section class="form-block">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -50,8 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</form>
|
</form>
|
||||||
</clr-tab-content>
|
</section>
|
||||||
</clr-tabs>
|
|
||||||
<div>
|
<div>
|
||||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
<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)="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 { ConfigurationEmailComponent } from './email/config-email.component';
|
||||||
|
|
||||||
import { AppConfigService } from '../app-config.service';
|
import { AppConfigService } from '../app-config.service';
|
||||||
|
import { SessionService } from '../shared/session.service';
|
||||||
|
|
||||||
const fakePass = "fakepassword";
|
const fakePass = "fakepassword";
|
||||||
|
const TabLinkContentMap = {
|
||||||
|
"config-auth": "authentication",
|
||||||
|
"config-replication": "replication",
|
||||||
|
"config-email": "email",
|
||||||
|
"config-system": "system_settings"
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'config',
|
selector: 'config',
|
||||||
|
@ -27,7 +34,7 @@ const fakePass = "fakepassword";
|
||||||
export class ConfigurationComponent implements OnInit, OnDestroy {
|
export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||||
private onGoing: boolean = false;
|
private onGoing: boolean = false;
|
||||||
allConfig: Configuration = new Configuration();
|
allConfig: Configuration = new Configuration();
|
||||||
private currentTabId: string = "";
|
private currentTabId: string = "config-auth";//default tab
|
||||||
private originalCopy: Configuration;
|
private originalCopy: Configuration;
|
||||||
private confirmSub: Subscription;
|
private confirmSub: Subscription;
|
||||||
private testingOnGoing: boolean = false;
|
private testingOnGoing: boolean = false;
|
||||||
|
@ -41,17 +48,76 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||||
private msgService: MessageService,
|
private msgService: MessageService,
|
||||||
private configService: ConfigurationService,
|
private configService: ConfigurationService,
|
||||||
private confirmService: ConfirmationDialogService,
|
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 {
|
ngOnInit(): void {
|
||||||
//First load
|
//First load
|
||||||
|
//Double confirm the current use has admin role
|
||||||
|
let currentUser = this.session.getCurrentUser();
|
||||||
|
if (currentUser && currentUser.has_admin_role > 0) {
|
||||||
this.retrieveConfig();
|
this.retrieveConfig();
|
||||||
|
}
|
||||||
|
|
||||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||||
if (confirmation &&
|
if (confirmation &&
|
||||||
confirmation.state === ConfirmationState.CONFIRMED &&
|
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||||
confirmation.source === ConfirmationTargets.CONFIG) {
|
if (confirmation.source === ConfirmationTargets.CONFIG) {
|
||||||
this.reset(confirmation.data);
|
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();
|
return this.authConfig && this.authConfig.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
public tabLinkChanged(tabLink: any) {
|
public tabLinkClick(tabLink: string) {
|
||||||
this.currentTabId = tabLink.id;
|
//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 {
|
public cancel(): void {
|
||||||
let changes = this.getChanges();
|
let changes = this.getChanges();
|
||||||
if (!this.isEmpty(changes)) {
|
if (!this.isEmpty(changes)) {
|
||||||
let msg = new ConfirmationMessage(
|
this.confirmUnsavedChanges(changes);
|
||||||
"CONFIG.CONFIRM_TITLE",
|
|
||||||
"CONFIG.CONFIRM_SUMMARY",
|
|
||||||
"",
|
|
||||||
changes,
|
|
||||||
ConfirmationTargets.CONFIG
|
|
||||||
);
|
|
||||||
this.confirmService.openComfirmDialog(msg);
|
|
||||||
} else {
|
} else {
|
||||||
//Inprop situation, should not come here
|
//Inprop situation, should not come here
|
||||||
console.error("Nothing changed");
|
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 {
|
private retrieveConfig(): void {
|
||||||
this.onGoing = true;
|
this.onGoing = true;
|
||||||
this.configService.getConfiguration()
|
this.configService.getConfiguration()
|
||||||
|
|
|
@ -18,6 +18,7 @@ export class MessageComponent implements OnInit {
|
||||||
globalMessage: Message = new Message();
|
globalMessage: Message = new Message();
|
||||||
globalMessageOpened: boolean;
|
globalMessageOpened: boolean;
|
||||||
messageText: string = "";
|
messageText: string = "";
|
||||||
|
private timer: any = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
|
@ -48,7 +49,7 @@ export class MessageComponent implements OnInit {
|
||||||
|
|
||||||
// Make the message alert bar dismiss after several intervals.
|
// Make the message alert bar dismiss after several intervals.
|
||||||
//Only for this case
|
//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
|
//Translate or refactor the message shown to user
|
||||||
translateMessage(msg: Message): void {
|
translateMessage(msg: Message): void {
|
||||||
if (!msg) {
|
let key = "UNKNOWN_ERROR", param = "";
|
||||||
return;
|
if (msg && msg.message) {
|
||||||
}
|
key = (typeof msg.message === "string" ? msg.message.trim() : msg.message);
|
||||||
|
|
||||||
let key = "";
|
|
||||||
if (!msg.message) {
|
|
||||||
key = "UNKNOWN_ERROR";
|
|
||||||
} else {
|
|
||||||
key = typeof msg.message === "string" ? msg.message.trim() : msg.message;
|
|
||||||
if (key === "") {
|
if (key === "") {
|
||||||
key = "UNKNOWN_ERROR";
|
key = "UNKNOWN_ERROR";
|
||||||
}
|
}
|
||||||
|
@ -73,13 +68,11 @@ export class MessageComponent implements OnInit {
|
||||||
//Override key for HTTP 401 and 403
|
//Override key for HTTP 401 and 403
|
||||||
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
if (this.globalMessage.statusCode === httpStatusCode.Unauthorized) {
|
||||||
key = "UNAUTHORIZED_ERROR";
|
key = "UNAUTHORIZED_ERROR";
|
||||||
}
|
} else if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
||||||
|
|
||||||
if (this.globalMessage.statusCode === httpStatusCode.Forbidden) {
|
|
||||||
key = "FORBIDDEN_ERROR";
|
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 {
|
public get needAuth(): boolean {
|
||||||
|
@ -98,6 +91,9 @@ export class MessageComponent implements OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
onClose() {
|
onClose() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
}
|
||||||
this.globalMessageOpened = false;
|
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 { SignInGuard } from './shared/route/sign-in-guard-activate.service';
|
||||||
import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service';
|
import { LeavingConfigRouteDeactivate } from './shared/route/leaving-config-deactivate.service';
|
||||||
|
|
||||||
|
import { MemberGuard } from './shared/route/member-guard-activate.service';
|
||||||
|
|
||||||
const harborRoutes: Routes = [
|
const harborRoutes: Routes = [
|
||||||
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
{ path: '', redirectTo: 'harbor', pathMatch: 'full' },
|
||||||
{ path: 'password-reset', component: ResetPasswordComponent },
|
{ path: 'password-reset', component: ResetPasswordComponent },
|
||||||
|
@ -79,6 +81,7 @@ const harborRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: 'projects/:id',
|
path: 'projects/:id',
|
||||||
component: ProjectDetailComponent,
|
component: ProjectDetailComponent,
|
||||||
|
canActivate: [MemberGuard],
|
||||||
resolve: {
|
resolve: {
|
||||||
projectResolver: ProjectRoutingResolver
|
projectResolver: ProjectRoutingResolver
|
||||||
},
|
},
|
||||||
|
@ -89,7 +92,8 @@ const harborRoutes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'replication',
|
path: 'replication',
|
||||||
component: ReplicationComponent
|
component: ReplicationComponent,
|
||||||
|
canActivate: [SystemAdminGuard]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'member',
|
path: 'member',
|
||||||
|
|
|
@ -44,5 +44,4 @@ export class AuditLogService extends BaseService {
|
||||||
.map(response => response.json() as AuditLog[])
|
.map(response => response.json() as AuditLog[])
|
||||||
.catch(error => this.handleError(error));
|
.catch(error => this.handleError(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,25 +2,18 @@
|
||||||
margin-top: 0px !important;
|
margin-top: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-log {
|
|
||||||
float: right;
|
|
||||||
margin-right: 24px;
|
|
||||||
position: relative;
|
|
||||||
top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-head-pos {
|
.action-head-pos {
|
||||||
position: relative;
|
padding-right: 18px;
|
||||||
top: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.refresh-btn {
|
.refresh-btn {
|
||||||
position: absolute;
|
|
||||||
right: -4px;
|
|
||||||
top: 8px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
color: #00bfff;
|
||||||
|
}
|
||||||
|
|
||||||
.custom-lines-button {
|
.custom-lines-button {
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
min-width: 25px !important;
|
min-width: 25px !important;
|
||||||
|
@ -30,3 +23,20 @@
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
text-decoration: underline;
|
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,21 +1,25 @@
|
||||||
<div>
|
<div>
|
||||||
<h2 class="h2-log-override">{{'SIDE_NAV.LOGS' | translate}}</h2>
|
<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="action-head-pos">
|
||||||
<span>
|
<div class="select log-select">
|
||||||
<label>{{'RECENT_LOG.SUB_TITLE' | translate}} </label>
|
<select id="log_display_num" (change)="handleOnchange($event)">
|
||||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 10" (click)="setLines(10)">10</button>
|
<option value="10">{{'RECENT_LOG.SUB_TITLE' | translate}} 10 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||||
<label> | </label>
|
<option value="25">{{'RECENT_LOG.SUB_TITLE' | translate}} 25 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 25" (click)="setLines(25)">25</button>
|
<option value="50">{{'RECENT_LOG.SUB_TITLE' | translate}} 50 {{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</option>
|
||||||
<label> | </label>
|
</select>
|
||||||
<button type="submit" class="btn btn-link custom-lines-button" [class.lines-button-toggole]="lines === 50" (click)="setLines(50)">50</button>
|
</div>
|
||||||
<label>{{'RECENT_LOG.SUB_TITLE_SUFIX' | translate}}</label>
|
<div class="item-divider"></div>
|
||||||
</span>
|
<grid-filter filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
||||||
<grid-filter class="filter-log" filterPlaceholder='{{"AUDIT_LOG.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)"></grid-filter>
|
<span (click)="refresh()" class="refresh-btn">
|
||||||
<span class="refresh-btn" (click)="refresh()">
|
|
||||||
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
|
||||||
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'AUDIT_LOG.USERNAME' | translate}}</clr-dg-column>
|
||||||
|
|
|
@ -34,18 +34,23 @@ export class RecentLogComponent implements OnInit {
|
||||||
this.retrieveLogs();
|
this.retrieveLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get inProgress(): boolean {
|
private handleOnchange($event: any) {
|
||||||
return this.onGoing;
|
if (event && event.target && event.srcElement["value"]) {
|
||||||
}
|
this.lines = event.srcElement["value"];
|
||||||
|
|
||||||
public setLines(lines: number): void {
|
|
||||||
this.lines = lines;
|
|
||||||
if (this.lines < 10) {
|
if (this.lines < 10) {
|
||||||
this.lines = 10;
|
this.lines = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.retrieveLogs();
|
this.retrieveLogs();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get logNumber(): number {
|
||||||
|
return this.recentLogs?this.recentLogs.length:0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get inProgress(): boolean {
|
||||||
|
return this.onGoing;
|
||||||
|
}
|
||||||
|
|
||||||
public doFilter(terms: string): void {
|
public doFilter(terms: string): void {
|
||||||
if (terms.trim() === "") {
|
if (terms.trim() === "") {
|
||||||
|
@ -60,7 +65,7 @@ export class RecentLogComponent implements OnInit {
|
||||||
this.retrieveLogs();
|
this.retrieveLogs();
|
||||||
}
|
}
|
||||||
|
|
||||||
public formatDateTime(dateTime: string){
|
public formatDateTime(dateTime: string) {
|
||||||
let dt: Date = new Date(dateTime);
|
let dt: Date = new Date(dateTime);
|
||||||
return dt.toLocaleString();
|
return dt.toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { InlineAlertComponent } from '../../shared/inline-alert/inline-alert.com
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'create-project',
|
selector: 'create-project',
|
||||||
templateUrl: 'create-project.component.html',
|
templateUrl: 'create-project.component.html',
|
||||||
|
@ -50,6 +49,7 @@ export class CreateProjectComponent implements AfterViewChecked {
|
||||||
.subscribe(
|
.subscribe(
|
||||||
status=>{
|
status=>{
|
||||||
this.create.emit(true);
|
this.create.emit(true);
|
||||||
|
this.messageService.announceMessage(status, 'PROJECT.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||||
this.createProjectOpened = false;
|
this.createProjectOpened = false;
|
||||||
},
|
},
|
||||||
error=>{
|
error=>{
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'PROJECT.CREATION_TIME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'PROJECT.DESCRIPTION' | 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-row *ngFor="let p of projects" [clrDgItem]="p">
|
||||||
<clr-dg-action-overflow *ngIf="listFullMode">
|
<clr-dg-action-overflow [hidden]="!listFullMode || p.current_user_role_id !== 1">
|
||||||
<button class="action-item" (click)="newReplicationRule(p)">{{'PROJECT.REPLICATION_RULE' | translate}}</button>
|
<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)="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>
|
<button class="action-item" (click)="deleteProject(p)">{{'PROJECT.DELETE' | translate}}</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
|
|
|
@ -43,6 +43,11 @@ export class ListProjectComponent implements OnInit {
|
||||||
return this.mode === ListMode.FULL && this.session.getCurrentUser() != null;
|
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 {
|
goToLink(proId: number): void {
|
||||||
this.searchTrigger.closeSearch(false);
|
this.searchTrigger.closeSearch(false);
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,15 @@
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
<label class="col-md-4 form-group-label-override">{{'MEMBER.ROLE' | translate}}</label>
|
||||||
<div class="radio">
|
<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>
|
<label for="checkrads_project_admin">{{'MEMBER.PROJECT_ADMIN' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio">
|
<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>
|
<label for="checkrads_developer">{{'MEMBER.DEVELOPER' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="radio">
|
<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>
|
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,6 +36,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline" (click)="onCancel()">{{'BUTTON.CANCEL' | translate}}</button>
|
<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>
|
</div>
|
||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
|
@ -51,6 +51,7 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
.addMember(this.projectId, this.member.username, +this.member.role_id)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
|
this.messageService.announceMessage(response, 'MEMBER.ADDED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Added member successfully.');
|
console.log('Added member successfully.');
|
||||||
this.added.emit(true);
|
this.added.emit(true);
|
||||||
this.addMemberOpened = false;
|
this.addMemberOpened = false;
|
||||||
|
@ -112,9 +113,11 @@ export class AddMemberComponent implements AfterViewChecked {
|
||||||
}
|
}
|
||||||
|
|
||||||
openAddMemberModal(): void {
|
openAddMemberModal(): void {
|
||||||
|
this.memberForm.reset();
|
||||||
this.member = new Member();
|
this.member = new Member();
|
||||||
this.addMemberOpened = true;
|
this.addMemberOpened = true;
|
||||||
this.hasChanged = false;
|
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="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="flex-xs-middle option-left">
|
<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>
|
<add-member [projectId]="projectId" (added)="addedMember($event)"></add-member>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-xs-middle option-right">
|
<div class="flex-xs-middle option-right">
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-row *ngFor="let u of members">
|
<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, 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, 2)">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||||
<button class="action-item" (click)="changeRole(u.user_id, 3)">{{'MEMBER.GUEST' | 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)
|
@ViewChild(AddMemberComponent)
|
||||||
addMemberComponent: AddMemberComponent;
|
addMemberComponent: AddMemberComponent;
|
||||||
|
|
||||||
|
hasProjectAdminRole: boolean;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute, private router: Router,
|
constructor(private route: ActivatedRoute, private router: Router,
|
||||||
private memberService: MemberService, private messageService: MessageService,
|
private memberService: MemberService, private messageService: MessageService,
|
||||||
private deletionDialogService: ConfirmationDialogService,
|
private deletionDialogService: ConfirmationDialogService,
|
||||||
session: SessionService) {
|
session: SessionService) {
|
||||||
//Get current user from registered resolver.
|
//Get current user from registered resolver.
|
||||||
this.currentUser = session.getCurrentUser();
|
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 => {
|
this.delSub = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||||
if (message &&
|
if (message &&
|
||||||
message.state === ConfirmationState.CONFIRMED &&
|
message.state === ConfirmationState.CONFIRMED &&
|
||||||
|
@ -54,7 +64,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||||
.deleteMember(this.projectId, message.data)
|
.deleteMember(this.projectId, message.data)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
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, '');
|
this.retrieve(this.projectId, '');
|
||||||
},
|
},
|
||||||
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
|
error => this.messageService.announceMessage(error.status, 'Failed to change role with user ' + message.data, AlertType.DANGER)
|
||||||
|
@ -102,6 +113,7 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||||
.changeMemberRole(this.projectId, userId, roleId)
|
.changeMemberRole(this.projectId, userId, roleId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
|
this.messageService.announceMessage(response, 'MEMBER.SWITCHED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
console.log('Successful change role with user ' + userId + ' to roleId ' + roleId);
|
||||||
this.retrieve(this.projectId, '');
|
this.retrieve(this.projectId, '');
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
<h2 class="header-title">{{'PROJECT.PROJECTS' | translate}}</h2>
|
||||||
<div class="row flex-items-xs-between">
|
<div class="row flex-items-xs-between">
|
||||||
<div class="option-left">
|
<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>
|
<create-project (create)="createProject($event)"></create-project>
|
||||||
</div>
|
</div>
|
||||||
<div class="option-right">
|
<div class="option-right">
|
||||||
|
|
|
@ -23,6 +23,10 @@ import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
import { State } from 'clarity-angular';
|
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' };
|
const types: {} = { 0: 'PROJECT.MY_PROJECTS', 1: 'PROJECT.PUBLIC_PROJECTS' };
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -59,6 +63,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||||
constructor(
|
constructor(
|
||||||
private projectService: ProjectService,
|
private projectService: ProjectService,
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
|
private appConfigService: AppConfigService,
|
||||||
|
private sessionService: SessionService,
|
||||||
private deletionDialogService: ConfirmationDialogService) {
|
private deletionDialogService: ConfirmationDialogService) {
|
||||||
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
this.subscription = deletionDialogService.confirmationConfirm$.subscribe(message => {
|
||||||
if (message &&
|
if (message &&
|
||||||
|
@ -69,6 +75,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||||
.deleteProject(projectId)
|
.deleteProject(projectId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
|
this.messageService.announceMessage(response, 'PROJECT.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful delete project with ID:' + projectId);
|
console.log('Successful delete project with ID:' + projectId);
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
},
|
},
|
||||||
|
@ -76,11 +83,13 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.projectName = '';
|
this.projectName = '';
|
||||||
this.isPublic = 0;
|
this.isPublic = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
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 {
|
retrieve(state?: State): void {
|
||||||
if (state) {
|
if (state) {
|
||||||
this.page = state.page.to + 1;
|
this.page = state.page.to + 1;
|
||||||
|
@ -135,7 +157,10 @@ export class ProjectComponent implements OnInit, OnDestroy {
|
||||||
this.projectService
|
this.projectService
|
||||||
.toggleProjectPublic(p.project_id, p.public)
|
.toggleProjectPublic(p.project_id, p.public)
|
||||||
.subscribe(
|
.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)
|
error => this.messageService.announceMessage(error.status, error, AlertType.WARNING)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,4 +70,11 @@ export class ProjectService {
|
||||||
.catch(error=>Observable.throw(error));
|
.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)
|
.createTarget(this.target)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
|
this.messageService.announceMessage(response, 'DESTINATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful added target.');
|
console.log('Successful added target.');
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
|
@ -129,7 +130,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||||
.get(errorMessageKey)
|
.get(errorMessageKey)
|
||||||
.subscribe(res=>{
|
.subscribe(res=>{
|
||||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
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)
|
.updateTarget(this.target)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
|
this.messageService.announceMessage(response, 'DESTINATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful updated target.');
|
console.log('Successful updated target.');
|
||||||
this.createEditDestinationOpened = false;
|
this.createEditDestinationOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
|
@ -158,7 +160,7 @@ export class CreateEditDestinationComponent implements AfterViewChecked {
|
||||||
this.translateService
|
this.translateService
|
||||||
.get(errorMessageKey)
|
.get(errorMessageKey)
|
||||||
.subscribe(res=>{
|
.subscribe(res=>{
|
||||||
this.inlineAlert.showInlineError(errorMessageKey);
|
this.inlineAlert.showInlineError(res);
|
||||||
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
this.messageService.announceMessage(error.status, errorMessageKey, AlertType.DANGER);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,14 +43,15 @@ export class DestinationComponent implements OnInit {
|
||||||
.deleteTarget(targetId)
|
.deleteTarget(targetId)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
|
this.messageService.announceMessage(response, 'DESTINATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful deleted target with ID:' + targetId);
|
console.log('Successful deleted target with ID:' + targetId);
|
||||||
this.reload();
|
this.reload();
|
||||||
},
|
},
|
||||||
error => this.messageService
|
error => {
|
||||||
.announceMessage(error.status,
|
this.messageService
|
||||||
'Failed to delete target with ID:' + targetId + ', error:' + error,
|
.announceMessage(error.status,'DESTINATION.DELETED_FAILED', AlertType.DANGER);
|
||||||
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.TAGS_COUNT' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.PULL_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-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_ID' | translate}}</button>
|
||||||
<button class="action-item">{{'REPOSITORY.COPY_PARENT_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>
|
<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 { SessionService } from '../../shared/session.service';
|
||||||
import { ListMode } from '../../shared/shared.const';
|
import { ListMode } from '../../shared/shared.const';
|
||||||
|
|
||||||
|
import { SessionUser } from '../../shared/session-user';
|
||||||
|
import { Member } from '../../project/member/member';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'list-repository',
|
selector: 'list-repository',
|
||||||
templateUrl: 'list-repository.component.html'
|
templateUrl: 'list-repository.component.html'
|
||||||
|
@ -25,10 +28,22 @@ export class ListRepositoryComponent {
|
||||||
|
|
||||||
pageOffset: number = 1;
|
pageOffset: number = 1;
|
||||||
|
|
||||||
|
hasProjectAdminRole: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private searchTrigger: SearchTriggerService,
|
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) {
|
deleteRepo(repoName: string) {
|
||||||
this.delete.emit(repoName);
|
this.delete.emit(repoName);
|
||||||
|
|
|
@ -60,6 +60,7 @@ export class RepositoryComponent implements OnInit {
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.refresh();
|
this.refresh();
|
||||||
|
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_REPO_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful deleted repo:' + repoName);
|
console.log('Successful deleted repo:' + repoName);
|
||||||
},
|
},
|
||||||
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
error => this.messageService.announceMessage(error.status, 'Failed to delete repo:' + repoName, AlertType.DANGER)
|
||||||
|
|
|
@ -3,19 +3,19 @@
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.TAG' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.PULL_COMMAND' | 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.AUTHOR' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.CREATED' | 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.DOCKER_VERSION' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
<clr-dg-column>{{'REPOSITORY.ARCHITECTURE' | translate}}</clr-dg-column>
|
||||||
<clr-dg-column>{{'REPOSITORY.OS' | 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-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>
|
<button class="action-item" (click)="deleteTag(t)">{{'REPOSITORY.DELETE' | translate}}</button>
|
||||||
</clr-dg-action-overflow>
|
</clr-dg-action-overflow>
|
||||||
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
<clr-dg-cell>{{t.tag}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{t.pullCommand}}</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="check" *ngIf="t.signed" style="color: #1D5100;"></clr-icon>
|
||||||
<clr-icon shape="close" *ngIf="!t.signed" style="color: #C92100;"></clr-icon>
|
<clr-icon shape="close" *ngIf="!t.signed" style="color: #C92100;"></clr-icon>
|
||||||
</clr-dg-cell>
|
</clr-dg-cell>
|
||||||
|
|
|
@ -10,10 +10,14 @@ import { ConfirmationMessage } from '../../shared/confirmation-dialog/confirmati
|
||||||
|
|
||||||
import { Subscription } from 'rxjs/Subscription';
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
|
import { Tag } from '../tag';
|
||||||
import { TagView } from '../tag-view';
|
import { TagView } from '../tag-view';
|
||||||
|
|
||||||
import { AppConfigService } from '../../app-config.service';
|
import { AppConfigService } from '../../app-config.service';
|
||||||
|
|
||||||
|
import { SessionService } from '../../shared/session.service';
|
||||||
|
import { Member } from '../../project/member/member';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
moduleId: module.id,
|
moduleId: module.id,
|
||||||
selector: 'tag-repository',
|
selector: 'tag-repository',
|
||||||
|
@ -25,8 +29,11 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
projectId: number;
|
projectId: number;
|
||||||
repoName: string;
|
repoName: string;
|
||||||
|
|
||||||
|
hasProjectAdminRole: boolean;
|
||||||
|
|
||||||
tags: TagView[];
|
tags: TagView[];
|
||||||
registryUrl: string;
|
registryUrl: string;
|
||||||
|
withNotary: boolean;
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
@ -35,7 +42,18 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
private messageService: MessageService,
|
private messageService: MessageService,
|
||||||
private deletionDialogService: ConfirmationDialogService,
|
private deletionDialogService: ConfirmationDialogService,
|
||||||
private repositoryService: RepositoryService,
|
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(
|
this.subscription = this.deletionDialogService.confirmationConfirm$.subscribe(
|
||||||
message => {
|
message => {
|
||||||
if (message &&
|
if (message &&
|
||||||
|
@ -52,6 +70,7 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
|
this.messageService.announceMessage(response, 'REPOSITORY.DELETED_TAG_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Deleted repo:' + this.repoName + ' with tag:' + tagName);
|
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)
|
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.repoName = this.route.snapshot.params['repo'];
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
this.registryUrl = this.appConfigService.getConfig().registry_url;
|
||||||
|
this.withNotary = this.appConfigService.getConfig().with_notary;
|
||||||
this.retrieve();
|
this.retrieve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,11 +99,23 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
retrieve() {
|
retrieve() {
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
|
if(this.withNotary) {
|
||||||
this.repositoryService
|
this.repositoryService
|
||||||
.listTagsWithVerifiedSignatures(this.repoName)
|
.listTagsWithVerifiedSignatures(this.repoName)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
items => {
|
items => this.listTags(items),
|
||||||
items.forEach(t => {
|
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();
|
let tag = new TagView();
|
||||||
tag.tag = t.tag;
|
tag.tag = t.tag;
|
||||||
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
let data = JSON.parse(t.manifest.history[0].v1Compatibility);
|
||||||
|
@ -96,8 +128,6 @@ export class TagRepositoryComponent implements OnInit, OnDestroy {
|
||||||
tag.os = data['os'];
|
tag.os = data['os'];
|
||||||
this.tags.push(tag);
|
this.tags.push(tag);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
error => this.messageService.announceMessage(error.status, 'Failed to list tags with repo:' + this.repoName, AlertType.DANGER));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteTag(tag: TagView) {
|
deleteTag(tag: TagView) {
|
||||||
|
|
|
@ -183,6 +183,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
.createPolicy(this.getPolicyByForm())
|
.createPolicy(this.getPolicyByForm())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
|
this.messageService.announceMessage(response, 'REPLICATION.CREATED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful created policy: ' + response);
|
console.log('Successful created policy: ' + response);
|
||||||
this.createEditPolicyOpened = false;
|
this.createEditPolicyOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
|
@ -199,6 +200,7 @@ export class CreateEditPolicyComponent implements OnInit, AfterViewChecked {
|
||||||
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
.createOrUpdatePolicyWithNewTarget(this.getPolicyByForm(), this.getTargetByForm())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response=>{
|
response=>{
|
||||||
|
this.messageService.announceMessage(response, 'REPLICATION.UPDATED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful created policy and target:' + response);
|
console.log('Successful created policy and target:' + response);
|
||||||
this.createEditPolicyOpened = false;
|
this.createEditPolicyOpened = false;
|
||||||
this.reload.emit(true);
|
this.reload.emit(true);
|
||||||
|
|
|
@ -50,7 +50,10 @@ export class ListPolicyComponent implements OnDestroy {
|
||||||
this.replicationService
|
this.replicationService
|
||||||
.enablePolicy(policy.id, policy.enabled)
|
.enablePolicy(policy.id, policy.enabled)
|
||||||
.subscribe(
|
.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)
|
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)
|
.deletePolicy(message.data)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
response => {
|
response => {
|
||||||
|
this.messageService.announceMessage(response, 'REPLICATION.DELETED_SUCCESS', AlertType.SUCCESS);
|
||||||
console.log('Successful delete policy with ID:' + message.data);
|
console.log('Successful delete policy with ID:' + message.data);
|
||||||
this.reload.emit(true);
|
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 'rxjs/add/operator/toPromise';
|
||||||
|
|
||||||
import { SessionUser } from './session-user';
|
import { SessionUser } from './session-user';
|
||||||
|
import { Member } from '../project/member/member';
|
||||||
|
|
||||||
import { SignInCredential } from './sign-in-credential';
|
import { SignInCredential } from './sign-in-credential';
|
||||||
import { enLang } from '../shared/shared.const'
|
import { enLang } from '../shared/shared.const'
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ const langMap = {
|
||||||
export class SessionService {
|
export class SessionService {
|
||||||
currentUser: SessionUser = null;
|
currentUser: SessionUser = null;
|
||||||
|
|
||||||
|
projectMembers: Member[];
|
||||||
|
|
||||||
private headers = new Headers({
|
private headers = new Headers({
|
||||||
"Content-Type": 'application/json'
|
"Content-Type": 'application/json'
|
||||||
});
|
});
|
||||||
|
@ -143,4 +147,12 @@ export class SessionService {
|
||||||
})
|
})
|
||||||
.catch(error => this.handleError(error));
|
.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,
|
REPOSITORY,
|
||||||
TAG,
|
TAG,
|
||||||
CONFIG,
|
CONFIG,
|
||||||
CONFIG_ROUTE
|
CONFIG_ROUTE,
|
||||||
|
CONFIG_TAB
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enum ActionType {
|
export const enum ActionType {
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { StatisticsComponent } from './statictics/statistics.component';
|
||||||
import { StatisticsPanelComponent } from './statictics/statistics-panel.component';
|
import { StatisticsPanelComponent } from './statictics/statistics-panel.component';
|
||||||
import { SignInGuard } from './route/sign-in-guard-activate.service';
|
import { SignInGuard } from './route/sign-in-guard-activate.service';
|
||||||
import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service';
|
import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.service';
|
||||||
|
import { MemberGuard } from './route/member-guard-activate.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -79,7 +80,8 @@ import { LeavingConfigRouteDeactivate } from './route/leaving-config-deactivate.
|
||||||
SystemAdminGuard,
|
SystemAdminGuard,
|
||||||
AuthCheckGuard,
|
AuthCheckGuard,
|
||||||
SignInGuard,
|
SignInGuard,
|
||||||
LeavingConfigRouteDeactivate
|
LeavingConfigRouteDeactivate,
|
||||||
|
MemberGuard
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SharedModule {
|
export class SharedModule {
|
||||||
|
|
|
@ -132,7 +132,10 @@
|
||||||
"DELETION_TITLE": "Confirm project deletion",
|
"DELETION_TITLE": "Confirm project deletion",
|
||||||
"DELETION_SUMMARY": "Do you want to delete project {{param}}?",
|
"DELETION_SUMMARY": "Do you want to delete project {{param}}?",
|
||||||
"FILTER_PLACEHOLDER": "Filter Projects",
|
"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": {
|
"PROJECT_DETAIL": {
|
||||||
"REPOSITORIES": "Repositories",
|
"REPOSITORIES": "Repositories",
|
||||||
|
@ -159,7 +162,10 @@
|
||||||
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
"UNKNOWN_ERROR": "Unknown error occurred while adding member.",
|
||||||
"FILTER_PLACEHOLDER": "Filter Members",
|
"FILTER_PLACEHOLDER": "Filter Members",
|
||||||
"DELETION_TITLE": "Confirm project member deletion",
|
"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": {
|
"AUDIT_LOG": {
|
||||||
"USERNAME": "Username",
|
"USERNAME": "Username",
|
||||||
|
@ -235,7 +241,12 @@
|
||||||
"TOGGLE_ENABLE_TITLE": "Enable Policy",
|
"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.",
|
"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",
|
"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": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "New Endpoint",
|
"NEW_ENDPOINT": "New Endpoint",
|
||||||
|
@ -257,7 +268,11 @@
|
||||||
"INVALID_NAME": "Invalid destination name.",
|
"INVALID_NAME": "Invalid destination name.",
|
||||||
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
"FAILED_TO_GET_TARGET": "Failed to get endpoint.",
|
||||||
"CREATION_TIME": "Creation Time",
|
"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": {
|
"REPOSITORY": {
|
||||||
"COPY_ID": "Copy ID",
|
"COPY_ID": "Copy ID",
|
||||||
|
@ -286,7 +301,9 @@
|
||||||
"SHOW_DETAILS": "Show Details",
|
"SHOW_DETAILS": "Show Details",
|
||||||
"REPOSITORIES": "Repositories",
|
"REPOSITORIES": "Repositories",
|
||||||
"ITEMS": "item(s)",
|
"ITEMS": "item(s)",
|
||||||
"POP_REPOS": "Popular Repositories"
|
"POP_REPOS": "Popular Repositories",
|
||||||
|
"DELETED_REPO_SUCCESS": "Deleted repository successfully.",
|
||||||
|
"DELETED_TAG_SUCCESS": "Deleted tag successfully."
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
|
"FORM_CHANGE_CONFIRMATION": "Some changes are not saved yet, do you really want to cancel?"
|
||||||
|
@ -310,7 +327,7 @@
|
||||||
"EMAIL": "Email",
|
"EMAIL": "Email",
|
||||||
"SYSTEM": "System Settings",
|
"SYSTEM": "System Settings",
|
||||||
"CONFIRM_TITLE": "Confirm to cancel",
|
"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",
|
"SAVE_SUCCESS": "Configurations have been successfully saved",
|
||||||
"MAIL_SERVER": "Email Server",
|
"MAIL_SERVER": "Email Server",
|
||||||
"MAIL_SERVER_PORT": "Email Server Port",
|
"MAIL_SERVER_PORT": "Email Server Port",
|
||||||
|
@ -386,7 +403,8 @@
|
||||||
"IN_PROGRESS": "Search...",
|
"IN_PROGRESS": "Search...",
|
||||||
"BACK": "Back"
|
"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",
|
"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_TITLE": "删除项目确认",
|
||||||
"DELETION_SUMMARY": "你确认删除项目 {{param}}?",
|
"DELETION_SUMMARY": "你确认删除项目 {{param}}?",
|
||||||
"FILTER_PLACEHOLDER": "过滤项目",
|
"FILTER_PLACEHOLDER": "过滤项目",
|
||||||
"REPLICATION_RULE": "复制策略"
|
"REPLICATION_RULE": "复制策略",
|
||||||
|
"CREATED_SUCCESS": "创建项目成功。",
|
||||||
|
"DELETED_SUCCESS": "删除项目成功。",
|
||||||
|
"TOGGLED_SUCCESS": "切换状态成功。"
|
||||||
},
|
},
|
||||||
"PROJECT_DETAIL": {
|
"PROJECT_DETAIL": {
|
||||||
"REPOSITORIES": "镜像仓库",
|
"REPOSITORIES": "镜像仓库",
|
||||||
|
@ -159,7 +162,10 @@
|
||||||
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
"UNKNOWN_ERROR": "添加成员时发生未知错误。",
|
||||||
"FILTER_PLACEHOLDER": "过滤成员",
|
"FILTER_PLACEHOLDER": "过滤成员",
|
||||||
"DELETION_TITLE": "删除项目成员确认",
|
"DELETION_TITLE": "删除项目成员确认",
|
||||||
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?"
|
"DELETION_SUMMARY": "你确认删除项目成员 {{param}}?",
|
||||||
|
"ADDED_SUCCESS": "新增成员成功。",
|
||||||
|
"DELETED_SUCCESS": "删除成员成功",
|
||||||
|
"SWITCHED_SUCCESS": "切换角色成功"
|
||||||
},
|
},
|
||||||
"AUDIT_LOG": {
|
"AUDIT_LOG": {
|
||||||
"USERNAME": "用户名",
|
"USERNAME": "用户名",
|
||||||
|
@ -235,7 +241,12 @@
|
||||||
"TOGGLE_ENABLE_TITLE": "启用策略",
|
"TOGGLE_ENABLE_TITLE": "启用策略",
|
||||||
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。",
|
"CONFIRM_TOGGLE_ENABLE_POLICY": "启用策略后,该项目下的所有镜像仓库将复制到目标实例。请确认继续。",
|
||||||
"TOGGLE_DISABLE_TITLE": "停用策略",
|
"TOGGLE_DISABLE_TITLE": "停用策略",
|
||||||
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。"
|
"CONFIRM_TOGGLE_DISABLE_POLICY": "停用策略后,所有未完成的复制任务将被终止和取消。请确认继续。",
|
||||||
|
"CREATED_SUCCESS": "创建复制策略成功。",
|
||||||
|
"UPDATED_SUCCESS": "更新复制策略成功。",
|
||||||
|
"DELETED_SUCCESS": "删除复制策略成功。",
|
||||||
|
"DELETED_FAILED": "删除复制策略失败。",
|
||||||
|
"TOGGLED_SUCCESS": "切换复制策略状态成功。"
|
||||||
},
|
},
|
||||||
"DESTINATION": {
|
"DESTINATION": {
|
||||||
"NEW_ENDPOINT": "新建目标",
|
"NEW_ENDPOINT": "新建目标",
|
||||||
|
@ -257,7 +268,11 @@
|
||||||
"INVALID_NAME": "无效的目标名称。",
|
"INVALID_NAME": "无效的目标名称。",
|
||||||
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
"FAILED_TO_GET_TARGET": "获取目标失败。",
|
||||||
"CREATION_TIME": "创建时间",
|
"CREATION_TIME": "创建时间",
|
||||||
"ITEMS": "条记录"
|
"ITEMS": "条记录",
|
||||||
|
"CREATED_SUCCESS": "创建目标成功。",
|
||||||
|
"UPDATED_SUCCESS": "更新目标成功。",
|
||||||
|
"DELETED_SUCCESS": "删除目标成功。",
|
||||||
|
"DELETED_FAILED": "删除目标失败。"
|
||||||
},
|
},
|
||||||
"REPOSITORY": {
|
"REPOSITORY": {
|
||||||
"COPY_ID": "复制ID",
|
"COPY_ID": "复制ID",
|
||||||
|
@ -286,7 +301,9 @@
|
||||||
"SHOW_DETAILS": "显示详细",
|
"SHOW_DETAILS": "显示详细",
|
||||||
"REPOSITORIES": "镜像仓库",
|
"REPOSITORIES": "镜像仓库",
|
||||||
"ITEMS": "条记录",
|
"ITEMS": "条记录",
|
||||||
"POP_REPOS": "受欢迎的镜像库"
|
"POP_REPOS": "受欢迎的镜像库",
|
||||||
|
"DELETED_REPO_SUCCESS": "删除镜像仓库成功。",
|
||||||
|
"DELETED_TAG_SUCCESS": "删除镜像标签成功。"
|
||||||
},
|
},
|
||||||
"ALERT": {
|
"ALERT": {
|
||||||
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?"
|
"FORM_CHANGE_CONFIRMATION": "表单内容改变,确认取消?"
|
||||||
|
@ -388,5 +405,6 @@
|
||||||
},
|
},
|
||||||
"UNKNOWN_ERROR": "发生未知错误,请稍后再试",
|
"UNKNOWN_ERROR": "发生未知错误,请稍后再试",
|
||||||
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续",
|
"UNAUTHORIZED_ERROR": "会话无效或者已经过期, 请重新登录以继续",
|
||||||
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限"
|
"FORBIDDEN_ERROR": "当前操作被禁止,请确认你有合法的权限",
|
||||||
|
"GENERAL_ERROR": "调用后台服务时出现错误: {{param}}"
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user