mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-26 04:05:40 +01:00
Merge pull request #5074 from ninjadq/ldap_search_ui
Add LDAP search UI
This commit is contained in:
commit
6dfccc7dea
@ -79,7 +79,7 @@ script:
|
||||
- sudo mkdir -p /harbor
|
||||
- sudo mv ./VERSION /harbor/UIVERSION
|
||||
- sudo service postgresql stop
|
||||
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1
|
||||
- sudo make run_clarity_ut CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0
|
||||
- cat ./src/ui_ng/npm-ut-test-results
|
||||
- sudo ./tests/testprepare.sh
|
||||
- sudo make -f make/photon/Makefile _build_db _build_registry -e VERSIONTAG=dev -e CLAIRDBVERSION=dev -e REGISTRYVERSION=v2.6.2
|
||||
@ -105,7 +105,7 @@ script:
|
||||
- sudo rm -rf /data/config/*
|
||||
- sudo rm -rf /data/database/*
|
||||
- ls /data/cert
|
||||
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1 NOTARYFLAG=true CLAIRFLAG=true
|
||||
- sudo make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 NOTARYFLAG=true CLAIRFLAG=true
|
||||
- sleep 10
|
||||
- docker ps
|
||||
- ./tests/validatecontainers.sh
|
||||
|
@ -50,19 +50,19 @@ You can compile the code by one of the three approaches:
|
||||
* Build, install and bring up Harbor without Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor with Notary:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1 NOTARYFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 NOTARYFLAG=true
|
||||
```
|
||||
|
||||
* Build, install and bring up Harbor with Clair:
|
||||
|
||||
```sh
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.1 CLAIRFLAG=true
|
||||
$ make install GOBUILDIMAGE=golang:1.9.2 COMPILETAG=compile_golangimage CLARITYIMAGE=vmware/harbor-clarity-ui-builder:1.4.0 CLAIRFLAG=true
|
||||
```
|
||||
|
||||
#### II. Compile code with your own Golang environment, then build Harbor
|
||||
|
@ -34,7 +34,7 @@ sed -i 's/* as//g' src/app/shared/gauge/gauge.component.js
|
||||
cp ./dist/build.min.js ../ui/static/
|
||||
|
||||
cp -r ./src/i18n/ ../ui/static/
|
||||
cp ./src/styles.scss ../ui/static/
|
||||
cp ./src/styles.css ../ui/static/
|
||||
cp -r ./src/images/ ../ui/static/
|
||||
cp ./src/setting.json ../ui/static/
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
"styles": [
|
||||
"../node_modules/clarity-icons/clarity-icons.min.css",
|
||||
"../node_modules/clarity-ui/clarity-ui.min.css",
|
||||
"styles.scss"
|
||||
"styles.css"
|
||||
],
|
||||
"scripts": [
|
||||
"../node_modules/core-js/client/shim.min.js",
|
||||
|
@ -65,6 +65,10 @@ export class Configuration {
|
||||
ldap_uid: StringValueItem;
|
||||
ldap_url: StringValueItem;
|
||||
ldap_verify_cert: BoolValueItem;
|
||||
ldap_group_base_dn: StringValueItem;
|
||||
ldap_group_search_filter: StringValueItem;
|
||||
ldap_group_attribute_name: StringValueItem;
|
||||
ldap_group_search_scope: NumberValueItem;
|
||||
uaa_client_id: StringValueItem;
|
||||
uaa_client_secret?: StringValueItem;
|
||||
uaa_endpoint: StringValueItem;
|
||||
@ -96,6 +100,10 @@ export class Configuration {
|
||||
this.ldap_uid = new StringValueItem("", true);
|
||||
this.ldap_url = new StringValueItem("", true);
|
||||
this.ldap_verify_cert = new BoolValueItem(true, true);
|
||||
this.ldap_group_base_dn = new StringValueItem("", true);
|
||||
this.ldap_group_search_filter = new StringValueItem("", true);
|
||||
this.ldap_group_attribute_name = new StringValueItem("", true);
|
||||
this.ldap_group_search_scope = new NumberValueItem(0, true);
|
||||
this.uaa_client_id = new StringValueItem("", true);
|
||||
this.uaa_client_secret = new StringValueItem("", true);
|
||||
this.uaa_endpoint = new StringValueItem("", true);
|
||||
|
@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
export class BatchInfo {
|
||||
id?: number;
|
||||
name: string;
|
||||
status: string;
|
||||
loading: boolean;
|
||||
@ -17,11 +18,17 @@ export class BatchInfo {
|
||||
}
|
||||
}
|
||||
|
||||
export function BathInfoChanges(list: BatchInfo, status: string, loading = false, errStatus = false, errorInfo = '') {
|
||||
list.status = status;
|
||||
list.loading = loading;
|
||||
list.errorState = errStatus;
|
||||
list.errorInfo = errorInfo;
|
||||
return list;
|
||||
export function BathInfoChanges(batchInfo: BatchInfo, status: string, loading = false, errStatus = false, errorInfo = '') {
|
||||
batchInfo.status = status;
|
||||
batchInfo.loading = loading;
|
||||
batchInfo.errorState = errStatus;
|
||||
batchInfo.errorInfo = errorInfo;
|
||||
return batchInfo;
|
||||
}
|
||||
|
||||
export enum BatchOperations {
|
||||
Idle,
|
||||
Delete,
|
||||
ChangeRole
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,9 @@ export function getCurrentLanguage(translateService: TranslateService) {
|
||||
BaseModule,
|
||||
AccountModule,
|
||||
HarborRoutingModule,
|
||||
ConfigurationModule,
|
||||
ConfigurationModule
|
||||
],
|
||||
exports: [
|
||||
],
|
||||
providers: [
|
||||
AppConfigService,
|
||||
|
@ -19,6 +19,7 @@ import { ProjectModule } from '../project/project.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
import { AccountModule } from '../account/account.module';
|
||||
import { RepositoryModule } from '../repository/repository.module';
|
||||
import { GroupModule } from '../group/group.module';
|
||||
|
||||
import { NavigatorComponent } from './navigator/navigator.component';
|
||||
import { GlobalSearchComponent } from './global-search/global-search.component';
|
||||
@ -36,7 +37,8 @@ import { SearchTriggerService } from './global-search/search-trigger.service';
|
||||
UserModule,
|
||||
AccountModule,
|
||||
RouterModule,
|
||||
RepositoryModule
|
||||
RepositoryModule,
|
||||
GroupModule
|
||||
],
|
||||
declarations: [
|
||||
NavigatorComponent,
|
||||
|
@ -28,6 +28,13 @@
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.USER' | translate}}
|
||||
</a>
|
||||
<a *ngIf='isLdapMode'
|
||||
clrVerticalNavLink
|
||||
routerLink="/harbor/groups"
|
||||
routerLinkActive="active">
|
||||
<clr-icon shape="users" clrVerticalNavIcon></clr-icon>
|
||||
{{'SIDE_NAV.SYSTEM_MGMT.GROUP' | translate}}
|
||||
</a>
|
||||
<a clrVerticalNavLink
|
||||
routerLink="/harbor/registries"
|
||||
routerLinkActive="active">
|
||||
|
@ -13,6 +13,8 @@
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { AppConfigService } from '../..//app-config.service';
|
||||
|
||||
import { ModalEvent } from '../modal-event';
|
||||
import { modalEvents } from '../modal-events.const';
|
||||
@ -23,11 +25,7 @@ import { NavigatorComponent } from '../navigator/navigator.component';
|
||||
import { SessionService } from '../../shared/session.service';
|
||||
|
||||
import { AboutDialogComponent } from '../../shared/about-dialog/about-dialog.component';
|
||||
|
||||
import { SearchTriggerService } from '../global-search/search-trigger.service';
|
||||
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { CommonRoutes } from '../../shared/shared.const';
|
||||
|
||||
@Component({
|
||||
@ -61,7 +59,8 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private session: SessionService,
|
||||
private searchTrigger: SearchTriggerService) { }
|
||||
private searchTrigger: SearchTriggerService,
|
||||
private appConfigService: AppConfigService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.searchSub = this.searchTrigger.searchTriggerChan$.subscribe(searchEvt => {
|
||||
@ -98,6 +97,11 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
||||
return account != null && account.has_admin_role;
|
||||
}
|
||||
|
||||
public get isLdapMode(): boolean {
|
||||
let appConfig = this.appConfigService.getConfig();
|
||||
return appConfig.auth_mode === 'ldap_auth';
|
||||
}
|
||||
|
||||
public get isUserExisting(): boolean {
|
||||
let account = this.session.getCurrentUser();
|
||||
return account != null;
|
||||
|
@ -19,50 +19,44 @@
|
||||
<section class="form-block" *ngIf="showUAA">
|
||||
<div class="form-group">
|
||||
<label for="uaa-endpoint" class="required">{{'CONFIG.UAA.ENDPOINT' | translate}}</label>
|
||||
<input type="text" id="uaa-endpoint" name="uaa-endpoint" size="35"
|
||||
[(ngModel)]="currentConfig.uaa_endpoint.value" [disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
<input type="text" id="uaa-endpoint" name="uaa-endpoint" size="35" [(ngModel)]="currentConfig.uaa_endpoint.value" [disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uaa-id" class="required">{{'CONFIG.UAA.CLIENT_ID' | translate}}</label>
|
||||
<input type="text" id="uaa-cid" name="uaa-client-id" size="35"
|
||||
[(ngModel)]="currentConfig.uaa_client_id.value" [disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
<input type="text" id="uaa-cid" name="uaa-client-id" size="35" [(ngModel)]="currentConfig.uaa_client_id.value" [disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uaa-id" class="required">{{'CONFIG.UAA.CLIENT_SECRET' | translate}}</label>
|
||||
<input type="text" id="uaa-secret" name="uaa-client-secret" type="password" size="35"
|
||||
[(ngModel)]="currentConfig.uaa_client_secret.value" [disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
<input type="text" id="uaa-secret" name="uaa-client-secret" type="password" size="35" [(ngModel)]="currentConfig.uaa_client_secret.value"
|
||||
[disabled]="!currentConfig.uaa_verify_cert.editable">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="uaa-cert" class="required">{{'CONFIG.UAA.VERIFY_CERT' | translate}}</label>
|
||||
<clr-checkbox id="uaa-cert" name="uaa-cert" [(clrChecked)]="currentConfig.uaa_verify_cert.value"
|
||||
[clrDisabled]="!currentConfig.uaa_verify_cert.editable"></clr-checkbox>
|
||||
<clr-checkbox id="uaa-cert" name="uaa-cert" [(clrChecked)]="currentConfig.uaa_verify_cert.value" [clrDisabled]="!currentConfig.uaa_verify_cert.editable"></clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="form-block" *ngIf="showLdap">
|
||||
<div class="form-group">
|
||||
<label for="ldapUrl" class="required">{{'CONFIG.LDAP.URL' | translate}}</label>
|
||||
<label for="ldapUrl" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right" [class.invalid]="ldapUrlInput.invalid && (ldapUrlInput.dirty || ldapUrlInput.touched)">
|
||||
<input name="ldapUrl" type="text" #ldapUrlInput="ngModel" [(ngModel)]="currentConfig.ldap_url.value"
|
||||
required
|
||||
id="ldapUrl"
|
||||
size="40"
|
||||
[disabled]="disabled(currentConfig.ldap_url)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<label for="ldapUrl" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right"
|
||||
[class.invalid]="ldapUrlInput.invalid && (ldapUrlInput.dirty || ldapUrlInput.touched)">
|
||||
<input name="ldapUrl" type="text" #ldapUrlInput="ngModel" [(ngModel)]="currentConfig.ldap_url.value" required id="ldapUrl"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_url)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapSearchDN">{{'CONFIG.LDAP.SEARCH_DN' | translate}}</label>
|
||||
<label for="ldapSearchDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapSearchDN" type="text" #ldapSearchDNInput="ngModel" [(ngModel)]="currentConfig.ldap_search_dn.value"
|
||||
id="ldapSearchDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_search_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<input name="ldapSearchDN" type="text" #ldapSearchDNInput="ngModel" [(ngModel)]="currentConfig.ldap_search_dn.value" id="ldapSearchDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_search_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-1px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.LDAP_SEARCH_DN' | translate}}</span>
|
||||
@ -71,25 +65,23 @@
|
||||
<div class="form-group">
|
||||
<label for="ldapSearchPwd">{{'CONFIG.LDAP.SEARCH_PWD' | translate}}</label>
|
||||
<label for="ldapSearchPwd" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapSearchPwd" type="password" #ldapSearchPwdInput="ngModel" [(ngModel)]="currentConfig.ldap_search_password.value"
|
||||
id="ldapSearchPwd"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_search_password)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<input name="ldapSearchPwd" type="password" #ldapSearchPwdInput="ngModel" [(ngModel)]="currentConfig.ldap_search_password.value"
|
||||
id="ldapSearchPwd" size="40" [disabled]="disabled(currentConfig.ldap_search_password)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapBaseDN" class="required">{{'CONFIG.LDAP.BASE_DN' | translate}}</label>
|
||||
<label for="ldapBaseDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right" [class.invalid]="ldapBaseDNInput.invalid && (ldapBaseDNInput.dirty || ldapBaseDNInput.touched)">
|
||||
<input name="ldapBaseDN" type="text" #ldapBaseDNInput="ngModel" [(ngModel)]="currentConfig.ldap_base_dn.value"
|
||||
required
|
||||
id="ldapBaseDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_base_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<label for="ldapBaseDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right"
|
||||
[class.invalid]="ldapBaseDNInput.invalid && (ldapBaseDNInput.dirty || ldapBaseDNInput.touched)">
|
||||
<input name="ldapBaseDN" type="text" #ldapBaseDNInput="ngModel" [(ngModel)]="currentConfig.ldap_base_dn.value" required id="ldapBaseDN"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_base_dn)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top: -1px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.LDAP_BASE_DN' | translate}}</span>
|
||||
@ -98,25 +90,23 @@
|
||||
<div class="form-group">
|
||||
<label for="ldapFilter">{{'CONFIG.LDAP.FILTER' | translate}}</label>
|
||||
<label for="ldapFilter" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapFilter" type="text" #ldapFilterInput="ngModel" [(ngModel)]="currentConfig.ldap_filter.value"
|
||||
id="ldapFilter"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_filter)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<input name="ldapFilter" type="text" #ldapFilterInput="ngModel" [(ngModel)]="currentConfig.ldap_filter.value" id="ldapFilter"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_filter)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapUid" class="required">{{'CONFIG.LDAP.UID' | translate}}</label>
|
||||
<label for="ldapUid" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right" [class.invalid]="ldapUidInput.invalid && (ldapUidInput.dirty || ldapUidInput.touched)">
|
||||
<input name="ldapUid" type="text" #ldapUidInput="ngModel" [(ngModel)]="currentConfig.ldap_uid.value"
|
||||
required
|
||||
id="ldapUid"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_uid)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<label for="ldapUid" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right"
|
||||
[class.invalid]="ldapUidInput.invalid && (ldapUidInput.dirty || ldapUidInput.touched)">
|
||||
<input name="ldapUid" type="text" #ldapUidInput="ngModel" [(ngModel)]="currentConfig.ldap_uid.value" required id="ldapUid"
|
||||
size="40" [disabled]="disabled(currentConfig.ldap_uid)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.ITEM_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top: -1px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.LDAP_UID' | translate}}</span>
|
||||
@ -138,13 +128,72 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapVerifyCert">{{'CONFIG.LDAP.VERIFY_CERT' | translate}}</label>
|
||||
<clr-checkbox name="ldapVerifyCert" id="ldapVerifyCert" [clrChecked]="currentConfig.ldap_verify_cert.value" [clrDisabled]="disabled(currentConfig.ldap_scope)" (clrCheckedChange)="setVerifyCertValue($event)">
|
||||
<clr-checkbox name="ldapVerifyCert" id="ldapVerifyCert" [clrChecked]="currentConfig.ldap_verify_cert.value" [clrDisabled]="disabled(currentConfig.ldap_scope)"
|
||||
(clrCheckedChange)="setVerifyCertValue($event)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_CERT' | translate}}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapGroupBaseDN" class="required">{{'CONFIG.LDAP.LDAP_GROUP_BASE_DN' | translate}}</label>
|
||||
<label for="ldapGroupBaseDN" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapGroupBaseDN" type="text" #ldapGroupDNInput="ngModel" [(ngModel)]="currentConfig.ldap_group_base_dn.value"
|
||||
id="ldapGroupBaseDN" size="40" [disabled]="disabled(currentConfig.ldap_group_base_dn)">
|
||||
<span class="tooltip-content"> {{'TOOLTIP.ITEM_REQUIRED' | translate}} </span>
|
||||
</label>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.LDAP.LDAP_GROUP_BASE_DN_INFO' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapGroupFilter" class="required">{{'CONFIG.LDAP.LDAP_GROUP_FILTER' | translate}}</label>
|
||||
<label for="ldapGroupFilter" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapGroupFilter" type="text" #ldapGroupFilterInput="ngModel" [(ngModel)]="currentConfig.ldap_group_search_filter.value"
|
||||
id="ldapGroupFilter" size="40" [disabled]="disabled(currentConfig.ldap_group_search_filter)">
|
||||
<span class="tooltip-content"> {{'TOOLTIP.ITEM_REQUIRED' | translate}} </span>
|
||||
</label>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.LDAP.LDAP_GROUP_FILTER_INFO' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapGroupGID" class="required">{{'CONFIG.LDAP.LDAP_GROUP_GID' | translate}}</label>
|
||||
<label for="ldapGroupGID" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-lg tooltip-top-right">
|
||||
<input name="ldapGroupGID" type="text" #ldapGroupDNInput="ngModel" [(ngModel)]="currentConfig.ldap_group_attribute_name.value"
|
||||
id="ldapGroupGID" size="40" [disabled]="disabled(currentConfig.ldap_group_attribute_name)">
|
||||
<span class="tooltip-content"> {{'TOOLTIP.ITEM_REQUIRED' | translate}} </span>
|
||||
</label>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.LDAP.LDAP_GROUP_GID_INFO' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ldapGroupScope">{{'CONFIG.LDAP.GROUP_SCOPE' | translate}}</label>
|
||||
<div class="select">
|
||||
<select id="ldapGroupScope" name="ldapGroupScope" [(ngModel)]="currentConfig.ldap_group_search_scope.value" [disabled]="disabled(currentConfig.ldap_group_search_scope)">
|
||||
<option value="0">{{'CONFIG.SCOPE_BASE' | translate }}</option>
|
||||
<option value="1">{{'CONFIG.SCOPE_ONE_LEVEL' | translate }}</option>
|
||||
<option value="2">{{'CONFIG.SCOPE_SUBTREE' | translate }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
<clr-tooltip-content clrPosition="top-right" clrSize="lg" *clrIfOpen>
|
||||
<span>{{'CONFIG.LDAP.GROUP_SCOPE_INFO' | translate}}</span>
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
</div>
|
||||
</section>
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
@ -165,8 +214,10 @@
|
||||
<clr-checkbox name="selfReg" id="selfReg" [(ngModel)]="currentConfig.self_registration.value" [disabled]="disabled(currentConfig.self_registration)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right" style="top:-7px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span *ngIf="checkable; else elseBlock" class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_ENABLE' | translate}}</span>
|
||||
<ng-template #elseBlock><span class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_DISABLE' | translate}}</span></ng-template>
|
||||
<span *ngIf="checkable; else elseBlock" class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_ENABLE' | translate}}</span>
|
||||
<ng-template #elseBlock>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.SELF_REGISTRATION_DISABLE' | translate}}</span>
|
||||
</ng-template>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
|
@ -0,0 +1,3 @@
|
||||
clr-tooltip {
|
||||
top: -1px;
|
||||
}
|
@ -371,7 +371,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
.catch(error => {
|
||||
this.testingLDAPOnGoing = false;
|
||||
let err = error._body;
|
||||
if (!err) {
|
||||
if (!err || !err.trim()) {
|
||||
err = 'UNKNOWN';
|
||||
}
|
||||
this.msgHandler.showError('CONFIG.TEST_LDAP_FAILED', { 'param': err });
|
||||
|
@ -0,0 +1,43 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalClosable]="false">
|
||||
<h3 class="modal-title" *ngIf="mode === 'create'">{{'GROUP.IMPORT_LDAP_GROUP' | translate}}</h3>
|
||||
<h3 class="modal-title" *ngIf="mode !== 'create'">{{'GROUP.EDIT' | translate}}</h3>
|
||||
|
||||
<div class="modal-body">
|
||||
<form class="form" #groupForm="ngForm">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="ldap_group_dn" class="required">{{ 'GROUP.GROUP_DN' | translate}}</label>
|
||||
<label for="ldap_group_dn"
|
||||
aria-haspopup="true"
|
||||
role="tooltip"
|
||||
class="tooltip tooltip-validation tooltip-sm tooltip-right"
|
||||
[class.invalid]="isDNInvalid">
|
||||
<input type="text" id="ldap_group_dn" name="ldap_group_dn"
|
||||
required
|
||||
[(ngModel)]="group.ldap_group_dn"
|
||||
#groupDN="ngModel">
|
||||
<span class="tooltip-content">
|
||||
{{dnTooltip | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="type">{{'GROUP.TYPE' | translate}}</label>
|
||||
<label id="type">LDAP</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="group_name">{{'GROUP.NAME' | translate}}</label>
|
||||
<label for="group_name">
|
||||
<input type="text" id="group_name" name="group_name"
|
||||
[(ngModel)]="group.group_name"
|
||||
#groupDN="ngModel">
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline" (click)="close()">{{'BUTTON.CANCEL' | translate | translate}}</button>
|
||||
<button type="button" class="btn btn-primary" [disabled]="!isFormValid" (click)="save()">{{'BUTTON.SAVE' | translate | translate}}</button>
|
||||
</div>
|
||||
</clr-modal>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddGroupModalComponent } from './add-group-modal.component';
|
||||
|
||||
describe('AddGroupModalComponent', () => {
|
||||
let component: AddGroupModalComponent;
|
||||
let fixture: ComponentFixture<AddGroupModalComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AddGroupModalComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddGroupModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,109 @@
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { Component, OnInit, EventEmitter, Output, ChangeDetectorRef, OnDestroy, ViewChild } from "@angular/core";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import "rxjs/add/operator/finally";
|
||||
|
||||
import { GroupService } from "../group.service";
|
||||
import { MessageHandlerService } from "./../../shared/message-handler/message-handler.service";
|
||||
import { SessionService } from "./../../shared/session.service";
|
||||
import { UserGroup } from "./../group";
|
||||
|
||||
@Component({
|
||||
selector: "hbr-add-group-modal",
|
||||
templateUrl: "./add-group-modal.component.html",
|
||||
styleUrls: ["./add-group-modal.component.scss"]
|
||||
})
|
||||
export class AddGroupModalComponent implements OnInit, OnDestroy {
|
||||
opened = false;
|
||||
mode = "create";
|
||||
dnTooltip = 'TOOLTIP.ITEM_REQUIRED';
|
||||
|
||||
group: UserGroup = new UserGroup();
|
||||
|
||||
formChangeSubscription: Subscription;
|
||||
|
||||
@ViewChild('groupForm')
|
||||
groupForm: NgForm;
|
||||
|
||||
submitted = false;
|
||||
|
||||
@Output() dataChange = new EventEmitter();
|
||||
|
||||
constructor(
|
||||
private session: SessionService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private groupService: GroupService,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
|
||||
ngOnDestroy() { }
|
||||
|
||||
public get isDNInvalid(): boolean {
|
||||
let dnControl = this.groupForm.controls['ldap_group_dn'];
|
||||
return dnControl && dnControl.invalid && (dnControl.dirty || dnControl.touched);
|
||||
}
|
||||
|
||||
public get isFormValid(): boolean {
|
||||
return this.groupForm.valid;
|
||||
}
|
||||
|
||||
public open(group?: UserGroup, editMode: boolean = false): void {
|
||||
this.resetGroup();
|
||||
if (editMode) {
|
||||
this.mode = "edit";
|
||||
Object.assign(this.group, group);
|
||||
} else {
|
||||
this.mode = "create";
|
||||
}
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
this.resetGroup();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (this.mode === "create") {
|
||||
this.createGroup();
|
||||
} else {
|
||||
this.editGroup();
|
||||
}
|
||||
}
|
||||
|
||||
createGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService
|
||||
.createGroup(groupCopy)
|
||||
.finally(() => this.close())
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess("GROUP.ADD_GROUP_SUCCESS");
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
editGroup() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.groupService
|
||||
.editGroup(groupCopy)
|
||||
.finally(() => this.close())
|
||||
.subscribe(
|
||||
res => {
|
||||
this.msgHandler.showSuccess("ADD_GROUP_FAILURE");
|
||||
this.dataChange.emit();
|
||||
},
|
||||
error => this.msgHandler.handleError(error)
|
||||
);
|
||||
}
|
||||
|
||||
resetGroup() {
|
||||
this.group = new UserGroup();
|
||||
this.groupForm.reset();
|
||||
}
|
||||
}
|
40
src/ui_ng/src/app/group/group.component.html
Normal file
40
src/ui_ng/src/app/group/group.component.html
Normal file
@ -0,0 +1,40 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2">{{'GROUP.GROUP' | translate}}</h2>
|
||||
<div class="action-panel-pos rightPos">
|
||||
<hbr-filter [withDivider]="true" class="filter-pos" filterPlaceholder='group name' (filterEvt)="doFilter($event)" [currentValue]="currentTerm"></hbr-filter>
|
||||
<span class="refresh-btn">
|
||||
<clr-icon shape="refresh" [hidden]="loading" ng-disabled="loading" (click)="refresh()"></clr-icon>
|
||||
<span class="spinner spinner-inline" [hidden]="loading === false"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<clr-datagrid [(clrDgSelected)]="selectedGroups" [clrDgLoading]="loading">
|
||||
<clr-dg-action-bar >
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addGroup()" [disabled]="!canAddGroup">
|
||||
<clr-icon shape="plus" size="15"></clr-icon> {{'GROUP.ADD' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="editGroup()" [disabled]="!canEditGroup">
|
||||
<clr-icon shape="pencil" size="15"></clr-icon> {{'GROUP.EDIT' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="openDeleteConfirmationDialog()" [disabled]="!canAddGroup">
|
||||
<clr-icon shape="times" size="15"></clr-icon> {{'GROUP.DELETE' | translate}}</button>
|
||||
</clr-dg-action-bar>
|
||||
|
||||
<clr-dg-column>{{'GROUP.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'GROUP.TYPE' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'GROUP.DN' | translate}}</clr-dg-column>
|
||||
|
||||
<clr-dg-row *clrDgItems="let group of groups" [clrDgItem]="group">
|
||||
<clr-dg-cell>{{group.group_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{groupToSring(group.group_type) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{group.ldap_group_dn}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="15">
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'USER.OF' | translate }} {{pagination.totalItems}} {{'GROUP.GROUPS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<hbr-add-group-modal (dataChange)="loadData()"></hbr-add-group-modal>
|
||||
</div>
|
||||
</div>
|
45
src/ui_ng/src/app/group/group.component.scss
Normal file
45
src/ui_ng/src/app/group/group.component.scss
Normal file
@ -0,0 +1,45 @@
|
||||
.custom-add-button {
|
||||
font-size: 12px;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.filter-icon {
|
||||
position: relative;
|
||||
right: -12px;
|
||||
}
|
||||
|
||||
.filter-pos {
|
||||
float: right;
|
||||
margin-right: 24px;
|
||||
position: relative;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
.action-panel-pos {
|
||||
position: relative;
|
||||
padding-left: 12px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 17px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
color: #007CBB;
|
||||
}
|
||||
|
||||
.hide-create {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.rightPos {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
margin-top: -7px;
|
||||
height: 32px;
|
||||
z-index: 100;
|
||||
}
|
25
src/ui_ng/src/app/group/group.component.spec.ts
Normal file
25
src/ui_ng/src/app/group/group.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GroupComponent } from './group.component';
|
||||
|
||||
describe('GroupComponent', () => {
|
||||
let component: GroupComponent;
|
||||
let fixture: ComponentFixture<GroupComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ GroupComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GroupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
166
src/ui_ng/src/app/group/group.component.ts
Normal file
166
src/ui_ng/src/app/group/group.component.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { SessionService } from "./../shared/session.service";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { Component, OnInit, ViewChild, OnDestroy } from "@angular/core";
|
||||
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
|
||||
|
||||
import {
|
||||
ConfirmationTargets,
|
||||
ConfirmationState,
|
||||
ConfirmationButtons
|
||||
} from "../shared/shared.const";
|
||||
import { ConfirmationMessage } from "../shared/confirmation-dialog/confirmation-message";
|
||||
import { ConfirmationDialogService } from "./../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { AddGroupModalComponent } from "./add-group-modal/add-group-modal.component";
|
||||
import { UserGroup } from "./group";
|
||||
import { GroupService } from "./group.service";
|
||||
import { MessageHandlerService } from "../shared/message-handler/message-handler.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-group",
|
||||
templateUrl: "./group.component.html",
|
||||
styleUrls: ["./group.component.scss"]
|
||||
})
|
||||
export class GroupComponent implements OnInit, OnDestroy {
|
||||
searchTerm = "";
|
||||
loading = true;
|
||||
groups: UserGroup[] = [];
|
||||
currentPage = 1;
|
||||
totalCount = 0;
|
||||
selectedGroups: UserGroup[] = [];
|
||||
currentTerm = "";
|
||||
delSub: Subscription;
|
||||
batchOps = 'idle';
|
||||
batchInfos = new Map();
|
||||
|
||||
@ViewChild(AddGroupModalComponent) newGroupModal: AddGroupModalComponent;
|
||||
|
||||
constructor(
|
||||
private operationService: OperationService,
|
||||
private translate: TranslateService,
|
||||
private operateDialogService: ConfirmationDialogService,
|
||||
private groupService: GroupService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private session: SessionService
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.loadData();
|
||||
this.delSub = this.operateDialogService.confirmationConfirm$.subscribe(
|
||||
message => {
|
||||
if (
|
||||
message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER
|
||||
) {
|
||||
if (this.batchOps === 'delete') {
|
||||
this.deleteGroups();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
ngOnDestroy(): void {
|
||||
this.delSub.unsubscribe();
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
loadData(): void {
|
||||
this.loading = true;
|
||||
this.groupService.getUserGroups().subscribe(groups => {
|
||||
this.groups = groups.filter(group => {
|
||||
if (!group.group_name) {group.group_name = ''; }
|
||||
return group.group_name.includes(this.searchTerm);
|
||||
}
|
||||
);
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
addGroup(): void {
|
||||
this.newGroupModal.open();
|
||||
}
|
||||
|
||||
editGroup(): void {
|
||||
this.newGroupModal.open(this.selectedGroups[0], true);
|
||||
}
|
||||
|
||||
openDeleteConfirmationDialog(): void {
|
||||
// open delete modal
|
||||
this.batchOps = 'delete';
|
||||
let nameArr: string[] = [];
|
||||
if (this.selectedGroups.length > 0) {
|
||||
this.selectedGroups.forEach(group => {
|
||||
nameArr.push(group.group_name);
|
||||
});
|
||||
// batchInfo.id = group.id;
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
"MEMBER.DELETION_TITLE",
|
||||
"MEMBER.DELETION_SUMMARY",
|
||||
nameArr.join(","),
|
||||
this.selectedGroups,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.operateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
deleteGroups() {
|
||||
let obs = this.selectedGroups.map(group => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_GROUP';
|
||||
operMessage.data.id = group.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = group.group_name;
|
||||
|
||||
this.operationService.publishInfo(operMessage);
|
||||
return this.groupService
|
||||
.deleteGroup(group.id)
|
||||
.flatMap(response => {
|
||||
return this.translate.get("BATCH.DELETED_SUCCESS").flatMap(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
return Observable.of(res);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
return this.translate.get("BATCH.DELETED_FAILURE").flatMap(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
return Observable.of(res);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Observable.forkJoin(obs).subscribe(
|
||||
res => {
|
||||
this.selectedGroups = [];
|
||||
this.batchOps = 'idle';
|
||||
this.loadData();
|
||||
},
|
||||
err => this.msgHandler.handleError(err)
|
||||
);
|
||||
}
|
||||
|
||||
groupToSring(type: number) {
|
||||
if (type === 1) {return 'GROUP.LDAP_TYPE'; } else {return 'UNKNOWN'; }
|
||||
}
|
||||
|
||||
doFilter(groupName: string): void {
|
||||
this.searchTerm = groupName;
|
||||
this.loadData();
|
||||
}
|
||||
get canAddGroup(): boolean {
|
||||
return this.session.currentUser.has_admin_role;
|
||||
}
|
||||
|
||||
get canEditGroup(): boolean {
|
||||
return (
|
||||
this.selectedGroups.length === 1 &&
|
||||
this.session.currentUser.has_admin_role
|
||||
);
|
||||
}
|
||||
}
|
27
src/ui_ng/src/app/group/group.module.ts
Normal file
27
src/ui_ng/src/app/group/group.module.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { GroupComponent } from './group.component';
|
||||
import { AddGroupModalComponent } from './add-group-modal/add-group-modal.component';
|
||||
import { GroupService } from './group.service';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
exports: [
|
||||
GroupComponent,
|
||||
AddGroupModalComponent,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [ GroupService ],
|
||||
declarations: [GroupComponent, AddGroupModalComponent]
|
||||
})
|
||||
export class GroupModule { }
|
15
src/ui_ng/src/app/group/group.service.spec.ts
Normal file
15
src/ui_ng/src/app/group/group.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { GroupService } from './group.service';
|
||||
|
||||
describe('GroupService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [GroupService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([GroupService], (service: GroupService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
81
src/ui_ng/src/app/group/group.service.ts
Normal file
81
src/ui_ng/src/app/group/group.service.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Http, Response } from "@angular/http";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import "rxjs/add/observable/of";
|
||||
import "rxjs/add/operator/delay";
|
||||
import "rxjs/add/operator/toPromise";
|
||||
|
||||
import { UserGroup } from "./group";
|
||||
import { HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS } from "../shared/shared.utils";
|
||||
|
||||
const userGroupEndpoint = "/api/usergroups";
|
||||
const ldapGroupSearchEndpoint = "/api/ldap/groups/search?groupname=";
|
||||
|
||||
@Injectable()
|
||||
export class GroupService {
|
||||
constructor(private http: Http) {}
|
||||
|
||||
private extractData(res: Response) {
|
||||
if (res.text() === '') {return []; };
|
||||
return res.json() || [];
|
||||
}
|
||||
private handleErrorObservable(error: Response | any) {
|
||||
console.error(error.message || error);
|
||||
return Observable.throw(error.message || error);
|
||||
}
|
||||
|
||||
getUserGroups(): Observable<UserGroup[]> {
|
||||
return this.http.get(userGroupEndpoint, HTTP_GET_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(error => {
|
||||
return this.handleErrorObservable(error);
|
||||
});
|
||||
}
|
||||
|
||||
createGroup(group: UserGroup): Observable<any> {
|
||||
return this.http
|
||||
.post(userGroupEndpoint, group, HTTP_JSON_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
getGroup(group_id: number): Observable<UserGroup> {
|
||||
return this.http
|
||||
.get(`${userGroupEndpoint}/${group_id}`, HTTP_JSON_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
editGroup(group: UserGroup): Observable<any> {
|
||||
return this.http
|
||||
.put(`${userGroupEndpoint}/${group.id}`, group, HTTP_JSON_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
deleteGroup(group_id: number): Observable<any> {
|
||||
return this.http
|
||||
.delete(`${userGroupEndpoint}/${group_id}`)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
|
||||
searchGroup(group_name: string): Observable<UserGroup[]> {
|
||||
return this.http
|
||||
.get(`${ldapGroupSearchEndpoint}${group_name}`, HTTP_GET_OPTIONS)
|
||||
.map(response => {
|
||||
return this.extractData(response);
|
||||
})
|
||||
.catch(this.handleErrorObservable);
|
||||
}
|
||||
}
|
12
src/ui_ng/src/app/group/group.ts
Normal file
12
src/ui_ng/src/app/group/group.ts
Normal file
@ -0,0 +1,12 @@
|
||||
export class UserGroup {
|
||||
id?: number;
|
||||
group_name?: string;
|
||||
group_type: number;
|
||||
ldap_group_dn?: string;
|
||||
|
||||
constructor() {
|
||||
{
|
||||
this.group_type = 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import { ConfigurationComponent } from './config/config.component';
|
||||
import { UserComponent } from './user/user.component';
|
||||
import { SignInComponent } from './account/sign-in/sign-in.component';
|
||||
import { ResetPasswordComponent } from './account/password-setting/reset-password/reset-password.component';
|
||||
import { GroupComponent } from './group/group.component';
|
||||
|
||||
import { TotalReplicationPageComponent } from './replication/total-replication/total-replication-page.component';
|
||||
import { DestinationPageComponent } from './replication/destination/destination-page.component';
|
||||
@ -74,6 +75,11 @@ const harborRoutes: Routes = [
|
||||
component: UserComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'groups',
|
||||
component: GroupComponent,
|
||||
canActivate: [SystemAdminGuard]
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
component: DestinationPageComponent,
|
||||
|
@ -0,0 +1,104 @@
|
||||
<clr-modal [(clrModalOpen)]="opened" [clrModalSize]="'lg'" [clrModalStaticBackdrop]="'true'" [clrModalClosable]="false">
|
||||
<h3 class="modal-title">{{'MEMBER.IMPORT_GROUP' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<label>{{ 'MEMBER.NEW_GROUP_INFO' | translate}}</label>
|
||||
|
||||
<div class="form-group modeSelectradios">
|
||||
<div class="radio">
|
||||
<input type="radio" name="modeRadios" [value]="false" id="select_group" [(ngModel)]="createGroupMode">
|
||||
<label for="select_group">{{'MEMBER.ADD_GROUP_SELECT' | translate}}</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<input type="radio" name="modeRadios" [value]="true" id="create_group" [(ngModel)]="createGroupMode">
|
||||
<label for="create_group">{{'MEMBER.CREATE_GROUP_SELECT' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="createGroupMode">
|
||||
<form #groupForm="ngForm">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="ldap_group_dn" class="required">{{ 'MEMBER.LDAP_SEARCH_DN' | translate}}</label>
|
||||
<label for="ldap_group_dn"
|
||||
aria-haspopup="true"
|
||||
role="tooltip"
|
||||
class="tooltip tooltip-validation tooltip-md tooltip-right"
|
||||
[class.invalid]="isDNInvalid">
|
||||
<input type="text" name="ldap_group_dn" size="45"
|
||||
required
|
||||
[(ngModel)]="group.ldap_group_dn"
|
||||
#groupDN="ngModel">
|
||||
<span class="tooltip-content">
|
||||
{{dnTooltip | translate}}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">{{'MEMBER.LDAP_SEARCH_NAME' | translate}}</label>
|
||||
<input type="text" name="ldap_group_name" size="35" [(ngModel)]="group.group_name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="member_role1">{{ 'MEMBER.ROLE' | translate}}</label>
|
||||
<div class="select">
|
||||
<select id="member_role1" name="member_role" [(ngModel)]="selectedRole">
|
||||
<option *ngFor="let role of roles" [ngValue]="role.id"> {{role.value | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
</div>
|
||||
<div *ngIf="!createGroupMode">
|
||||
<div class='row flex-items-xs-between'>
|
||||
<div></div>
|
||||
<div class="filterTool">
|
||||
<hbr-filter [withDivider]="true" filterPlaceholder='{{"MEMBER.FILTER_PLACEHOLDER" | translate}}' (filterEvt)="doFilter($event)"
|
||||
[currentValue]="currentTerm"></hbr-filter>
|
||||
<span class="refresh-btn" (click)="loadGroups()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-1 col-md-1 col-sm-1 col-xs-1">
|
||||
<label>{{'MEMBER.LDAP_GROUP' | translate}}</label>
|
||||
</div>
|
||||
<div class="class=col-lg-11 col-md-11 col-sm-11 col-xs-11">
|
||||
<clr-datagrid class="datagrid-compact" [(clrDgSelected)]="selectedGroups" [clrDgLoading]="onLoading">
|
||||
<clr-dg-column [clrDgField]="'group_name'">{{'MEMBER.LDAP_SEARCH_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'ldap_group_dn'">{{'MEMBER.LDAP_SEARCH_DN' | translate}}</clr-dg-column>
|
||||
<clr-dg-column [clrDgField]="'property'">{{'MEMBER.LDAP_PROPERTY' | translate}}</clr-dg-column>
|
||||
|
||||
<clr-dg-row *clrDgItems="let group of groups" [clrDgItem]="group">
|
||||
<clr-dg-cell>{{group.group_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{group.ldap_group_dn}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{group.property}}</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
|
||||
<clr-dg-footer>
|
||||
<clr-dg-pagination #pagination [clrDgPageSize]="5">
|
||||
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'USER.OF' | translate }} {{pagination.totalItems}} {{'MEMBER.GROUPS' | translate}}
|
||||
</clr-dg-pagination>
|
||||
</clr-dg-footer>
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-1 col-md-1 col-sm-1 col-xs-1">
|
||||
<label>{{ 'MEMBER.ROLE' | translate}}</label>
|
||||
</div>
|
||||
<div class="class=col-lg-4 col-md-4 col-sm-2 col-xs-1">
|
||||
<div class="select">
|
||||
<select id="member_role2" [(ngModel)]="selectedRole">
|
||||
<option *ngFor="let role of roles" [ngValue]="role.id"> {{role.value | translate}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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" [disabled]="!isValid" (click)="onSave()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
</div>
|
||||
|
||||
</clr-modal>
|
@ -0,0 +1,18 @@
|
||||
clr-datagrid {
|
||||
::ng-deep .datagrid {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.modeSelectradios {
|
||||
margin-top: 21px;
|
||||
}
|
||||
.filterTool {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
right: 15px;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AddGroupComponent } from './add-group.component';
|
||||
|
||||
describe('AddGroupComponent', () => {
|
||||
let component: AddGroupComponent;
|
||||
let fixture: ComponentFixture<AddGroupComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ AddGroupComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddGroupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,167 @@
|
||||
import { ChangeDetectorRef, ChangeDetectionStrategy, ViewChild } from "@angular/core";
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { forkJoin } from "rxjs/observable/forkJoin";
|
||||
import { Observable } from "rxjs/Observable";
|
||||
import "rxjs/observable/of";
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import "rxjs/observable/timer";
|
||||
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
|
||||
|
||||
import { UserGroup } from "./../../../group/group";
|
||||
import { MemberService } from "./../member.service";
|
||||
import { GroupService } from "../../../group/group.service";
|
||||
import { ProjectRoles } from "../../../shared/shared.const";
|
||||
import { MessageHandlerService } from '../../../shared/message-handler/message-handler.service';
|
||||
import { Member } from "../member";
|
||||
|
||||
@Component({
|
||||
selector: "add-group",
|
||||
templateUrl: "./add-group.component.html",
|
||||
styleUrls: ["./add-group.component.scss"],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class AddGroupComponent implements OnInit {
|
||||
opened = false;
|
||||
createGroupMode = false;
|
||||
onLoading = false;
|
||||
roles = ProjectRoles;
|
||||
currentTerm = '';
|
||||
|
||||
selectedRole = 1;
|
||||
group = new UserGroup();
|
||||
selectedGroups: UserGroup[] = [];
|
||||
groups: UserGroup[] = [];
|
||||
|
||||
dnTooltip = 'TOOLTIP.ITEM_REQUIRED';
|
||||
|
||||
@Input() projectId: number;
|
||||
@Input() memberList: Member[] = [];
|
||||
@Output() added = new EventEmitter<boolean>();
|
||||
|
||||
@ViewChild('groupForm')
|
||||
groupForm: NgForm;
|
||||
|
||||
constructor(
|
||||
private translateService: TranslateService,
|
||||
private msgHandler: MessageHandlerService,
|
||||
private operationService: OperationService,
|
||||
private ref: ChangeDetectorRef,
|
||||
private groupService: GroupService,
|
||||
private memberService: MemberService
|
||||
) {}
|
||||
|
||||
ngOnInit() { }
|
||||
|
||||
public get isValid(): boolean {
|
||||
if (this.createGroupMode) {
|
||||
return this.groupForm && this.groupForm.valid;
|
||||
} else {
|
||||
return this.selectedGroups.length > 0;
|
||||
}
|
||||
}
|
||||
public get isDNInvalid(): boolean {
|
||||
if (!this.groupForm) {return false; };
|
||||
let dnControl = this.groupForm.controls['ldap_group_dn'];
|
||||
return dnControl && dnControl.invalid && (dnControl.dirty || dnControl.touched);
|
||||
}
|
||||
|
||||
loadGroups() {
|
||||
this.onLoading = true;
|
||||
this.groupService.getUserGroups().subscribe(groups => {
|
||||
this.groups = groups.filter(group => {
|
||||
if (!group.group_name) {group.group_name = ''; };
|
||||
return group.group_name.includes(this.currentTerm)
|
||||
&& !this.memberList.some(member => member.entity_type === 'g' && member.entity_id === group.id);
|
||||
});
|
||||
this.onLoading = false;
|
||||
this.ref.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
doFilter(name: string) {
|
||||
this.currentTerm = name;
|
||||
this.loadGroups();
|
||||
}
|
||||
|
||||
resetModaldata() {
|
||||
this.group = new UserGroup();
|
||||
this.selectedRole = 1;
|
||||
this.selectedGroups = [];
|
||||
this.groups = [];
|
||||
}
|
||||
|
||||
public open() {
|
||||
this.resetModaldata();
|
||||
this.loadGroups();
|
||||
this.opened = true;
|
||||
this.ref.detectChanges();
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.resetModaldata();
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
onSave() {
|
||||
if (!this.createGroupMode) {
|
||||
this.addGroups();
|
||||
} else {
|
||||
this.createGroupAsMember();
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
addGroups() {
|
||||
let GroupAdders$ = this.selectedGroups.map(group => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.ADD_GROUP';
|
||||
operMessage.data.id = group.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = group.group_name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
return this.memberService
|
||||
.addGroupMember(this.projectId, group, this.selectedRole)
|
||||
.flatMap(response => {
|
||||
return this.translateService.get("BATCH.DELETED_SUCCESS")
|
||||
.flatMap(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
return Observable.of(res);
|
||||
}); })
|
||||
.catch(error => {
|
||||
return this.translateService.get("BATCH.DELETED_FAILURE")
|
||||
.flatMap(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
return Observable.of(res);
|
||||
}); })
|
||||
.catch(error => Observable.of(error.status));
|
||||
});
|
||||
forkJoin(GroupAdders$)
|
||||
.subscribe(results => {
|
||||
if (results.some(code => code < 200 || code > 299)) {
|
||||
this.added.emit(false);
|
||||
} else {
|
||||
this.added.emit(true);
|
||||
}
|
||||
});
|
||||
this.opened = false;
|
||||
}
|
||||
|
||||
createGroupAsMember() {
|
||||
let groupCopy = Object.assign({}, this.group);
|
||||
this.memberService.addGroupMember(this.projectId, groupCopy, this.selectedRole)
|
||||
.subscribe(
|
||||
res => this.added.emit(true),
|
||||
err => {
|
||||
this.msgHandler.handleError(err);
|
||||
this.added.emit(false);
|
||||
}
|
||||
);
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
<clr-modal [(clrModalOpen)]="addMemberOpened" [clrModalStaticBackdrop]="staticBackdrop" [clrModalClosable]="closable">
|
||||
<h3 class="modal-title">{{'MEMBER.NEW_MEMBER' | translate}}</h3>
|
||||
<inline-alert class="modal-title" (confirmEvt)="confirmCancel($event)"></inline-alert>
|
||||
<h3 class="modal-title">{{'MEMBER.NEW_USER' | translate}}</h3>
|
||||
<div class="modal-body">
|
||||
<label>{{ 'MEMBER.NEW_USER_INFO' | translate}}</label>
|
||||
|
||||
<form #memberForm="ngForm">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="member_name" class="col-md-4 form-group-label-override required">{{'MEMBER.NAME' | translate}}</label>
|
||||
<label for="member_name" aria-haspopup="true" role="tooltip" [class.invalid]="!isMemberNameValid"
|
||||
class="tooltip tooltip-validation tooltip-md tooltip-bottom-left" (mouseleave)="leaveInput()">
|
||||
class="tooltip tooltip-validation tooltip-sm tooltip-bottom-left" (mouseleave)="leaveInput()">
|
||||
<input type="text" id="member_name" [(ngModel)]="member.entity_name"
|
||||
name="member_name"
|
||||
size="20"
|
||||
#memberName="ngModel"
|
||||
required
|
||||
size="20"
|
||||
#memberName="ngModel"
|
||||
required
|
||||
(keyup)='handleValidation()' autocomplete="off">
|
||||
<span class="tooltip-content">
|
||||
{{ memberTooltip | translate }}
|
||||
@ -28,15 +29,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="member_role" id="checkrads_project_admin" [value]="1" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="member_role" 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="member_role" id="checkrads_developer" [value]="2" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="member_role" 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="member_role" id="checkrads_guest" [value]="3" [(ngModel)]="member.role_id">
|
||||
<input type="radio" name="member_role" id="checkrads_guest" [value]=3 [(ngModel)]="member.role_id">
|
||||
<label for="checkrads_guest">{{'MEMBER.GUEST' | translate}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -23,4 +23,4 @@
|
||||
color: #262626;
|
||||
background-image: linear-gradient(180deg,#f5f5f5 0,#e8e8e8);
|
||||
background-repeat: repeat-x;
|
||||
}
|
||||
}
|
||||
|
@ -86,19 +86,18 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
private ref: ChangeDetectorRef) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
let hasProjectAdminRole: boolean;
|
||||
if (resolverData) {
|
||||
hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
if (hasProjectAdminRole) {
|
||||
this.userService.getUsers()
|
||||
let resolverData = this.route.snapshot.parent.data;
|
||||
let hasProjectAdminRole: boolean;
|
||||
if (resolverData) {
|
||||
hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
|
||||
}
|
||||
if (hasProjectAdminRole) {
|
||||
this.userService.getUsers()
|
||||
.then(users => {
|
||||
this.userLists = users;
|
||||
});
|
||||
|
||||
this.nameChecker
|
||||
this.nameChecker
|
||||
.debounceTime(500)
|
||||
.distinctUntilChanged()
|
||||
.subscribe((name: string) => {
|
||||
@ -108,17 +107,17 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
if (cont.valid) {
|
||||
this.checkOnGoing = true;
|
||||
this.memberService
|
||||
.listMembers(this.projectId, cont.value).toPromise()
|
||||
.then((members: Member[]) => {
|
||||
if (members.filter(m => { return m.entity_name === cont.value; }).length > 0) {
|
||||
this.isMemberNameValid = false;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_ALREADY_EXISTS';
|
||||
}
|
||||
this.checkOnGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
.listMembers(this.projectId, cont.value).toPromise()
|
||||
.then((members: Member[]) => {
|
||||
if (members.filter(m => { return m.entity_name === cont.value; }).length > 0) {
|
||||
this.isMemberNameValid = false;
|
||||
this.memberTooltip = 'MEMBER.USERNAME_ALREADY_EXISTS';
|
||||
}
|
||||
this.checkOnGoing = false;
|
||||
})
|
||||
.catch(error => {
|
||||
this.checkOnGoing = false;
|
||||
});
|
||||
// username autocomplete
|
||||
if (this.userLists && this.userLists.length) {
|
||||
this.selectUserName = [];
|
||||
@ -129,17 +128,17 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
});
|
||||
let changeTimer = setInterval(() => this.ref.detectChanges(), 200);
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
clearInterval(changeTimer);
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
this.memberTooltip = 'MEMBER.USERNAME_IS_REQUIRED';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -149,12 +148,20 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
onSubmit(): void {
|
||||
if (!this.member.entity_name || this.member.entity_name.length === 0) { return; }
|
||||
this.memberService
|
||||
.addMember(this.projectId, this.member.entity_name, +this.member.role_id)
|
||||
.addUserMember(this.projectId, {username: this.member.entity_name}, +this.member.role_id)
|
||||
.finally(() => {
|
||||
this.addMemberOpened = false;
|
||||
let changeTimer = setInterval(() => this.ref.detectChanges(), 200);
|
||||
setTimeout(() => {
|
||||
clearInterval(changeTimer);
|
||||
}, 2000);
|
||||
}
|
||||
)
|
||||
.subscribe(
|
||||
response => {
|
||||
() => {
|
||||
this.messageHandlerService.showSuccess('MEMBER.ADDED_SUCCESS');
|
||||
this.added.emit(true);
|
||||
this.addMemberOpened = false;
|
||||
// this.addMemberOpened = false;
|
||||
},
|
||||
error => {
|
||||
if (error instanceof Response) {
|
||||
@ -171,19 +178,15 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
if (this.messageHandlerService.isAppLevel(error)) {
|
||||
this.messageHandlerService.handleError(error);
|
||||
this.addMemberOpened = false;
|
||||
// this.addMemberOpened = false;
|
||||
} else {
|
||||
this.translateService
|
||||
.get(errorMessageKey)
|
||||
.subscribe(errorMessage => this.inlineAlert.showInlineError(errorMessage));
|
||||
.subscribe(errorMessage => this.messageHandlerService.handleError(errorMessage));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
setInterval(() => this.ref.markForCheck(), 100);
|
||||
}, 1000);
|
||||
});
|
||||
// this.addMemberOpened = false;
|
||||
}
|
||||
|
||||
selectedName(username: string) {
|
||||
@ -192,12 +195,8 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
if (this.hasChanged) {
|
||||
this.inlineAlert.showInlineConfirmation({ message: 'ALERT.FORM_CHANGE_CONFIRMATION' });
|
||||
} else {
|
||||
this.addMemberOpened = false;
|
||||
this.memberForm.reset();
|
||||
}
|
||||
}
|
||||
|
||||
leaveInput() {
|
||||
@ -212,7 +211,6 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
let memberName = data['member_name'];
|
||||
if (memberName && memberName !== '') {
|
||||
this.hasChanged = true;
|
||||
this.inlineAlert.close();
|
||||
} else {
|
||||
this.hasChanged = false;
|
||||
}
|
||||
@ -220,12 +218,6 @@ export class AddMemberComponent implements AfterViewChecked, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
confirmCancel(confirmed: boolean) {
|
||||
this.addMemberOpened = false;
|
||||
this.inlineAlert.close();
|
||||
this.memberForm.reset();
|
||||
}
|
||||
|
||||
openAddMemberModal(): void {
|
||||
this.currentForm.reset();
|
||||
this.member = new Member();
|
||||
|
@ -14,27 +14,33 @@
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<clr-dg-action-bar>
|
||||
<button class="btn btn-sm btn-secondary" (click)="openAddMemberModal()" [disabled]="!hasProjectAdminRole">
|
||||
<span><clr-icon shape="plus" size="16"></clr-icon> {{'MEMBER.NEW_MEMBER' | translate }}</span>
|
||||
<span><clr-icon shape="plus" size="16"></clr-icon> {{'MEMBER.NEW_USER' | translate }}</span>
|
||||
</button>
|
||||
<clr-dropdown id='member-action' [clrCloseMenuOnItemClick]="false" class="btn btn-sm btn-secondary" clrDropdownTrigger>
|
||||
<button class="btn btn-sm btn-secondary" (click)="openAddGroupModal()" [disabled]="!hasProjectAdminRole">
|
||||
<span><clr-icon shape="plus" size="16"></clr-icon> {{'MEMBER.NEW_GROUP' | translate}}</span>
|
||||
</button>
|
||||
<clr-dropdown id='member-action' [clrCloseMenuOnItemClick]="false" class="btn btn-sm btn-link" clrDropdownTrigger>
|
||||
<span>{{'MEMBER.ACTION' | translate}}<clr-icon shape="caret down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button class="btn btn-sm btn-secondary" (click)="changeRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<label class="dropdown-header">{{'MEMBER.SET_ROLE' | translate}}</label>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 1)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.PROJECT_ADMIN' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 2)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.DEVELOPER' | translate}}</button>
|
||||
<button clrDropdownItem (click)="changeMembersRole(selectedRow, 3)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.GUEST' | translate}}</button>
|
||||
<div class="dropdown-divider"></div>
|
||||
<button clrDropdownItem (click)="openDeleteMembersDialog(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">{{'MEMBER.REMOVE' | translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<button class="btn btn-sm btn-secondary" (click)="deleteMembers(selectedRow)" [disabled]="!(selectedRow.length && hasProjectAdminRole) || onlySelf">
|
||||
<span><clr-icon shape="times" size="16"></clr-icon> {{'MEMBER.REMOVE' | translate}}</span>
|
||||
</button>
|
||||
</clr-dg-action-bar>
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" (clrDgSelectedChange)="SelectedChange()">
|
||||
<clr-datagrid [(clrDgSelected)]="selectedRow" [clrDgLoading]="loading">
|
||||
<clr-dg-column>{{'MEMBER.NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.MEMBER_TYPE'| translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'MEMBER.ROLE' | translate}}</clr-dg-column>
|
||||
<clr-dg-row *clrDgItems="let m of members" [clrDgItem]="m">
|
||||
<clr-dg-cell>{{m.entity_name}}</clr-dg-cell>
|
||||
<clr-dg-cell>{{member_type_toString( m.entity_type) | translate}}</clr-dg-cell>
|
||||
<clr-dg-cell>
|
||||
<span>{{roleInfo[m.role_id] | translate}}</span>
|
||||
<span *ngIf="ChangeRoleOngoing(m.id)" class="spinner spinner-inline"></span>
|
||||
<span *ngIf="!ChangeRoleOngoing(m.id)">{{roleInfo[m.role_id] | translate}}</span>
|
||||
</clr-dg-cell>
|
||||
</clr-dg-row>
|
||||
<clr-dg-footer>
|
||||
@ -45,4 +51,5 @@
|
||||
</clr-datagrid>
|
||||
</div>
|
||||
<add-member [projectId]="projectId" [memberList]="members" (added)="addedMember($event)"></add-member>
|
||||
<add-group [projectId]="projectId" [memberList]="members" (added)="addedGroup($event)"></add-group>
|
||||
</div>
|
@ -22,4 +22,10 @@
|
||||
z-index: 100;
|
||||
right: 35px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
clr-datagrid {
|
||||
::ng-deep clr-checkbox {
|
||||
position: inherit;
|
||||
}
|
||||
}
|
@ -14,31 +14,27 @@
|
||||
import { Component, OnInit, ViewChild, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { SessionUser } from "../../shared/session-user";
|
||||
import { Member } from "./member";
|
||||
import { MemberService } from "./member.service";
|
||||
|
||||
import { AddMemberComponent } from "./add-member/add-member.component";
|
||||
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from "../../shared/shared.const";
|
||||
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { SessionService } from "../../shared/session.service";
|
||||
|
||||
import { RoleInfo } from "../../shared/shared.const";
|
||||
|
||||
import "rxjs/add/operator/switchMap";
|
||||
import "rxjs/add/operator/catch";
|
||||
import "rxjs/add/operator/map";
|
||||
import "rxjs/add/observable/throw";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
|
||||
import { Project } from "../../project/project";
|
||||
import {TranslateService} from "@ngx-translate/core";
|
||||
import {operateChanges, OperateInfo, OperationService, OperationState} from "harbor-ui";
|
||||
|
||||
import { MessageHandlerService } from "../../shared/message-handler/message-handler.service";
|
||||
import { ConfirmationTargets, ConfirmationState, ConfirmationButtons } from "../../shared/shared.const";
|
||||
import { ConfirmationDialogService } from "../../shared/confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationMessage } from "../../shared/confirmation-dialog/confirmation-message";
|
||||
import { SessionService } from "../../shared/session.service";
|
||||
import { RoleInfo } from "../../shared/shared.const";
|
||||
import { Project } from "../../project/project";
|
||||
import { Member } from "./member";
|
||||
import { SessionUser } from "../../shared/session-user";
|
||||
import { AddGroupComponent } from './add-group/add-group.component';
|
||||
import { MemberService } from "./member.service";
|
||||
import { AddMemberComponent } from "./add-member/add-member.component";
|
||||
|
||||
@Component({
|
||||
templateUrl: "member.component.html",
|
||||
styleUrls: ["./member.component.scss"],
|
||||
@ -51,17 +47,25 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
roleInfo = RoleInfo;
|
||||
delSub: Subscription;
|
||||
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
currentUser: SessionUser;
|
||||
hasProjectAdminRole: boolean;
|
||||
|
||||
batchOps = 'delete';
|
||||
searchMember: string;
|
||||
selectedRow: Member[] = [];
|
||||
roleNum: number;
|
||||
isDelete = false;
|
||||
isChangeRole = false;
|
||||
loading = false;
|
||||
|
||||
isChangingRole = false;
|
||||
batchChangeRoleInfos = {};
|
||||
|
||||
@ViewChild(AddMemberComponent)
|
||||
addMemberComponent: AddMemberComponent;
|
||||
|
||||
@ViewChild(AddGroupComponent)
|
||||
addGroupComponent: AddGroupComponent;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -78,11 +82,8 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
if (message &&
|
||||
message.state === ConfirmationState.CONFIRMED &&
|
||||
message.source === ConfirmationTargets.PROJECT_MEMBER) {
|
||||
if (this.isDelete) {
|
||||
this.deleteMem(message.data);
|
||||
}
|
||||
if (this.isChangeRole) {
|
||||
this.changeOpe(message.data);
|
||||
if (this.batchOps === 'delete') {
|
||||
this.deleteMembers(message.data);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -90,22 +91,6 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
}
|
||||
|
||||
retrieve(projectId: number, username: string) {
|
||||
this.selectedRow = [];
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.members = response;
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
},
|
||||
error => {
|
||||
this.router.navigate(["/harbor", "projects"]);
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.delSub) {
|
||||
this.delSub.unsubscribe();
|
||||
@ -124,147 +109,6 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
openAddMemberModal() {
|
||||
this.addMemberComponent.openAddMemberModal();
|
||||
}
|
||||
|
||||
addedMember($event: any) {
|
||||
this.searchMember = "";
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
get onlySelf(): boolean {
|
||||
if (this.selectedRow.length === 1 && this.selectedRow[0].entity_id === this.currentUser.user_id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
changeRole(m: Member[], roleId: number) {
|
||||
if (m && m.length) {
|
||||
this.isDelete = false;
|
||||
this.isChangeRole = true;
|
||||
this.roleNum = roleId;
|
||||
this.changeOpe(m);
|
||||
}
|
||||
}
|
||||
|
||||
changeOpe(members: Member[]) {
|
||||
if (members && members.length) {
|
||||
let promiseList: any[] = [];
|
||||
members.forEach(member => {
|
||||
promiseList.push(this.changeOperate(this.projectId, this.roleNum, member));
|
||||
});
|
||||
|
||||
Promise.all(promiseList).then(num => {
|
||||
this.retrieve(this.projectId, "");
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
changeOperate(projectId: number, roleId: number, member: Member) {
|
||||
// init operation info
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.SWITCH_ROLE';
|
||||
operMessage.data.id = member.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = member.entity_name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
|
||||
if (member.entity_id === this.currentUser.user_id) {
|
||||
this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return this.memberService
|
||||
.changeMemberRole(projectId, member.id, roleId)
|
||||
.then(
|
||||
response => {
|
||||
this.translate.get("BATCH.SWITCH_SUCCESS").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.translate.get("BATCH.SWITCH_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
deleteMembers(m: Member[]) {
|
||||
this.isDelete = true;
|
||||
this.isChangeRole = false;
|
||||
let nameArr: string[] = [];
|
||||
if (m && m.length) {
|
||||
m.forEach(data => {
|
||||
nameArr.push(data.entity_name);
|
||||
});
|
||||
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
"MEMBER.DELETION_TITLE",
|
||||
"MEMBER.DELETION_SUMMARY",
|
||||
nameArr.join(","),
|
||||
m,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.OperateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
deleteMem(members: Member[]) {
|
||||
if (members && members.length) {
|
||||
let promiseLists: any[] = [];
|
||||
members.forEach(member => {
|
||||
promiseLists.push(this.delOperate(this.projectId, member));
|
||||
});
|
||||
|
||||
Promise.all(promiseLists).then(item => {
|
||||
this.selectedRow = [];
|
||||
this.retrieve(this.projectId, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
delOperate(projectId: number, member: Member) {
|
||||
// init operation info
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_MEMBER';
|
||||
operMessage.data.id = member.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = member.entity_name;
|
||||
this.operationService.publishInfo(operMessage);
|
||||
|
||||
if (member.entity_id === this.currentUser.user_id) {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.memberService
|
||||
.deleteMember(projectId, member.id)
|
||||
.then(
|
||||
response => {
|
||||
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
});
|
||||
},
|
||||
error => {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
SelectedChange(): void {
|
||||
// this.forceRefreshView(5000);
|
||||
}
|
||||
|
||||
doSearch(searchMember: string) {
|
||||
this.searchMember = searchMember;
|
||||
this.retrieve(this.projectId, this.searchMember);
|
||||
@ -273,4 +117,154 @@ export class MemberComponent implements OnInit, OnDestroy {
|
||||
refresh() {
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
retrieve(projectId: number, username: string) {
|
||||
this.loading = true;
|
||||
this.selectedRow = [];
|
||||
this.memberService
|
||||
.listMembers(projectId, username)
|
||||
.finally(() => this.loading = false)
|
||||
.subscribe(
|
||||
response => {
|
||||
this.members = response;
|
||||
let hnd = setInterval(() => this.ref.markForCheck(), 100);
|
||||
setTimeout(() => clearInterval(hnd), 1000);
|
||||
},
|
||||
error => {
|
||||
this.router.navigate(["/harbor", "projects"]);
|
||||
this.messageHandlerService.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
get onlySelf(): boolean {
|
||||
if (this.selectedRow.length === 1 &&
|
||||
this.selectedRow[0].entity_type === 'u' &&
|
||||
this.selectedRow[0].entity_id === this.currentUser.user_id) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
member_type_toString(user_type: string) {
|
||||
if (user_type === 'u') {
|
||||
return 'MEMBER.USER_TYPE';
|
||||
} else {
|
||||
return 'MEMBER.GROUP_TYPE';
|
||||
}
|
||||
}
|
||||
|
||||
// Add member
|
||||
openAddMemberModal() {
|
||||
this.addMemberComponent.openAddMemberModal();
|
||||
}
|
||||
|
||||
addedMember(result: boolean) {
|
||||
this.searchMember = "";
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
// Add group
|
||||
openAddGroupModal() {
|
||||
this.addGroupComponent.open();
|
||||
}
|
||||
addedGroup(result: boolean) {
|
||||
this.searchMember = "";
|
||||
this.retrieve(this.projectId, "");
|
||||
}
|
||||
|
||||
changeMembersRole(members: Member[], roleId: number) {
|
||||
if (!members) {
|
||||
return;
|
||||
}
|
||||
|
||||
let changeOperate = (projectId: number, member: Member, ) => {
|
||||
return this.memberService
|
||||
.changeMemberRole(projectId, member.id, roleId)
|
||||
.then( () => this.batchChangeRoleInfos[member.id] = 'done')
|
||||
.catch(error => this.messageHandlerService.handleError(error + ": " + member.entity_name));
|
||||
};
|
||||
|
||||
// Preparation for members role change
|
||||
this.batchChangeRoleInfos = {};
|
||||
let RoleChangePromises: Promise<any>[] = [];
|
||||
members.forEach(member => {
|
||||
if (member.entity_type === 'u' && member.entity_id === this.currentUser.user_id) {
|
||||
return;
|
||||
}
|
||||
this.batchChangeRoleInfos[member.id] = 'pending';
|
||||
RoleChangePromises.push(changeOperate(this.projectId, member));
|
||||
});
|
||||
|
||||
Promise.all(RoleChangePromises).then(() => {
|
||||
this.retrieve(this.projectId, "");
|
||||
});
|
||||
}
|
||||
|
||||
ChangeRoleOngoing(entity_id: number) {
|
||||
return this.batchChangeRoleInfos[entity_id] === 'pending';
|
||||
}
|
||||
|
||||
// Delete members
|
||||
openDeleteMembersDialog(members: Member[]) {
|
||||
this.batchOps = 'delete';
|
||||
let nameArr: string[] = [];
|
||||
if (members && members.length) {
|
||||
members.forEach(data => {
|
||||
nameArr.push(data.entity_name);
|
||||
});
|
||||
let deletionMessage = new ConfirmationMessage(
|
||||
"MEMBER.DELETION_TITLE",
|
||||
"MEMBER.DELETION_SUMMARY",
|
||||
nameArr.join(","),
|
||||
members,
|
||||
ConfirmationTargets.PROJECT_MEMBER,
|
||||
ConfirmationButtons.DELETE_CANCEL
|
||||
);
|
||||
this.OperateDialogService.openComfirmDialog(deletionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
deleteMembers(members: Member[]) {
|
||||
if (!members) { return; }
|
||||
let memberDeletingPromises: Promise<any>[] = [];
|
||||
|
||||
// Function to delete specific member
|
||||
let deleteMember = (projectId: number, member: Member) => {
|
||||
let operMessage = new OperateInfo();
|
||||
operMessage.name = 'OPERATION.DELETE_MEMBER';
|
||||
operMessage.data.id = member.id;
|
||||
operMessage.state = OperationState.progressing;
|
||||
operMessage.data.name = member.entity_name;
|
||||
|
||||
this.operationService.publishInfo(operMessage);
|
||||
if (member.entity_type === 'u' && member.entity_id === this.currentUser.user_id) {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.memberService
|
||||
.deleteMember(projectId, member.id)
|
||||
.then(response => {
|
||||
this.translate.get("BATCH.DELETED_SUCCESS").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.success);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
this.translate.get("BATCH.DELETED_FAILURE").subscribe(res => {
|
||||
operateChanges(operMessage, OperationState.failure, res);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Deleting member then wating for results
|
||||
members.forEach(member => memberDeletingPromises.push(deleteMember(this.projectId, member)));
|
||||
|
||||
Promise.all(memberDeletingPromises).then(() => {
|
||||
this.selectedRow = [];
|
||||
this.batchOps = 'idle';
|
||||
this.retrieve(this.projectId, "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -19,24 +19,48 @@ import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/observable/throw';
|
||||
|
||||
import { Member } from './member';
|
||||
import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../../shared/shared.utils";
|
||||
import { User } from '../../user/user';
|
||||
import { Member } from './member';
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class MemberService {
|
||||
|
||||
constructor(private http: Http) {}
|
||||
|
||||
listMembers(projectId: number, username: string): Observable<Member[]> {
|
||||
listMembers(projectId: number, entity_name: string): Observable<Member[]> {
|
||||
return this.http
|
||||
.get(`/api/projects/${projectId}/members?entityname=${username}`, HTTP_GET_OPTIONS)
|
||||
.get(`/api/projects/${projectId}/members?entityname=${entity_name}`, HTTP_GET_OPTIONS)
|
||||
.map(response => response.json() as Member[])
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
addMember(projectId: number, username: string, roleId: number): Observable<any> {
|
||||
addUserMember(projectId: number, user: User, roleId: number): Observable<any> {
|
||||
let member_user = {};
|
||||
if (user.user_id) {
|
||||
member_user = {user_id: user.user_id};
|
||||
} else if (user.username) {
|
||||
member_user = {username: user.username};
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
return this.http.post(
|
||||
`/api/projects/${projectId}/members`,
|
||||
{
|
||||
role_id: roleId,
|
||||
member_user: member_user
|
||||
},
|
||||
HTTP_JSON_OPTIONS)
|
||||
.map(response => response.status)
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
|
||||
addGroupMember(projectId: number, group: any, roleId: number): Observable<any> {
|
||||
return this.http
|
||||
.post(`/api/projects/${projectId}/members`, { role_id: roleId, member_user: {username: username} }, HTTP_JSON_OPTIONS)
|
||||
.post(`/api/projects/${projectId}/members`,
|
||||
{ role_id: roleId, member_group: group},
|
||||
HTTP_JSON_OPTIONS)
|
||||
.map(response => response.status)
|
||||
.catch(error => Observable.throw(error));
|
||||
}
|
||||
@ -48,9 +72,9 @@ export class MemberService {
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
deleteMember(projectId: number, userId: number): Promise<any> {
|
||||
deleteMember(projectId: number, memberId: number): Promise<any> {
|
||||
return this.http
|
||||
.delete(`/api/projects/${projectId}/members/${userId}`).toPromise()
|
||||
.delete(`/api/projects/${projectId}/members/${memberId}`).toPromise()
|
||||
.then(response => response.status)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
@ -26,13 +26,14 @@ import { ListProjectComponent } from './list-project/list-project.component';
|
||||
import { ProjectDetailComponent } from './project-detail/project-detail.component';
|
||||
import { MemberComponent } from './member/member.component';
|
||||
import { AddMemberComponent } from './member/add-member/add-member.component';
|
||||
import { AddGroupComponent } from './member/add-group/add-group.component';
|
||||
|
||||
import { ProjectService } from './project.service';
|
||||
import { MemberService } from './member/member.service';
|
||||
import { ProjectRoutingResolver } from './project-routing-resolver.service';
|
||||
|
||||
import { TargetExistsValidatorDirective } from '../shared/target-exists-directive';
|
||||
import {ProjectLabelComponent} from "../project/project-label/project-label.component";
|
||||
import { ProjectLabelComponent } from "../project/project-label/project-label.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -50,7 +51,8 @@ import {ProjectLabelComponent} from "../project/project-label/project-label.comp
|
||||
MemberComponent,
|
||||
AddMemberComponent,
|
||||
TargetExistsValidatorDirective,
|
||||
ProjectLabelComponent
|
||||
ProjectLabelComponent,
|
||||
AddGroupComponent
|
||||
],
|
||||
exports: [ProjectComponent, ListProjectComponent],
|
||||
providers: [ProjectRoutingResolver, ProjectService, MemberService]
|
||||
|
@ -78,3 +78,8 @@ export const enum ConfirmationButtons {
|
||||
export const ProjectTypes = { 0: 'PROJECT.ALL_PROJECTS', 1: 'PROJECT.PRIVATE_PROJECTS', 2: 'PROJECT.PUBLIC_PROJECTS' };
|
||||
export const RoleInfo = { 1: 'MEMBER.PROJECT_ADMIN', 2: 'MEMBER.DEVELOPER', 3: 'MEMBER.GUEST' };
|
||||
export const RoleMapping = { 'projectAdmin': 'MEMBER.PROJECT_ADMIN', 'developer': 'MEMBER.DEVELOPER', 'guest': 'MEMBER.GUEST' };
|
||||
export const ProjectRoles = [
|
||||
{ id: 1, value: "MEMBER.PROJECT_ADMIN" },
|
||||
{ id: 2, value: "MEMBER.DEVELOPER" },
|
||||
{ id: 3, value: "MEMBER.GUEST" }
|
||||
];
|
||||
|
@ -14,6 +14,7 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { TranslateModule } from "@ngx-translate/core";
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { CookieService } from "ngx-cookie";
|
||||
import {
|
||||
IServiceConfig,
|
||||
@ -65,6 +66,8 @@ const uiLibConfig: IServiceConfig = {
|
||||
CoreModule,
|
||||
TranslateModule,
|
||||
RouterModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HarborLibraryModule.forRoot({
|
||||
config: { provide: SERVICE_CONFIG, useValue: uiLibConfig },
|
||||
errorHandler: { provide: ErrorHandler, useClass: MessageHandlerService }
|
||||
@ -103,7 +106,9 @@ const uiLibConfig: IServiceConfig = {
|
||||
ListProjectROComponent,
|
||||
ListRepositoryROComponent,
|
||||
GaugeComponent,
|
||||
DateValidatorDirective
|
||||
DateValidatorDirective,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
providers: [
|
||||
SessionService,
|
||||
|
@ -13,8 +13,13 @@
|
||||
<clr-dg-action-bar>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="addNewUser()" [disabled]="!canCreateUser"><clr-icon shape="plus" size="16"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="set-admin" [disabled]="!ifSameRole" (click)="changeAdminRole()" ><clr-icon shape="wrench" size="16"></clr-icon> {{ISADMNISTRATOR | translate}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="changePwd" [hidden]="!canCreateUser" [disabled]="!(selectedRow.length==1)" (click)="openChangePwdModal()" ><clr-icon shape="edit" size="16"></clr-icon> {{'RESET_PWD.TITLE' | translate | uppercase}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteUsers(selectedRow)" [disabled]="!selectedRow.length || onlySelf || !canCreateUser"><clr-icon shape="times" size="16"></clr-icon> {{'USER.DEL_ACTION' | translate}}</button>
|
||||
<clr-dropdown id='member-action' [clrCloseMenuOnItemClick]="false" class="btn btn-sm btn-link" clrDropdownTrigger>
|
||||
<span>{{'BUTTON.ACTIONS' | translate}}<clr-icon shape="caret down"></clr-icon></span>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<button type="button" class="btn btn-sm btn-secondary" id="changePwd" [hidden]="!canCreateUser" [disabled]="!(selectedRow.length==1)" (click)="openChangePwdModal()" ><clr-icon shape="edit" size="16"></clr-icon> {{'RESET_PWD.TITLE' | translate | uppercase}}</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" (click)="deleteUsers(selectedRow)" [disabled]="!selectedRow.length || onlySelf || !canCreateUser"><clr-icon shape="times" size="16"></clr-icon> {{'USER.DEL_ACTION' | translate}}</button>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</clr-dg-action-bar>
|
||||
<clr-dg-column>{{'USER.COLUMN_NAME' | translate}}</clr-dg-column>
|
||||
<clr-dg-column>{{'USER.COLUMN_ADMIN' | translate}}</clr-dg-column>
|
||||
|
@ -15,10 +15,12 @@ import { Injectable } from '@angular/core';
|
||||
import { Http } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { User } from './user';
|
||||
import {HTTP_JSON_OPTIONS, HTTP_GET_OPTIONS} from "../shared/shared.utils";
|
||||
import { User, LDAPUser } from './user';
|
||||
import LDAPUsertoUser from './user';
|
||||
|
||||
const userMgmtEndpoint = '/api/users';
|
||||
const ldapUserEndpoint = '/api/ldap/users';
|
||||
|
||||
/**
|
||||
* Define related methods to handle account and session corresponding things
|
||||
@ -81,11 +83,33 @@ export class UserService {
|
||||
}
|
||||
|
||||
return this.http.put(userMgmtEndpoint + '/' + uid + '/password',
|
||||
{"old_password": newPassword, 'new_password': confirmPwd}, HTTP_JSON_OPTIONS)
|
||||
{
|
||||
"old_password": newPassword,
|
||||
'new_password': confirmPwd
|
||||
},
|
||||
HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(response => response)
|
||||
.catch(error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
// Get User from LDAP
|
||||
getLDAPUsers(username: string): Promise<User[]> {
|
||||
return this.http.get(`${ldapUserEndpoint}/search?username=${username}`, HTTP_GET_OPTIONS)
|
||||
.toPromise()
|
||||
.then(response => {
|
||||
let ldapUser = response.json() as LDAPUser[] || [];
|
||||
return ldapUser.map(u => LDAPUsertoUser(u));
|
||||
})
|
||||
.catch( error => this.handleError(error));
|
||||
}
|
||||
|
||||
importLDAPUsers(usernames: string[]): Promise<any> {
|
||||
return this.http.post(`${ldapUserEndpoint}/import`, JSON.stringify({ldap_uid_list: usernames}), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(() => null )
|
||||
.catch(err => this.handleError(err));
|
||||
}
|
||||
}
|
||||
|
@ -18,12 +18,31 @@
|
||||
* @class User
|
||||
*/
|
||||
export class User {
|
||||
user_id: number;
|
||||
user_id?: number;
|
||||
username?: string;
|
||||
realname?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
comment?: string;
|
||||
deleted?: boolean;
|
||||
role_name?: string;
|
||||
role_id?: number;
|
||||
has_admin_role?: boolean;
|
||||
reset_uuid?: string;
|
||||
creation_time?: string;
|
||||
update_time?: string;
|
||||
}
|
||||
export interface LDAPUser {
|
||||
ldap_username: string;
|
||||
ldap_realname: string;
|
||||
ldap_email: string;
|
||||
}
|
||||
function LDAPUsertoUser(ldapU: LDAPUser): User {
|
||||
let user = new User();
|
||||
user.user_id = 0;
|
||||
user.username = ldapU.ldap_username;
|
||||
user.realname = ldapU.ldap_realname;
|
||||
user.email = ldapU.ldap_email;
|
||||
return user;
|
||||
}
|
||||
export default LDAPUsertoUser;
|
||||
|
@ -35,7 +35,8 @@
|
||||
"COPY": "COPY",
|
||||
"EDIT": "EDIT",
|
||||
"SWITCH": "SWITCH",
|
||||
"REPLICATE": "REPLICATE"
|
||||
"REPLICATE": "REPLICATE",
|
||||
"ACTIONS": "Actions"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
@ -112,6 +113,7 @@
|
||||
"SYSTEM_MGMT": {
|
||||
"NAME": "Administration",
|
||||
"USER": "Users",
|
||||
"GROUP": "Groups",
|
||||
"REGISTRY": "Registries",
|
||||
"REPLICATION": "Replications",
|
||||
"CONFIG": "Configuration"
|
||||
@ -200,6 +202,7 @@
|
||||
"AUTOSCAN_POLICY": "Automatically scan images when they are pushed to the project registry."
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_USER": "New User",
|
||||
"NEW_MEMBER": "New Member",
|
||||
"MEMBER": "Member",
|
||||
"NAME": "Name",
|
||||
@ -211,6 +214,25 @@
|
||||
"DELETE": "Delete",
|
||||
"ITEMS": "items",
|
||||
"ACTIONS": "Actions",
|
||||
"USER": " User",
|
||||
"USERS": "Users",
|
||||
"EMAIL": "Email",
|
||||
"ADD_USER": "Add User",
|
||||
"NEW_USER_INFO": "Add an user to be a member of this project with specified role",
|
||||
"NEW_GROUP": "New Group",
|
||||
"IMPORT_GROUP": "Import New LDAP Group",
|
||||
"NEW_GROUP_INFO": "Import a new LDAP group or select one or more existing groups to add with the specific role.",
|
||||
"ADD_GROUP_SELECT": "Add a group as a project member",
|
||||
"CREATE_GROUP_SELECT": "Import a new group from LDAP",
|
||||
"LDAP_SEARCH_DN": "LDAP Group DN",
|
||||
"LDAP_SEARCH_NAME": "Name",
|
||||
"LDAP_GROUP": "Group",
|
||||
"LDAP_GROUPS": "Groups",
|
||||
"LDAP_PROPERTY": "Property",
|
||||
"ACTION": "ACTION",
|
||||
"MEMBER_TYPE": "Member Type",
|
||||
"GROUP_TYPE": "Group",
|
||||
"USER_TYPE": "User",
|
||||
"USERNAME_IS_REQUIRED": "Username is required",
|
||||
"USERNAME_DOES_NOT_EXISTS": "Username does not exist.",
|
||||
"USERNAME_ALREADY_EXISTS": "Username already exists.",
|
||||
@ -224,8 +246,25 @@
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?",
|
||||
"ACTION": "SET ROLE",
|
||||
"REMOVE": "REMOVE"
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
"GROUPS": "Groups",
|
||||
"IMPORT_LDAP_GROUP": "Import LDAP Group",
|
||||
"ADD": "New Group",
|
||||
"EDIT": "Edit",
|
||||
"DELETE": "Delete",
|
||||
"NAME": "Name",
|
||||
"TYPE": "Type",
|
||||
"DN": "DN",
|
||||
"GROUP_DN": "Ldap Group DN",
|
||||
"PROPERTY": "Property",
|
||||
"REG_TIME": "Registration Time",
|
||||
"ADD_GROUP_SUCCESS": "Add group success",
|
||||
"ADD_GROUP_FAILURE": "Add group failure",
|
||||
"LDAP_TYPE": "LDAP"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Username",
|
||||
@ -507,7 +546,16 @@
|
||||
"FILTER": "LDAP Filter",
|
||||
"UID": "LDAP UID",
|
||||
"SCOPE": "LDAP Scope",
|
||||
"VERIFY_CERT": "LDAP Verify Cert"
|
||||
"VERIFY_CERT": "LDAP Verify Cert",
|
||||
"LDAP_GROUP_BASE_DN": "LDAP Group Base DN",
|
||||
"LDAP_GROUP_BASE_DN_INFO": "The base DN of your LDAP group.",
|
||||
"LDAP_GROUP_FILTER": "LDAP Group Filter",
|
||||
"LDAP_GROUP_FILTER_INFO": "The filter of your LDAP group",
|
||||
"LDAP_GROUP_GID": "LDAP Group GID",
|
||||
"LDAP_GROUP_GID_INFO": "The Group gid of your LDAP group",
|
||||
"GROUP_SCOPE": "LDAP Group Scope",
|
||||
"GROUP_SCOPE_INFO": "The scope of your LDAP Group"
|
||||
|
||||
},
|
||||
"UAA": {
|
||||
"ENDPOINT": "UAA Endpoint",
|
||||
@ -673,8 +721,11 @@
|
||||
"DELETE_USER": "Delete user",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete member",
|
||||
"DELETE_MEMBER": "Delete user member",
|
||||
"DELETE_GROUP": "Delete group member",
|
||||
"SWITCH_ROLE": "Switch role",
|
||||
"ADD_GROUP": "Add group member",
|
||||
"ADD_USER": "Add user member",
|
||||
"DELETE_LABEL": "Delete label",
|
||||
"REPLICATION": "Replication",
|
||||
"DAY_AGO": " day(s) ago",
|
||||
|
@ -35,7 +35,9 @@
|
||||
"COPY": "COPY",
|
||||
"EDIT": "EDITAR",
|
||||
"SWITCH": "SWITCH",
|
||||
"REPLICATE": "REPLICATE"
|
||||
"REPLICATE": "REPLICATE",
|
||||
"ACTIONS": "Actions"
|
||||
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "Deleted successfully",
|
||||
@ -83,6 +85,7 @@
|
||||
"COMMENT": "Comentarios",
|
||||
"PASSWORD": "Contraseña",
|
||||
"SAVE_SUCCESS": "Perfil de usuario guardado satisfactoriamente.",
|
||||
"ADMIN_RENAME_BUTTON": "Change username",
|
||||
"ADMIN_RENAME_TIP": "Select the button in order to change the username to \"admin@harbor.local\". This operation can not be undone.",
|
||||
"RENAME_SUCCESS": "Rename success!",
|
||||
"RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone."
|
||||
@ -112,6 +115,7 @@
|
||||
"NAME": "Administración",
|
||||
"USER": "Usuarios",
|
||||
"REGISTRY": "Registries",
|
||||
"GROUP": "Groups",
|
||||
"REPLICATION": "Replicacións",
|
||||
"CONFIG": "Configuración"
|
||||
},
|
||||
@ -199,6 +203,7 @@
|
||||
"AUTOSCAN_POLICY": "Escanee automáticamente las imágenes cuando son enviadas al registro del proyecto."
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_USER": "New User",
|
||||
"NEW_MEMBER": "Nuevo miembro",
|
||||
"MEMBER": "Miembro",
|
||||
"NAME": "Nombre",
|
||||
@ -210,6 +215,25 @@
|
||||
"DELETE": "Eliminar",
|
||||
"ITEMS": "elementos",
|
||||
"ACTIONS": "Acciones",
|
||||
"USER": " User",
|
||||
"USERS": "Users",
|
||||
"EMAIL": "Email",
|
||||
"ADD_USER": "Add User",
|
||||
"NEW_USER_INFO": "Add an user to be a member of this project with specified role",
|
||||
"NEW_GROUP": "New Group",
|
||||
"IMPORT_GROUP": "Import New LDAP Group",
|
||||
"NEW_GROUP_INFO": "Import a new LDAP group or select one or more existing groups to add with the specific role.",
|
||||
"ADD_GROUP_SELECT": "Add a group as a project member",
|
||||
"CREATE_GROUP_SELECT": "Import a new group from LDAP",
|
||||
"LDAP_SEARCH_DN": "LDAP Group DN",
|
||||
"LDAP_SEARCH_NAME": "Name",
|
||||
"LDAP_GROUP": "Group",
|
||||
"LDAP_GROUPS": "Groups",
|
||||
"LDAP_PROPERTY": "Property",
|
||||
"ACTION": "ACTION",
|
||||
"MEMBER_TYPE": "Member Type",
|
||||
"GROUP_TYPE": "Group",
|
||||
"USER_TYPE": "User",
|
||||
"USERNAME_IS_REQUIRED": "El nombre de usuario es obligatorio",
|
||||
"USERNAME_DOES_NOT_EXISTS": "Ese nombre de usuario no existe.",
|
||||
"USERNAME_ALREADY_EXISTS": "Ese nombre de usuario ya existe.",
|
||||
@ -223,8 +247,24 @@
|
||||
"OF": "of",
|
||||
"SWITCH_TITLE": "Confirm project members switch",
|
||||
"SWITCH_SUMMARY": "Do you want to switch project members {{param}}?",
|
||||
"ACTION": "SET ROLE",
|
||||
"REMOVE": "REMOVE"
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "Group",
|
||||
"GROUPS": "Groups",
|
||||
"IMPORT_LDAP_GROUP": "Import LDAP Group",
|
||||
"ADD": "Add",
|
||||
"EDIT": "Edit",
|
||||
"DELETE": "Delete",
|
||||
"TYPE": "Type",
|
||||
"DN": "DN",
|
||||
"GROUP_DN": "Ldap Group DN",
|
||||
"PROPERTY": "Property",
|
||||
"REG_TIME": "Registration Time",
|
||||
"ADD_GROUP_SUCCESS": "Add group success",
|
||||
"ADD_GROUP_FAILURE": "Add group failure",
|
||||
"LDAP_TYPE": "LDAP"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Nombre de usuario",
|
||||
@ -506,7 +546,15 @@
|
||||
"FILTER": "LDAP Filtro",
|
||||
"UID": "LDAP UID",
|
||||
"SCOPE": "LDAP Ámbito",
|
||||
"VERIFY_CERT": "LDAP Verify Cert"
|
||||
"VERIFY_CERT": "LDAP Verify Cert",
|
||||
"LDAP_GROUP_BASE_DN": "LDAP Group Base DN",
|
||||
"LDAP_GROUP_BASE_DN_INFO": "The base DN of your LDAP group.",
|
||||
"LDAP_GROUP_FILTER": "LDAP Group Filter",
|
||||
"LDAP_GROUP_FILTER_INFO": "The filter of your LDAP group",
|
||||
"LDAP_GROUP_GID": "LDAP Group GID",
|
||||
"LDAP_GROUP_GID_INFO": "The Group gid of your LDAP group",
|
||||
"GROUP_SCOPE": "LDAP Group Scope",
|
||||
"GROUP_SCOPE_INFO": "The scope of your LDAP Group"
|
||||
},
|
||||
"UAA": {
|
||||
"ENDPOINT": "UAA Endpoint",
|
||||
@ -672,8 +720,11 @@
|
||||
"DELETE_USER": "Delete user",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete member",
|
||||
"DELETE_MEMBER": "Delete user member",
|
||||
"DELETE_GROUP": "Delete group member",
|
||||
"SWITCH_ROLE": "Switch role",
|
||||
"ADD_GROUP": "Add group member",
|
||||
"ADD_USER": "Add user member",
|
||||
"DELETE_LABEL": "Delete label",
|
||||
"REPLICATION": "Replication",
|
||||
"DAY_AGO": " day(s) ago",
|
||||
|
@ -32,7 +32,8 @@
|
||||
"YES": "OUI",
|
||||
"NO": "NON",
|
||||
"NEGATIVE": "NEGATIF",
|
||||
"COPY": "COPIER"
|
||||
"COPY": "COPIER",
|
||||
"ACTIONS": "Actions"
|
||||
},
|
||||
"TOOLTIP": {
|
||||
"EMAIL": "L'email doit être une adresse email valide comme name@example.com.",
|
||||
@ -70,6 +71,7 @@
|
||||
"COMMENT": "Commentaires",
|
||||
"PASSWORD": "Mot de passe",
|
||||
"SAVE_SUCCESS": "Profil utilisateur sauvegardé avec succès.",
|
||||
"ADMIN_RENAME_BUTTON": "Change username",
|
||||
"ADMIN_RENAME_TIP": "Select the button in order to change the username to \"admin@harbor.local\". This operation can not be undone.",
|
||||
"RENAME_SUCCESS": "Rename success!",
|
||||
"RENAME_CONFIRM_INFO": "Warning, changing the name to admin@harbor.local can not be undone."
|
||||
@ -98,6 +100,7 @@
|
||||
"SYSTEM_MGMT": {
|
||||
"NAME": "Administration",
|
||||
"USER": "Utilisateurs",
|
||||
"GROUP": "Groups",
|
||||
"REPLICATION": "Réplication",
|
||||
"CONFIG": "Configuration"
|
||||
},
|
||||
@ -185,10 +188,30 @@
|
||||
"AUTOSCAN_POLICY": "Analyser automatiquement les images lorsqu'elles sont envoyées au registre du projet."
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_USER": "New User",
|
||||
"NEW_MEMBER": "Nouveau Membre",
|
||||
"MEMBER": "Membre",
|
||||
"NAME": "Nom",
|
||||
"EMAIL": "Email",
|
||||
"ROLE": "Rôle",
|
||||
"ADD_USER": "Add User",
|
||||
"NEW_USER_INFO": "Add an user to be a member of this project with specified role",
|
||||
"NEW_GROUP": "New Group",
|
||||
"IMPORT_GROUP": "Import New LDAP Group",
|
||||
"NEW_GROUP_INFO": "Import a new LDAP group or select one or more existing groups to add with the specific role.",
|
||||
"ADD_GROUP_SELECT": "Add a group as a project member",
|
||||
"CREATE_GROUP_SELECT": "Import a new group from LDAP",
|
||||
"LDAP_SEARCH_DN": "LDAP Group DN",
|
||||
"LDAP_SEARCH_NAME": "Name",
|
||||
"LDAP_GROUP": "Group",
|
||||
"LDAP_GROUPS": "Groups",
|
||||
"LDAP_PROPERTY": "Property",
|
||||
"ACTION": "ACTION",
|
||||
"USER": " User",
|
||||
"USERS": "Users",
|
||||
"MEMBER_TYPE": "Member Type",
|
||||
"GROUP_TYPE": "Group",
|
||||
"USER_TYPE": "User",
|
||||
"SYS_ADMIN": "System Admin",
|
||||
"PROJECT_ADMIN": "Project Admin",
|
||||
"DEVELOPER": "Développeur",
|
||||
@ -206,7 +229,26 @@
|
||||
"ADDED_SUCCESS": "Membre ajouté avec succès.",
|
||||
"DELETED_SUCCESS": "Membre supprimé avec succès.",
|
||||
"SWITCHED_SUCCESS": "Rôle du membre changé avec succés.",
|
||||
"OF": "de"
|
||||
"OF": "de",
|
||||
"SET_ROLE": "SET ROLE",
|
||||
"REMOVE": "Remove"
|
||||
},
|
||||
"GROUP": {
|
||||
"Group": "Group",
|
||||
"GROUPS": "Groups",
|
||||
"IMPORT_LDAP_GROUP": "Import LDAP Group",
|
||||
"ADD": "Add",
|
||||
"EDIT": "Edit",
|
||||
"DELETE": "Delete",
|
||||
"NAME": "Name",
|
||||
"TYPE": "Type",
|
||||
"DN": "DN",
|
||||
"GROUP_DN": "Ldap Group DN",
|
||||
"PROPERTY": "Property",
|
||||
"REG_TIME": "Registration Time",
|
||||
"ADD_GROUP_SUCCESS": "Add group success",
|
||||
"ADD_GROUP_FAILURE": "Add group failure",
|
||||
"LDAP_TYPE": "LDAP"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "Nom d'utilisateur",
|
||||
@ -474,7 +516,16 @@
|
||||
"BASE_DN": "Base DN LDAP",
|
||||
"FILTER": "Filtre LDAP",
|
||||
"UID": "UID LDAP",
|
||||
"SCOPE": "Scope LDAP"
|
||||
"SCOPE": "Scope LDAP",
|
||||
"VERIFY_CERT": "LDAP Verify Cert",
|
||||
"LDAP_GROUP_BASE_DN": "LDAP Group Base DN",
|
||||
"LDAP_GROUP_BASE_DN_INFO": "The base DN of your LDAP group.",
|
||||
"LDAP_GROUP_FILTER": "LDAP Group Filter",
|
||||
"LDAP_GROUP_FILTER_INFO": "The filter of your LDAP group",
|
||||
"LDAP_GROUP_GID": "LDAP Group GID",
|
||||
"LDAP_GROUP_GID_INFO": "The Group gid of your LDAP group",
|
||||
"GROUP_SCOPE": "LDAP Group Scope",
|
||||
"GROUP_SCOPE_INFO": "The scope of your LDAP Group"
|
||||
},
|
||||
"SCANNING": {
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Déclenchement d'analyse globale avec succès !",
|
||||
@ -634,7 +685,10 @@
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REPLICATION": "Delete replication",
|
||||
"DELETE_MEMBER": "Delete member",
|
||||
"DELETE_GROUP": "Delete member group",
|
||||
"SWITCH_ROLE": "Switch role",
|
||||
"ADD_GROUP": "Add group member",
|
||||
"ADD_USER": "Add user member",
|
||||
"DELETE_LABEL": "Delete label",
|
||||
"REPLICATION": "Replication",
|
||||
"DAY_AGO": " day(s) ago",
|
||||
|
@ -35,7 +35,8 @@
|
||||
"COPY": "拷贝",
|
||||
"EDIT": "编辑",
|
||||
"SWITCH": "切换",
|
||||
"REPLICATE": "复制"
|
||||
"REPLICATE": "复制",
|
||||
"ACTIONS": "操作"
|
||||
},
|
||||
"BATCH": {
|
||||
"DELETED_SUCCESS": "删除成功",
|
||||
@ -85,7 +86,8 @@
|
||||
"SAVE_SUCCESS": "成功保存用户设置。",
|
||||
"ADMIN_RENAME_TIP": "单击将用户名改为 \"admin@harbor.local\", 注意这个操作是无法撤销的",
|
||||
"RENAME_SUCCESS": "用户名更改成功!",
|
||||
"RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 你确定更改吗·?"
|
||||
"ADMIN_RENAME_BUTTON": "更改用户名",
|
||||
"RENAME_CONFIRM_INFO": "更改用户名为admin@harbor.local是无法撤销的, 你确定更改吗?"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "修改密码",
|
||||
@ -111,6 +113,7 @@
|
||||
"SYSTEM_MGMT": {
|
||||
"NAME": "系统管理",
|
||||
"USER": "用户管理",
|
||||
"GROUP": "组管理",
|
||||
"REGISTRY": "仓库管理",
|
||||
"REPLICATION": "复制管理",
|
||||
"CONFIG": "配置管理"
|
||||
@ -199,9 +202,11 @@
|
||||
"AUTOSCAN_POLICY": "当镜像上传后,自动进行扫描"
|
||||
},
|
||||
"MEMBER": {
|
||||
"NEW_USER": "新用户",
|
||||
"NEW_MEMBER": "新建成员",
|
||||
"MEMBER": "成员",
|
||||
"NAME": "姓名",
|
||||
"EMAIL": "邮箱",
|
||||
"ROLE": "角色",
|
||||
"SYS_ADMIN": "系统管理员",
|
||||
"PROJECT_ADMIN": "项目管理员",
|
||||
@ -210,6 +215,24 @@
|
||||
"DELETE": "删除",
|
||||
"ITEMS": "条记录",
|
||||
"ACTIONS": "操作",
|
||||
"USER": "用户",
|
||||
"USERS": "用户",
|
||||
"ADD_USER": "添加用户",
|
||||
"NEW_USER_INFO": "添加用户到此项目中并给予相对应的角色",
|
||||
"NEW_GROUP": "新增组",
|
||||
"IMPORT_GROUP": "导入LDAP组",
|
||||
"NEW_GROUP_INFO": "导入LDAP组,同时以选择的角色添加到项目成员列表",
|
||||
"ADD_GROUP_SELECT": "添加组到项目成员",
|
||||
"CREATE_GROUP_SELECT": "从LDAP导入新的组",
|
||||
"LDAP_SEARCH_DN": "LDAP Group DN",
|
||||
"LDAP_SEARCH_NAME": "名称",
|
||||
"LDAP_GROUP": "组",
|
||||
"LDAP_GROUPS": "组",
|
||||
"LDAP_PROPERTY": "属性",
|
||||
"ACTION": "其他操作",
|
||||
"MEMBER_TYPE": "成员类型",
|
||||
"GROUP_TYPE": "组",
|
||||
"USER_TYPE": "用户",
|
||||
"USERNAME_IS_REQUIRED": "用户名为必填项。",
|
||||
"USERNAME_DOES_NOT_EXISTS": "用户名不存在。",
|
||||
"USERNAME_ALREADY_EXISTS": "用户名已存在。",
|
||||
@ -223,9 +246,26 @@
|
||||
"OF": "共计",
|
||||
"SWITCH_TITLE": "切换项目成员确认",
|
||||
"SWITCH_SUMMARY": "你确认切换项目成员 {{param}}??",
|
||||
"ACTION": "设置角色",
|
||||
"SET_ROLE": "设置角色",
|
||||
"REMOVE": "移除成员"
|
||||
},
|
||||
"GROUP": {
|
||||
"GROUP": "组",
|
||||
"GROUPS": "组",
|
||||
"IMPORT_LDAP_GROUP": "导入LDAP组",
|
||||
"ADD": "新增",
|
||||
"EDIT": "编辑",
|
||||
"DELETE": "删除",
|
||||
"NAME": "名称",
|
||||
"TYPE": "类型",
|
||||
"DN": "DN",
|
||||
"PROPERTY": "属性",
|
||||
"GROUP_DN": "LDAP 组域",
|
||||
"REG_TIME": "注册时间",
|
||||
"ADD_GROUP_SUCCESS": "添加组成功",
|
||||
"ADD_GROUP_FAILURE": "添加组失败",
|
||||
"LDAP_TYPE": "LDAP"
|
||||
},
|
||||
"AUDIT_LOG": {
|
||||
"USERNAME": "用户名",
|
||||
"REPOSITORY_NAME": "镜像名称",
|
||||
@ -504,9 +544,17 @@
|
||||
"SEARCH_PWD": "LDAP搜索密码",
|
||||
"BASE_DN": "LDAP基础DN",
|
||||
"FILTER": "LDAP过滤器",
|
||||
"UID": "LDAP用户UID的属性",
|
||||
"UID": "LDAP用户UID",
|
||||
"SCOPE": "LDAP搜索范围",
|
||||
"VERIFY_CERT": "LDAP 检查证书"
|
||||
"VERIFY_CERT": "LDAP 检查证书",
|
||||
"LDAP_GROUP_BASE_DN": "LDAP组基础DN",
|
||||
"LDAP_GROUP_BASE_DN_INFO": "LDAP组的基础DN",
|
||||
"LDAP_GROUP_FILTER": "LDAP组过滤器",
|
||||
"LDAP_GROUP_FILTER_INFO": "LDAP组的过滤器",
|
||||
"LDAP_GROUP_GID": "LDAP组GID",
|
||||
"LDAP_GROUP_GID_INFO": "LDAP组的GID",
|
||||
"GROUP_SCOPE": "LDAP组搜索范围",
|
||||
"GROUP_SCOPE_INFO": "搜索范围"
|
||||
},
|
||||
"UAA": {
|
||||
"ENDPOINT": "UAA Endpoint",
|
||||
@ -670,10 +718,13 @@
|
||||
"DELETE_REPO": "删除仓库",
|
||||
"DELETE_TAG": "删除镜像标签",
|
||||
"DELETE_USER": "删除用户",
|
||||
"DELETE_REGISTRY": "Delete registry",
|
||||
"DELETE_REGISTRY": "删除Registry",
|
||||
"DELETE_REPLICATION": "删除复制",
|
||||
"DELETE_MEMBER": "删除成员",
|
||||
"DELETE_MEMBER": "删除用户成员",
|
||||
"DELETE_GROUP": "删除组成员",
|
||||
"SWITCH_ROLE": "切换角色",
|
||||
"ADD_GROUP": "添加组成员",
|
||||
"ADD_USER": "添加用户成员",
|
||||
"DELETE_LABEL": "删除标签",
|
||||
"REPLICATION": "复制",
|
||||
"DAY_AGO": "天前",
|
||||
|
@ -19,7 +19,7 @@ Library OperatingSystem
|
||||
|
||||
*** Variables ***
|
||||
${HARBOR_VERSION} v1.1.1
|
||||
${CLAIR_BUILDER} 1.4.1
|
||||
${CLAIR_BUILDER} 1.4.0
|
||||
${GOLANG_VERSION} 1.9.2
|
||||
|
||||
*** Keywords ***
|
||||
|
Loading…
Reference in New Issue
Block a user