mirror of
https://github.com/goharbor/harbor
synced 2025-04-16 20:42:22 +00:00
Revamp Copy Pull Command (#21155)
* update copy pull command in artifact tags page * This commit moves "Copy Pull Command" button inside the table * and add a separate column for better usability Signed-off-by: bupd <bupdprasanth@gmail.com> * add user preferences component * This Commit adds Preferences in navbar * Updates the navbar Signed-off-by: bupd <bupdprasanth@gmail.com> * add container runtime to preference settings Signed-off-by: bupd <bupdprasanth@gmail.com> * fix: lint & rebase Signed-off-by: bupd <bupdprasanth@gmail.com> * update pull cmd for tag Signed-off-by: bupd <bupdprasanth@gmail.com> * update copy pull command for digest Signed-off-by: bupd <bupdprasanth@gmail.com> * fix tests Signed-off-by: bupd <bupdprasanth@gmail.com> * add toast message on copy pull command Signed-off-by: bupd <bupdprasanth@gmail.com> * add top copy button Signed-off-by: bupd <bupdprasanth@gmail.com> * add test for preference settings component Signed-off-by: bupd <bupdprasanth@gmail.com> * fix lint Signed-off-by: bupd <bupdprasanth@gmail.com> * update comments and nits Signed-off-by: bupd <bupdprasanth@gmail.com> * update pull cmd prefix name * Updates title of preference settings * Updates container runtime to pull cmd prefix Signed-off-by: bupd <bupdprasanth@gmail.com> * extend copy pull command with custom prefix * This commit adds custom as dropdown option * add custom_runtime localstorage variable for the pull prefix * fix artifact list tab styles * align copy icon in artifact tag list tab Signed-off-by: bupd <bupdprasanth@gmail.com> * minor fix * allow only lowercase alphabets Signed-off-by: bupd <bupdprasanth@gmail.com> * remove unused copy pull command in i18n * removes unused in copy_pull_command in i18n in all languages Signed-off-by: bupd <bupdprasanth@gmail.com> * remove commented line Signed-off-by: Prasanth Baskar <bupdprasanth@gmail.com> * fix es-es-lang Signed-off-by: bupd <bupdprasanth@gmail.com> --------- Signed-off-by: bupd <bupdprasanth@gmail.com> Signed-off-by: Prasanth Baskar <bupdprasanth@gmail.com> Co-authored-by: Vadim Bauer <vb@container-registry.com>
This commit is contained in:
parent
b9528d8deb
commit
8419bb6beb
|
@ -201,6 +201,7 @@
|
|||
</div>
|
||||
</clr-main-container>
|
||||
<account-settings-modal></account-settings-modal>
|
||||
<preference-settings></preference-settings>
|
||||
<password-setting></password-setting>
|
||||
<global-confirmation-dialog></global-confirmation-dialog>
|
||||
<about-dialog></about-dialog>
|
||||
|
|
|
@ -18,70 +18,80 @@ import { SkinableConfig } from '../../services/skinable-config.service';
|
|||
import { AppConfigService } from '../../services/app-config.service';
|
||||
import { ErrorHandler } from '../../shared/units/error-handler';
|
||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||
import { PreferenceSettingsComponent } from '../preference-settings/preference-settings.component';
|
||||
import { InlineAlertComponent } from '../../shared/components/inline-alert/inline-alert.component';
|
||||
import { ScannerService } from '../../../../ng-swagger-gen/services/scanner.service';
|
||||
import { UserService } from '../../../../ng-swagger-gen/services/user.service';
|
||||
|
||||
// Mocks
|
||||
const fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
},
|
||||
};
|
||||
|
||||
const fakeSearchTriggerService = {
|
||||
searchTriggerChan$: of('null'),
|
||||
searchCloseChan$: of(null),
|
||||
};
|
||||
|
||||
const mockMessageHandlerService = null;
|
||||
|
||||
const mockPasswordSettingService = null;
|
||||
|
||||
const mockSkinableConfig = {
|
||||
getSkinConfig: function () {
|
||||
return {
|
||||
headerBgColor: {
|
||||
darkMode: '',
|
||||
lightMode: '',
|
||||
},
|
||||
loginBgImg: '',
|
||||
loginTitle: '',
|
||||
product: {
|
||||
name: '',
|
||||
logo: '',
|
||||
introduction: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
},
|
||||
isHttpAuthMode: function () {
|
||||
return false;
|
||||
},
|
||||
isOidcMode: function () {
|
||||
return false;
|
||||
},
|
||||
getConfig: function () {
|
||||
return {
|
||||
with_trivy: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
const fakedUserService = {
|
||||
getCurrentUserInfo() {
|
||||
return of({});
|
||||
},
|
||||
setCliSecret() {
|
||||
return of(null);
|
||||
},
|
||||
};
|
||||
|
||||
describe('HarborShellComponent', () => {
|
||||
let component: HarborShellComponent;
|
||||
let fixture: ComponentFixture<HarborShellComponent>;
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return { has_admin_role: true };
|
||||
},
|
||||
};
|
||||
let fakeSearchTriggerService = {
|
||||
searchTriggerChan$: of('null'),
|
||||
searchCloseChan$: of(null),
|
||||
};
|
||||
let mockMessageHandlerService = null;
|
||||
let mockPasswordSettingService = null;
|
||||
let mockSkinableConfig = {
|
||||
getSkinConfig: function () {
|
||||
return {
|
||||
headerBgColor: {
|
||||
darkMode: '',
|
||||
lightMode: '',
|
||||
},
|
||||
loginBgImg: '',
|
||||
loginTitle: '',
|
||||
product: {
|
||||
name: '',
|
||||
logo: '',
|
||||
introduction: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
let fakeAppConfigService = {
|
||||
isLdapMode: function () {
|
||||
return true;
|
||||
},
|
||||
isHttpAuthMode: function () {
|
||||
return false;
|
||||
},
|
||||
isOidcMode: function () {
|
||||
return false;
|
||||
},
|
||||
getConfig: function () {
|
||||
return {
|
||||
with_trivy: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
const fakedUserService = {
|
||||
getCurrentUserInfo() {
|
||||
return of({});
|
||||
},
|
||||
setCliSecret() {
|
||||
return of(null);
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
TranslateModule.forRoot(),
|
||||
TranslateModule.forRoot(), // Ensure TranslateModule is imported
|
||||
ClarityModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
|
@ -89,6 +99,7 @@ describe('HarborShellComponent', () => {
|
|||
declarations: [
|
||||
HarborShellComponent,
|
||||
AccountSettingsModalComponent,
|
||||
PreferenceSettingsComponent,
|
||||
PasswordSettingComponent,
|
||||
AboutDialogComponent,
|
||||
InlineAlertComponent,
|
||||
|
@ -105,10 +116,7 @@ describe('HarborShellComponent', () => {
|
|||
provide: MessageHandlerService,
|
||||
useValue: mockMessageHandlerService,
|
||||
},
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: fakedUserService,
|
||||
},
|
||||
{ provide: UserService, useValue: fakedUserService },
|
||||
{
|
||||
provide: PasswordSettingService,
|
||||
useValue: mockPasswordSettingService,
|
||||
|
@ -128,6 +136,9 @@ describe('HarborShellComponent', () => {
|
|||
).componentInstance;
|
||||
component.accountSettingsModal.inlineAlert =
|
||||
TestBed.createComponent(InlineAlertComponent).componentInstance;
|
||||
component.prefSetting = TestBed.createComponent(
|
||||
PreferenceSettingsComponent
|
||||
).componentInstance;
|
||||
component.pwdSetting = TestBed.createComponent(
|
||||
PasswordSettingComponent
|
||||
).componentInstance;
|
||||
|
@ -139,6 +150,7 @@ describe('HarborShellComponent', () => {
|
|||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should open users profile', async () => {
|
||||
component.openModal({
|
||||
modalName: modalEvents.USER_PROFILE,
|
||||
|
@ -149,7 +161,18 @@ describe('HarborShellComponent', () => {
|
|||
fixture.nativeElement.querySelector('#account_settings_username');
|
||||
expect(accountSettingsUsernameInput).toBeTruthy();
|
||||
});
|
||||
it('should open users changPwd', async () => {
|
||||
|
||||
it('should open users preferences', async () => {
|
||||
component.openModal({
|
||||
modalName: modalEvents.PREFERENCES,
|
||||
modalFlag: false,
|
||||
});
|
||||
await fixture.whenStable();
|
||||
const dropdowns = fixture.nativeElement.querySelector('.dropdowns');
|
||||
expect(dropdowns).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should open users changePwd', async () => {
|
||||
component.openModal({
|
||||
modalName: modalEvents.CHANGE_PWD,
|
||||
modalFlag: false,
|
||||
|
@ -159,6 +182,7 @@ describe('HarborShellComponent', () => {
|
|||
fixture.nativeElement.querySelector('#oldPassword');
|
||||
expect(oldPasswordInput).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should open users about-dialog', async () => {
|
||||
component.openModal({ modalName: modalEvents.ABOUT, modalFlag: false });
|
||||
await fixture.whenStable();
|
||||
|
|
|
@ -37,6 +37,7 @@ import { THEME_ARRAY, ThemeInterface } from '../../services/theme';
|
|||
import { clone } from '../../shared/units/utils';
|
||||
import { ThemeService } from '../../services/theme.service';
|
||||
import { AccountSettingsModalComponent } from '../account-settings/account-settings-modal.component';
|
||||
import { PreferenceSettingsComponent } from '../preference-settings/preference-settings.component';
|
||||
import {
|
||||
EventService,
|
||||
HarborEvent,
|
||||
|
@ -53,6 +54,9 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
@ViewChild(AccountSettingsModalComponent)
|
||||
accountSettingsModal: AccountSettingsModalComponent;
|
||||
|
||||
@ViewChild(PreferenceSettingsComponent)
|
||||
prefSetting: PreferenceSettingsComponent;
|
||||
|
||||
@ViewChild(PasswordSettingComponent)
|
||||
pwdSetting: PasswordSettingComponent;
|
||||
|
||||
|
@ -176,6 +180,9 @@ export class HarborShellComponent implements OnInit, OnDestroy {
|
|||
case modalEvents.USER_PROFILE:
|
||||
this.accountSettingsModal.open();
|
||||
break;
|
||||
case modalEvents.PREFERENCES:
|
||||
this.prefSetting.open();
|
||||
break;
|
||||
case modalEvents.CHANGE_PWD:
|
||||
this.pwdSetting.open();
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
// limitations under the License.
|
||||
export const modalEvents = {
|
||||
USER_PROFILE: 'USER_PROFILE',
|
||||
PREFERENCES: 'PREFERENCES',
|
||||
CHANGE_PWD: 'CHANGE_PWD',
|
||||
ABOUT: 'ABOUT',
|
||||
};
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
<clr-modal
|
||||
[(clrModalOpen)]="opened"
|
||||
[clrModalClosable]="false"
|
||||
[clrModalStaticBackdrop]="false">
|
||||
<h3 class="modal-title">{{ 'CHANGE_PREF.TITLE' | translate }}</h3>
|
||||
<div class="modal-body body-format dialog-body">
|
||||
<div class="dropdowns content-container">
|
||||
<div class="content-area centered-content-area">
|
||||
<div class="clr-control-label">
|
||||
{{ 'CHANGE_PREF.LANGUAGE' | translate }}
|
||||
</div>
|
||||
<clr-dropdown class="dropdown-lang dropdown bottom-left">
|
||||
<button class="nav-icon nav-icon-width" clrDropdownToggle>
|
||||
<clr-icon shape="world" class="icon-left"></clr-icon>
|
||||
<span class="currentLocale">{{ currentLang }}</span>
|
||||
<clr-icon size="10" shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<a
|
||||
*ngFor="let lang of guiLanguages"
|
||||
href="javascript:void(0)"
|
||||
clrDropdownItem
|
||||
(click)="switchLanguage(lang[0])"
|
||||
[class.lang-selected]="matchLang(lang[0])"
|
||||
>{{ lang[1][0] }}</a
|
||||
>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="content-area centered-content-area">
|
||||
<div class="clr-control-label">
|
||||
{{ 'CHANGE_PREF.DATE_TIME_FORMAT' | translate }}
|
||||
</div>
|
||||
<clr-dropdown class="dropdown bottom-left">
|
||||
<button class="nav-icon nav-icon-width" clrDropdownToggle>
|
||||
<clr-icon shape="date" class="icon-left"></clr-icon>
|
||||
<span class="currentLocale">{{
|
||||
currentDatetimeRendering | translate
|
||||
}}</span>
|
||||
<clr-icon size="10" shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<a
|
||||
*ngFor="let rendering of guiDatetimeRenderings"
|
||||
href="javascript:void(0)"
|
||||
clrDropdownItem
|
||||
(click)="switchDatetimeRendering(rendering[0])"
|
||||
[class.locale-selected]="
|
||||
matchDatetimeRendering(rendering[0])
|
||||
"
|
||||
>{{ rendering[1] | translate }}</a
|
||||
>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
|
||||
<div class="content-area centered-content-area">
|
||||
<div class="clr-control-label">
|
||||
{{ 'CHANGE_PREF.PULL_CMD_PREFIX' | translate }}
|
||||
</div>
|
||||
<clr-dropdown class="dropdown-lang dropdown bottom-left">
|
||||
<button class="nav-icon nav-icon-width" clrDropdownToggle>
|
||||
<clr-icon shape="bundle" class="icon-left"></clr-icon>
|
||||
<span class="currentLocale">{{ currentRuntime }}</span>
|
||||
<clr-icon size="10" shape="caret down"></clr-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<clr-dropdown>
|
||||
<button clrDropdownTrigger>custom</button>
|
||||
<clr-dropdown-menu *clrIfOpen>
|
||||
<form
|
||||
#customruntimeForm="ngForm"
|
||||
class="clr-form">
|
||||
<div class="clr-form-control">
|
||||
<label
|
||||
for="customPrefix"
|
||||
class="clr-control-label"
|
||||
>Custom</label
|
||||
>
|
||||
<div class="clr-control-container">
|
||||
<div class="clr-input-wrapper">
|
||||
<input
|
||||
type="text"
|
||||
id="customPrefix"
|
||||
name="customPrefix"
|
||||
placeholder="Enter custom prefix"
|
||||
[(ngModel)]="customRuntime"
|
||||
#customPrefix="ngModel"
|
||||
[ngClass]="{
|
||||
'is-invalid':
|
||||
customPrefix.invalid &&
|
||||
customPrefix.touched
|
||||
}"
|
||||
pattern="^[a-z]+$"
|
||||
minlength="2"
|
||||
class="clr-input" />
|
||||
<cds-icon
|
||||
class="clr-validate-icon"
|
||||
shape="exclamation-circle"></cds-icon>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="
|
||||
customPrefix.invalid &&
|
||||
customPrefix.touched
|
||||
">
|
||||
<div
|
||||
*ngIf="customPrefix.errors?.['required']">
|
||||
Prefix is required
|
||||
</div>
|
||||
<div
|
||||
*ngIf="customPrefix.errors?.['minlength']">
|
||||
Prefix must be at least 2
|
||||
characters
|
||||
</div>
|
||||
<div
|
||||
*ngIf="customPrefix.errors?.['pattern']">
|
||||
Prefix must only contain
|
||||
lowercase alphabets (a-z)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
clrDropdownItem
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
id="ok-btn"
|
||||
[disabled]="!isValid"
|
||||
(click)="addCustomRuntime()">
|
||||
{{ 'BUTTON.OK' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<ng-container *ngFor="let runtime of guiRuntimes">
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
clrDropdownItem
|
||||
(click)="switchRuntime(runtime[0])"
|
||||
[class.lang-selected]="matchRuntime(runtime[0])"
|
||||
>{{ runtime[1] }}</a
|
||||
>
|
||||
</ng-container>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer margin-left-override">
|
||||
<button type="button" class="btn btn-primary" (click)="close()">
|
||||
{{ 'BUTTON.CLOSE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</clr-modal>
|
|
@ -0,0 +1,32 @@
|
|||
.locale-selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-icon-width {
|
||||
width: auto !important;
|
||||
padding-left: 18px !important;
|
||||
|
||||
.icon-left {
|
||||
left: -8px;
|
||||
}
|
||||
/* stylelint-disable */
|
||||
.currentLocale {
|
||||
padding-right: 40px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.centered-content-area {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.dropdowns {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: start;
|
||||
gap: 20px;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import { PreferenceSettingsComponent } from './preference-settings.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SharedTestingModule } from 'src/app/shared/shared.module';
|
||||
|
||||
describe('PreferenceSettingsComponent', () => {
|
||||
let component: PreferenceSettingsComponent;
|
||||
let fixture: ComponentFixture<PreferenceSettingsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [PreferenceSettingsComponent],
|
||||
imports: [SharedTestingModule],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PreferenceSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright Project Harbor Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import {
|
||||
getContainerRuntime,
|
||||
getCustomContainerRuntime,
|
||||
getDatetimeRendering,
|
||||
} from 'src/app/shared/units/shared.utils';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
import { forkJoin, Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ClrCommonStrings } from '@clr/angular/utils/i18n/common-strings.interface';
|
||||
import { ClrCommonStringsService } from '@clr/angular';
|
||||
import {
|
||||
CUSTOM_RUNTIME_LOCALSTORAGE_KEY,
|
||||
DATETIME_RENDERINGS,
|
||||
DatetimeRendering,
|
||||
DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY,
|
||||
DEFAULT_LANG_LOCALSTORAGE_KEY,
|
||||
DEFAULT_RUNTIME_LOCALSTORAGE_KEY,
|
||||
DefaultDatetimeRendering,
|
||||
DeFaultLang,
|
||||
DeFaultRuntime,
|
||||
LANGUAGES,
|
||||
RUNTIMES,
|
||||
stringsForClarity,
|
||||
SupportedLanguage,
|
||||
SupportedRuntime,
|
||||
} from '../../shared/entities/shared.const';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { InlineAlertComponent } from 'src/app/shared/components/inline-alert/inline-alert.component';
|
||||
|
||||
@Component({
|
||||
selector: 'preference-settings',
|
||||
templateUrl: 'preference-settings.component.html',
|
||||
styleUrls: ['preference-settings.component.scss'],
|
||||
})
|
||||
export class PreferenceSettingsComponent implements OnInit {
|
||||
readonly guiLanguages = Object.entries(LANGUAGES);
|
||||
readonly guiRuntimes = Object.entries(RUNTIMES).filter(
|
||||
([_, value]) => value !== RUNTIMES.custom
|
||||
);
|
||||
readonly guiDatetimeRenderings = Object.entries(DATETIME_RENDERINGS);
|
||||
selectedLang: SupportedLanguage = DeFaultLang;
|
||||
selectedRuntime: SupportedRuntime = DeFaultRuntime;
|
||||
selectedDatetimeRendering: DatetimeRendering = DefaultDatetimeRendering;
|
||||
opened: boolean = false;
|
||||
error: any = null;
|
||||
customRuntime: string = '';
|
||||
|
||||
@ViewChild('customruntimeForm', { static: false })
|
||||
customRuntimeForm: NgForm;
|
||||
@ViewChild(InlineAlertComponent, { static: true })
|
||||
inlineAlert: InlineAlertComponent;
|
||||
|
||||
constructor(
|
||||
private translate: TranslateService,
|
||||
private commonStrings: ClrCommonStringsService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.selectedLang = this.translate.currentLang as SupportedLanguage;
|
||||
if (this.selectedLang) {
|
||||
registerLocaleData(
|
||||
LANGUAGES[this.selectedLang][1],
|
||||
this.selectedLang
|
||||
);
|
||||
this.translateClarityComponents();
|
||||
}
|
||||
this.selectedDatetimeRendering = getDatetimeRendering();
|
||||
this.selectedRuntime = getContainerRuntime();
|
||||
this.customRuntime = getCustomContainerRuntime();
|
||||
}
|
||||
|
||||
// Check If form is valid
|
||||
public get isValid(): boolean {
|
||||
const customPrefixControl =
|
||||
this.customRuntimeForm?.form.get('customPrefix');
|
||||
|
||||
return (
|
||||
customPrefixControl?.valid &&
|
||||
customPrefixControl?.value?.trim() !== '' &&
|
||||
this.error === null
|
||||
);
|
||||
}
|
||||
|
||||
addCustomRuntime() {
|
||||
if (this.customRuntime.trim()) {
|
||||
const customRuntimeValue = this.customRuntime.trim();
|
||||
this.switchRuntime('custom');
|
||||
this.switchCustomRuntime(customRuntimeValue);
|
||||
}
|
||||
}
|
||||
|
||||
//Internationalization for Clarity components, refer to https://clarity.design/documentation/internationalization
|
||||
translateClarityComponents() {
|
||||
const translatedObservables: Observable<string | any>[] = [];
|
||||
const translatedStringsForClarity: Partial<ClrCommonStrings> = {};
|
||||
for (let key in stringsForClarity) {
|
||||
translatedObservables.push(
|
||||
this.translate.get(stringsForClarity[key]).pipe(
|
||||
map(res => {
|
||||
return [key, res];
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
forkJoin(translatedObservables).subscribe(res => {
|
||||
if (res?.length) {
|
||||
res.forEach(item => {
|
||||
translatedStringsForClarity[item[0]] = item[1];
|
||||
});
|
||||
this.commonStrings.localize(translatedStringsForClarity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public get currentRuntime(): string {
|
||||
if (this.selectedRuntime) {
|
||||
return RUNTIMES[this.selectedRuntime] as string;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public get currentLang(): string {
|
||||
if (this.selectedLang) {
|
||||
return LANGUAGES[this.selectedLang][0] as string;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public get currentDatetimeRendering(): string {
|
||||
return DATETIME_RENDERINGS[this.selectedDatetimeRendering];
|
||||
}
|
||||
|
||||
matchLang(lang: SupportedLanguage): boolean {
|
||||
return lang === this.selectedLang;
|
||||
}
|
||||
|
||||
matchRuntime(runtime: SupportedRuntime): boolean {
|
||||
return runtime === this.selectedRuntime;
|
||||
}
|
||||
|
||||
matchDatetimeRendering(datetime: DatetimeRendering): boolean {
|
||||
return datetime === this.selectedDatetimeRendering;
|
||||
}
|
||||
|
||||
// Switch languages
|
||||
switchLanguage(lang: SupportedLanguage): void {
|
||||
this.selectedLang = lang;
|
||||
localStorage.setItem(DEFAULT_LANG_LOCALSTORAGE_KEY, lang);
|
||||
// due to the bug(https://github.com/ngx-translate/core/issues/1258) of translate module
|
||||
// have to reload
|
||||
this.translate.use(lang).subscribe(() => window.location.reload());
|
||||
}
|
||||
|
||||
switchRuntime(runtime: SupportedRuntime): void {
|
||||
this.selectedRuntime = runtime;
|
||||
localStorage.setItem(DEFAULT_RUNTIME_LOCALSTORAGE_KEY, runtime);
|
||||
}
|
||||
|
||||
switchCustomRuntime(runtime: SupportedRuntime): void {
|
||||
localStorage.setItem(CUSTOM_RUNTIME_LOCALSTORAGE_KEY, runtime);
|
||||
}
|
||||
|
||||
switchDatetimeRendering(datetime: DatetimeRendering): void {
|
||||
this.selectedDatetimeRendering = datetime;
|
||||
localStorage.setItem(
|
||||
DEFAULT_DATETIME_RENDERING_LOCALSTORAGE_KEY,
|
||||
datetime
|
||||
);
|
||||
// have to reload,as HarborDatetimePipe is pure pipe
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
this.opened = true;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
}
|
||||
}
|
|
@ -184,8 +184,9 @@
|
|||
[registryUrl]="registryUrl"
|
||||
[projectName]="projectName"
|
||||
[repoName]="repoName"
|
||||
[selectedRow]="selectedRow"
|
||||
[isTopModel]="true"
|
||||
class="mr-1"></app-pull-command>
|
||||
|
||||
<app-artifact-filter
|
||||
[withDivider]="true"
|
||||
(filterEvent)="filterEvent($event)"
|
||||
|
@ -196,7 +197,7 @@
|
|||
</div>
|
||||
</clr-dg-action-bar>
|
||||
|
||||
<clr-dg-column class="flex-max-width" [clrDgSortBy]="'digest'"
|
||||
<clr-dg-column [clrDgSortBy]="'digest'"
|
||||
><ng-template
|
||||
[clrDgHideableColumn]="{ hidden: hiddenArray[0] }">
|
||||
{{ 'REPOSITORY.ARTIFACTS_COUNT' | translate }}
|
||||
|
@ -259,7 +260,7 @@
|
|||
<clr-dg-row
|
||||
*ngFor="let artifact of artifactList; let i = index"
|
||||
[clrDgItem]="artifact">
|
||||
<clr-dg-cell class="flex-max-width truncated">
|
||||
<clr-dg-cell>
|
||||
<div class="cell white-normal">
|
||||
<div
|
||||
class="artifact-icon clr-display-inline-block"
|
||||
|
@ -279,6 +280,7 @@
|
|||
{{ artifact.digest | slice : 0 : 15 }}</a
|
||||
>
|
||||
<clr-tooltip
|
||||
class="fix-width"
|
||||
*ngIf="
|
||||
artifact?.references &&
|
||||
artifact?.references?.length
|
||||
|
@ -302,6 +304,12 @@
|
|||
{{ 'REPOSITORY.ARTIFACT_TOOTIP' | translate }}
|
||||
</clr-tooltip-content>
|
||||
</clr-tooltip>
|
||||
<app-pull-command
|
||||
[registryUrl]="registryUrl"
|
||||
[projectName]="projectName"
|
||||
[repoName]="repoName"
|
||||
[artifact]="artifact"
|
||||
class="fix-width"></app-pull-command>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell *ngIf="depth">
|
||||
|
|
|
@ -11,11 +11,16 @@
|
|||
height: 0;
|
||||
}
|
||||
|
||||
.fix-width {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.truncated {
|
||||
width: 100px;
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -198,6 +203,8 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.signpost-item {
|
||||
|
|
|
@ -103,6 +103,7 @@ export class ArtifactListTabComponent implements OnInit, OnDestroy {
|
|||
repoName: string;
|
||||
registryUrl: string;
|
||||
artifactList: ArtifactFront[] = [];
|
||||
artifact: ArtifactFront;
|
||||
availableTime = AVAILABLE_TIME;
|
||||
inprogress: boolean;
|
||||
pullComparator: Comparator<Artifact> = new CustomComparator<Artifact>(
|
||||
|
|
|
@ -1,131 +1,68 @@
|
|||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="mr-1" *ngIf="!isTagMode">
|
||||
<button
|
||||
[disabled]="
|
||||
selectedRow?.length !== 1 || !hasPullCommand(selectedRow[0])
|
||||
<div *ngIf="isTopModel" class="form-group">
|
||||
<hbr-copy-input
|
||||
#copyInputComponent
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForTopModel())"
|
||||
inputSize="55"
|
||||
headerTitle=""
|
||||
defaultValue="{{ getPullCommandForTopModel() }}"></hbr-copy-input>
|
||||
</div>
|
||||
|
||||
<clr-dropdown *ngIf="!isTopModel && !isTagMode">
|
||||
<hbr-copy-input
|
||||
*ngIf="isImage(artifact)"
|
||||
[title]="getPullCommandForRuntimeByDigest(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="
|
||||
onCpSuccess(getPullCommandForRuntimeByDigest(artifact))
|
||||
"
|
||||
class="btn btn-link copy-pull-command"
|
||||
clrDropdownTrigger>
|
||||
{{ 'PUSH_IMAGE.COPY_PULL_COMMAND' | translate }}
|
||||
<cds-icon shape="angle" direction="down"></cds-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
|
||||
<ng-container *ngIf="isImage(selectedRow[0])">
|
||||
<div
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForDocker(selectedRow[0])"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForDocker(selectedRow[0])
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.DOCKER' | translate }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForPadMan(selectedRow[0])"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForPadMan(selectedRow[0])
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.PODMAN' | translate }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div
|
||||
*ngIf="isCNAB(selectedRow[0])"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForCNAB(selectedRow[0])"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForCNAB(selectedRow[0])
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.CNAB' | translate }}</span>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isChart(selectedRow[0])"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForChart(selectedRow[0])"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForChart(selectedRow[0])
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.HELM' | translate }}</span>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
[defaultValue]="
|
||||
getPullCommandForRuntimeByDigest(artifact)
|
||||
"></hbr-copy-input>
|
||||
<div
|
||||
*ngIf="isCNAB(artifact)"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action">
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForCNAB(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForCNAB(artifact))"
|
||||
[defaultValue]="getPullCommandForCNAB(artifact)"></hbr-copy-input>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isChart(artifact)"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action">
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForChart(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForChart(artifact))"
|
||||
[defaultValue]="getPullCommandForChart(artifact)"></hbr-copy-input>
|
||||
</div>
|
||||
</clr-dropdown>
|
||||
|
||||
<clr-dropdown [clrCloseMenuOnItemClick]="false" class="mr-1" *ngIf="isTagMode">
|
||||
<button
|
||||
[disabled]="
|
||||
selectedTags?.length !== 1 || !hasPullCommandForTag(artifact)
|
||||
"
|
||||
class="btn btn-link copy-pull-command"
|
||||
clrDropdownTrigger>
|
||||
{{ 'PUSH_IMAGE.COPY_PULL_COMMAND' | translate }}
|
||||
<cds-icon shape="angle" direction="down"></cds-icon>
|
||||
</button>
|
||||
<clr-dropdown-menu clrPosition="bottom-right" *clrIfOpen>
|
||||
<ng-container *ngIf="isImage(artifact)">
|
||||
<div
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForDockerByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForDockerByTag(artifact)
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.DOCKER' | translate }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForPadManByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForPadManByTag(artifact)
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.PODMAN' | translate }}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
<div
|
||||
*ngIf="isCNAB(artifact)"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForCNABByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForCNABByTag(artifact)
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.CNAB' | translate }}</span>
|
||||
</div>
|
||||
<div
|
||||
*ngIf="isChart(artifact)"
|
||||
class="flex"
|
||||
aria-label="Dropdown header Action"
|
||||
clrDropdownItem>
|
||||
<hbr-copy-input
|
||||
[title]="getPullCommandForChartByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
[defaultValue]="
|
||||
getPullCommandForChartByTag(artifact)
|
||||
"></hbr-copy-input>
|
||||
<span>{{ 'PUSH_IMAGE.HELM' | translate }}</span>
|
||||
</div>
|
||||
</clr-dropdown-menu>
|
||||
<clr-dropdown
|
||||
class="mr-1"
|
||||
*ngIf="isTagMode && !isTopModel"
|
||||
[disabled]="!hasPullCommandForTag(artifact)">
|
||||
<hbr-copy-input
|
||||
*ngIf="isImage(artifact)"
|
||||
[title]="getPullCommandForRuntimeByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForRuntimeByTag(artifact))"
|
||||
[defaultValue]="
|
||||
getPullCommandForRuntimeByTag(artifact)
|
||||
"></hbr-copy-input>
|
||||
|
||||
<hbr-copy-input
|
||||
*ngIf="isCNAB(artifact)"
|
||||
[title]="getPullCommandForCNABByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForCNABByTag(artifact))"
|
||||
[defaultValue]="getPullCommandForCNABByTag(artifact)"></hbr-copy-input>
|
||||
<hbr-copy-input
|
||||
*ngIf="isChart(artifact)"
|
||||
[title]="getPullCommandForChartByTag(artifact)"
|
||||
[iconMode]="true"
|
||||
(onCopySuccess)="onCpSuccess(getPullCommandForChartByTag(artifact))"
|
||||
[defaultValue]="getPullCommandForChartByTag(artifact)"></hbr-copy-input>
|
||||
</clr-dropdown>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { PullCommandComponent } from './pull-command.component';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SharedTestingModule } from '../../../../../../../../shared/shared.module';
|
||||
import { ArtifactType } from '../../../../artifact'; // Import the necessary type
|
||||
|
||||
describe('PullCommandComponent', () => {
|
||||
let component: PullCommandComponent;
|
||||
let fixture: ComponentFixture<PullCommandComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [PullCommandComponent],
|
||||
|
@ -13,6 +15,14 @@ describe('PullCommandComponent', () => {
|
|||
|
||||
fixture = TestBed.createComponent(PullCommandComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
||||
// Mock the artifact input with a valid value
|
||||
component.artifact = {
|
||||
type: ArtifactType.IMAGE,
|
||||
digest: 'sampleDigest',
|
||||
tags: [{ name: 'latest' }],
|
||||
};
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
|
|
@ -6,9 +6,12 @@ import {
|
|||
Clients,
|
||||
getPullCommandByDigest,
|
||||
getPullCommandByTag,
|
||||
getPullCommandForTop,
|
||||
hasPullCommand,
|
||||
} from '../../../../artifact';
|
||||
import { Tag } from '../../../../../../../../../../ng-swagger-gen/models/tag';
|
||||
import { getContainerRuntime } from 'src/app/shared/units/shared.utils';
|
||||
import { MessageHandlerService } from 'src/app/shared/services/message-handler.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-pull-command',
|
||||
|
@ -16,6 +19,8 @@ import { Tag } from '../../../../../../../../../../ng-swagger-gen/models/tag';
|
|||
styleUrls: ['./pull-command.component.scss'],
|
||||
})
|
||||
export class PullCommandComponent {
|
||||
@Input()
|
||||
isTopModel: boolean = false; // TopModel is for tab top component,
|
||||
@Input()
|
||||
isTagMode: boolean = false; // tagMode is for tag list datagrid,
|
||||
@Input()
|
||||
|
@ -24,17 +29,20 @@ export class PullCommandComponent {
|
|||
registryUrl: string;
|
||||
@Input()
|
||||
repoName: string;
|
||||
@Input()
|
||||
selectedRow: Artifact[];
|
||||
|
||||
// for tagMode
|
||||
@Input()
|
||||
selectedTags: Tag[];
|
||||
selectedTag: string;
|
||||
@Input()
|
||||
artifact: Artifact;
|
||||
@Input()
|
||||
accessoryType: string;
|
||||
|
||||
constructor(
|
||||
private msgHandler: MessageHandlerService,
|
||||
private translate: TranslateService
|
||||
) {}
|
||||
|
||||
hasPullCommand(artifact: Artifact): boolean {
|
||||
return hasPullCommand(artifact);
|
||||
}
|
||||
|
@ -51,25 +59,31 @@ export class PullCommandComponent {
|
|||
return artifact.type === ArtifactType.CHART;
|
||||
}
|
||||
|
||||
getPullCommandForDocker(artifact: Artifact): string {
|
||||
return getPullCommandByDigest(
|
||||
artifact.type,
|
||||
// get client based on the selected container runtime
|
||||
getSelectedClient(): Clients {
|
||||
const runtime = getContainerRuntime();
|
||||
const client = Object.values(Clients).find(client => client == runtime);
|
||||
// return client if match found otherwise return (DOCKER)
|
||||
return client ? client : Clients.DOCKER;
|
||||
}
|
||||
|
||||
getPullCommandForTopModel(): string {
|
||||
return getPullCommandForTop(
|
||||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
artifact.digest,
|
||||
Clients.DOCKER
|
||||
this.getSelectedClient()
|
||||
);
|
||||
}
|
||||
|
||||
getPullCommandForPadMan(artifact: Artifact): string {
|
||||
getPullCommandForRuntimeByDigest(artifact: Artifact): string {
|
||||
return getPullCommandByDigest(
|
||||
artifact.type,
|
||||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
artifact.digest,
|
||||
Clients.PODMAN
|
||||
this.getSelectedClient()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -107,25 +121,14 @@ export class PullCommandComponent {
|
|||
);
|
||||
}
|
||||
|
||||
getPullCommandForDockerByTag(artifact: Artifact): string {
|
||||
getPullCommandForRuntimeByTag(artifact: Artifact): string {
|
||||
return getPullCommandByTag(
|
||||
artifact.type,
|
||||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
this.selectedTags[0].name,
|
||||
Clients.DOCKER
|
||||
);
|
||||
}
|
||||
|
||||
getPullCommandForPadManByTag(artifact: Artifact): string {
|
||||
return getPullCommandByTag(
|
||||
artifact.type,
|
||||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
this.selectedTags[0].name,
|
||||
Clients.PODMAN
|
||||
this.selectedTag,
|
||||
this.getSelectedClient()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -135,7 +138,7 @@ export class PullCommandComponent {
|
|||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
this.selectedTags[0].name,
|
||||
this.selectedTag,
|
||||
Clients.CNAB
|
||||
);
|
||||
}
|
||||
|
@ -146,8 +149,19 @@ export class PullCommandComponent {
|
|||
`${this.registryUrl ? this.registryUrl : location.hostname}/${
|
||||
this.projectName
|
||||
}/${this.repoName}`,
|
||||
this.selectedTags[0].name,
|
||||
this.selectedTag,
|
||||
Clients.CHART
|
||||
);
|
||||
}
|
||||
|
||||
onCpSuccess(copied: string): void {
|
||||
// $event is the defaultValue emitted from CopyInputComponent
|
||||
this.translate
|
||||
.get('REPOSITORY.COPY_SUCCESS', {
|
||||
param: copied,
|
||||
})
|
||||
.subscribe((res: string) => {
|
||||
this.msgHandler.showSuccess(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,15 +36,6 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="right-pos">
|
||||
<app-pull-command
|
||||
[isTagMode]="true"
|
||||
[artifact]="artifactDetails"
|
||||
[accessoryType]="accessoryType"
|
||||
[registryUrl]="registryUrl"
|
||||
[projectName]="projectName"
|
||||
[repoName]="repositoryName"
|
||||
[selectedTags]="selectedRow"
|
||||
class="mr-1"></app-pull-command>
|
||||
<span class="refresh-btn" (click)="refresh()">
|
||||
<clr-icon shape="refresh"></clr-icon>
|
||||
</span>
|
||||
|
@ -129,16 +120,25 @@
|
|||
</clr-dg-placeholder>
|
||||
<clr-dg-row *ngFor="let tag of currentTags" [clrDgItem]="tag">
|
||||
<clr-dg-cell>
|
||||
<div class="cell white-normal" [class.immutable]="tag.immutable">
|
||||
<div class="pull" [class.immutable]="tag.immutable">
|
||||
<span
|
||||
href="javascript:void(0)"
|
||||
class="max-width-100"
|
||||
class="max-width-150"
|
||||
title="{{ tag.name }}"
|
||||
>{{ tag.name }}</span
|
||||
>
|
||||
<span *ngIf="tag.immutable" class="label label-info ml-8">{{
|
||||
'REPOSITORY.IMMUTABLE' | translate
|
||||
}}</span>
|
||||
<app-pull-command
|
||||
class="pull-btn"
|
||||
[isTagMode]="true"
|
||||
[artifact]="artifactDetails"
|
||||
[accessoryType]="accessoryType"
|
||||
[registryUrl]="registryUrl"
|
||||
[projectName]="projectName"
|
||||
[repoName]="repositoryName"
|
||||
[selectedTag]="tag.name"></app-pull-command>
|
||||
</div>
|
||||
</clr-dg-cell>
|
||||
<clr-dg-cell>{{
|
||||
|
|
|
@ -1,61 +1,73 @@
|
|||
|
||||
.label-form {
|
||||
max-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.clr-control-container {
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.btn.remove-btn {
|
||||
border: none;
|
||||
height: 0.6rem;
|
||||
line-height: 1;
|
||||
border: none;
|
||||
height: 0.6rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.immutable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.datagrid-action-bar {
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.position-ab {
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.white-space-nowrap {
|
||||
white-space: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pull {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-content: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
cursor: pointer;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.spinner-tag {
|
||||
position: absolute;
|
||||
right: -.7rem;
|
||||
top: 1.2rem;
|
||||
position: absolute;
|
||||
right: -0.7rem;
|
||||
top: 1.2rem;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.right-pos {
|
||||
margin-right: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 35px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pull-btn {
|
||||
float: right;
|
||||
margin-right: -20px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Accessory } from 'ng-swagger-gen/models/accessory';
|
|||
import { Artifact } from '../../../../../../ng-swagger-gen/models/artifact';
|
||||
import { Platform } from '../../../../../../ng-swagger-gen/models/platform';
|
||||
import { Label } from '../../../../../../ng-swagger-gen/models/label';
|
||||
import { getCustomContainerRuntime } from 'src/app/shared/units/shared.utils';
|
||||
|
||||
export interface ArtifactFront extends Artifact {
|
||||
platform?: Platform;
|
||||
|
@ -101,6 +102,18 @@ export function hasPullCommand(artifact: Artifact): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
export function getPullCommandForTop(url: string, client: Clients): string {
|
||||
if (url) {
|
||||
if (Object.values(Clients).includes(client)) {
|
||||
if (client == 'custom') {
|
||||
return `${getCustomContainerRuntime()} pull ${url}`;
|
||||
}
|
||||
return `${client} pull ${url}`;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getPullCommandByDigest(
|
||||
artifactType: string,
|
||||
url: string,
|
||||
|
@ -109,13 +122,14 @@ export function getPullCommandByDigest(
|
|||
): string {
|
||||
if (artifactType && url && digest) {
|
||||
if (artifactType === ArtifactType.IMAGE) {
|
||||
if (client === Clients.DOCKER) {
|
||||
return `${Clients.DOCKER} pull ${url}@${digest}`;
|
||||
}
|
||||
if (client === Clients.PODMAN) {
|
||||
return `${Clients.PODMAN} pull ${url}@${digest}`;
|
||||
if (Object.values(Clients).includes(client)) {
|
||||
if (client == 'custom') {
|
||||
return `${getCustomContainerRuntime()} pull ${url}@${digest}`;
|
||||
}
|
||||
return `${client} pull ${url}@${digest}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (artifactType === ArtifactType.CNAB) {
|
||||
return `${Clients.CNAB} pull ${url}@${digest}`;
|
||||
}
|
||||
|
@ -131,11 +145,12 @@ export function getPullCommandByTag(
|
|||
): string {
|
||||
if (artifactType && url && tag) {
|
||||
if (artifactType === ArtifactType.IMAGE) {
|
||||
if (client === Clients.DOCKER) {
|
||||
return `${Clients.DOCKER} pull ${url}:${tag}`;
|
||||
}
|
||||
if (client === Clients.PODMAN) {
|
||||
return `${Clients.PODMAN} pull ${url}:${tag}`;
|
||||
if (Object.values(Clients).includes(client)) {
|
||||
if (client == 'custom') {
|
||||
return `${getCustomContainerRuntime()} pull ${url}:${tag}`;
|
||||
}
|
||||
|
||||
return `${client} pull ${url}:${tag}`;
|
||||
}
|
||||
}
|
||||
if (artifactType === ArtifactType.CNAB) {
|
||||
|
@ -159,17 +174,14 @@ export interface ArtifactFilterEvent {
|
|||
export enum Clients {
|
||||
DOCKER = 'docker',
|
||||
PODMAN = 'podman',
|
||||
NERDCTL = 'nerdctl',
|
||||
CONTAINERD = 'ctr',
|
||||
CRI_O = 'crictl',
|
||||
CUSTOM = 'custom',
|
||||
CHART = 'helm',
|
||||
CNAB = 'cnab-to-oci',
|
||||
}
|
||||
|
||||
export enum ClientNames {
|
||||
DOCKER = 'Docker',
|
||||
PODMAN = 'Podman',
|
||||
CHART = 'Helm',
|
||||
CNAB = 'CNAB',
|
||||
}
|
||||
|
||||
export enum ArtifactSbomType {
|
||||
SPDX = 'SPDX',
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
|
||||
<global-search></global-search>
|
||||
<div class="header-actions">
|
||||
<clr-dropdown class="dropdown-lang dropdown bottom-left">
|
||||
<clr-dropdown
|
||||
class="dropdown-lang dropdown bottom-left"
|
||||
*ngIf="!isSessionValid">
|
||||
<button class="nav-icon nav-icon-width" clrDropdownToggle>
|
||||
<clr-icon shape="world" class="icon-left"></clr-icon>
|
||||
<span class="currentLocale">{{ currentLang }}</span>
|
||||
|
@ -35,7 +37,9 @@
|
|||
>
|
||||
</clr-dropdown-menu>
|
||||
</clr-dropdown>
|
||||
<clr-dropdown class="dropdown-locale dropdown bottom-left">
|
||||
<clr-dropdown
|
||||
class="dropdown-locale dropdown bottom-left"
|
||||
*ngIf="!isSessionValid">
|
||||
<button class="nav-icon nav-icon-width" clrDropdownToggle>
|
||||
<clr-icon shape="date" class="icon-left"></clr-icon>
|
||||
<span class="currentLocale">{{
|
||||
|
@ -76,6 +80,12 @@
|
|||
(click)="openAccountSettingsModal()"
|
||||
>{{ 'ACCOUNT_SETTINGS.PROFILE' | translate }}</a
|
||||
>
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
clrDropdownItem
|
||||
(click)="openPreferencesModal()"
|
||||
>{{ 'ACCOUNT_SETTINGS.PREFERENCES' | translate }}</a
|
||||
>
|
||||
<a
|
||||
*ngIf="canChangePassword"
|
||||
href="javascript:void(0)"
|
||||
|
|
|
@ -173,6 +173,14 @@ export class NavigatorComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
// Open change preferences dialog
|
||||
openPreferencesModal(): void {
|
||||
this.showDialogModalAction.emit({
|
||||
modalName: modalEvents.PREFERENCES,
|
||||
modalFlag: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Open change password dialog
|
||||
openChangePwdModal(): void {
|
||||
this.showDialogModalAction.emit({
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<div>
|
||||
<div class="command-title" *ngIf="!iconMode">
|
||||
{{ headerTitle }}
|
||||
</div>
|
||||
<div class="command-title" *ngIf="!iconMode">{{ headerTitle }}</div>
|
||||
<div>
|
||||
<span [class.hide]="iconMode">
|
||||
<input
|
||||
|
|
|
@ -231,7 +231,24 @@ export enum GroupType {
|
|||
export const REFRESH_TIME_DIFFERENCE = 10000;
|
||||
|
||||
//
|
||||
export const DeFaultRuntime = 'default';
|
||||
export type SupportedRuntime = string;
|
||||
export const RUNTIMES = {
|
||||
default: 'docker',
|
||||
podman: 'podman',
|
||||
nerdctl: 'nerdctl',
|
||||
ctr: 'containerd',
|
||||
crictl: 'cri-o',
|
||||
custom: 'custom',
|
||||
} as const;
|
||||
export const supportedRuntimes = Object.keys(RUNTIMES) as SupportedRuntime[];
|
||||
/**
|
||||
* The default cookie key used to store current used container runtime preference.
|
||||
*/
|
||||
export const DEFAULT_RUNTIME_LOCALSTORAGE_KEY = 'harbor-runtime';
|
||||
export const CUSTOM_RUNTIME_LOCALSTORAGE_KEY = 'harbor-custom-runtime';
|
||||
|
||||
//
|
||||
export const DeFaultLang = 'en-us';
|
||||
export type SupportedLanguage = string;
|
||||
export const LANGUAGES = {
|
||||
|
|
|
@ -92,6 +92,7 @@ import {
|
|||
import { LabelLayout, UniversalTransition } from 'echarts/features';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { RobotPermissionsPanelComponent } from './components/robot-permissions-panel/robot-permissions-panel.component';
|
||||
import { PreferenceSettingsComponent } from '../base/preference-settings/preference-settings.component';
|
||||
|
||||
// register necessary components
|
||||
echarts.use([
|
||||
|
@ -168,6 +169,7 @@ ClarityIcons.add({
|
|||
InlineAlertComponent,
|
||||
NewUserFormComponent,
|
||||
MessageComponent,
|
||||
PreferenceSettingsComponent,
|
||||
NavigatorComponent,
|
||||
SearchResultComponent,
|
||||
GlobalSearchComponent,
|
||||
|
@ -211,6 +213,7 @@ ClarityIcons.add({
|
|||
InlineAlertComponent,
|
||||
NewUserFormComponent,
|
||||
MessageComponent,
|
||||
PreferenceSettingsComponent,
|
||||
NavigatorComponent,
|
||||
SearchResultComponent,
|
||||
GlobalSearchComponent,
|
||||
|
|
|
@ -22,6 +22,11 @@ import {
|
|||
httpStatusCode,
|
||||
SupportedLanguage,
|
||||
LANGUAGES,
|
||||
SupportedRuntime,
|
||||
DEFAULT_RUNTIME_LOCALSTORAGE_KEY,
|
||||
RUNTIMES,
|
||||
DeFaultRuntime,
|
||||
CUSTOM_RUNTIME_LOCALSTORAGE_KEY,
|
||||
} from '../entities/shared.const';
|
||||
|
||||
/**
|
||||
|
@ -208,6 +213,35 @@ export const errorHandler = function (error: any): string {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the container runtime saved by the user, or the default runtime if no valid saved value is found.
|
||||
*/
|
||||
export function getContainerRuntime(): SupportedRuntime {
|
||||
const savedContainerRuntime = localStorage.getItem(
|
||||
DEFAULT_RUNTIME_LOCALSTORAGE_KEY
|
||||
);
|
||||
if (savedContainerRuntime && isSupportedRuntime(savedContainerRuntime)) {
|
||||
return savedContainerRuntime;
|
||||
}
|
||||
return DeFaultRuntime;
|
||||
}
|
||||
function isSupportedRuntime(x: unknown): x is SupportedRuntime {
|
||||
return Object.keys(RUNTIMES).some(k => k === x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom container runtime saved by the user
|
||||
*/
|
||||
export function getCustomContainerRuntime(): SupportedRuntime {
|
||||
const savedContainerRuntime = localStorage.getItem(
|
||||
CUSTOM_RUNTIME_LOCALSTORAGE_KEY
|
||||
);
|
||||
if (savedContainerRuntime) {
|
||||
return savedContainerRuntime;
|
||||
}
|
||||
return DeFaultRuntime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the datetime rendering setting saved by the user, or the default setting if no valid saved value is found.
|
||||
*/
|
||||
|
|
|
@ -103,7 +103,7 @@
|
|||
}
|
||||
|
||||
.go-link {
|
||||
color: $mode-link-color1 !important;
|
||||
color: $mode-link-color1 !important;
|
||||
border-bottom: 1px solid $mode-link-color1 !important;
|
||||
}
|
||||
|
||||
|
@ -137,7 +137,7 @@ clr-dg-action-overflow {
|
|||
background: $select-back-color;
|
||||
color: $select-option-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hbr-tag {
|
||||
|
@ -252,7 +252,9 @@ clr-header {
|
|||
hbr-copy-input {
|
||||
.command-input {
|
||||
color: $command-input-color;
|
||||
padding-left: 8px;
|
||||
background-color: $command-input-bg-color;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -151,8 +151,15 @@
|
|||
"SAVE_SUCCESS": "Nutzerpasswort erfolgreich geändert.",
|
||||
"PASS_TIPS": "8-128 Zeichen mit einem Groß-, einem Kleinbuchstaben und einer Ziffer"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "Nutzerprofil",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Passwort ändern",
|
||||
"ABOUT": "Info",
|
||||
"LOGOUT": "Ausloggen"
|
||||
|
@ -773,7 +780,7 @@
|
|||
"ARTIFACT_TOOTIP": "Klicken um die Artefakt-Liste des OCI index zu sehen",
|
||||
"ARTIFACTS_COUNT": "Artefakte",
|
||||
"PULL_COUNT": "Pulls",
|
||||
"PULL_COMMAND": "Pull Befehl",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "Pull Zeit",
|
||||
"PUSH_TIME": "Push Zeit",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1146,8 +1153,7 @@
|
|||
"TOOLTIP": "Befehlreferenz um ein Artefakt in das Projekt zu pushen.",
|
||||
"TAG_COMMAND": "Tag ein Image für dieses Projekt:",
|
||||
"PUSH_COMMAND": "Push ein Image für dieses Projekt:",
|
||||
"COPY_ERROR": "Kopieren fehlgeschlagen, bitte die Befehlsreferenz manuell kopieren.",
|
||||
"COPY_PULL_COMMAND": "COPY PULL COMMAND"
|
||||
"COPY_ERROR": "Kopieren fehlgeschlagen, bitte die Befehlsreferenz manuell kopieren."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filter Artefakte",
|
||||
|
|
|
@ -151,8 +151,15 @@
|
|||
"SAVE_SUCCESS": "User password changed successfully.",
|
||||
"PASS_TIPS": "8-128 characters long with at least 1 uppercase, 1 lowercase and 1 number"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "User Profile",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Change Password",
|
||||
"ABOUT": "About",
|
||||
"LOGOUT": "Log Out"
|
||||
|
@ -773,7 +780,7 @@
|
|||
"ARTIFACT_TOOTIP": "Click to view this OCI index's artifact list",
|
||||
"ARTIFACTS_COUNT": "Artifacts",
|
||||
"PULL_COUNT": "Pulls",
|
||||
"PULL_COMMAND": "Pull Command",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "Pull Time",
|
||||
"PUSH_TIME": "Push Time",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1148,8 +1155,7 @@
|
|||
"TOOLTIP": "Command references for pushing an artifact to this project.",
|
||||
"TAG_COMMAND": "Tag an image for this project:",
|
||||
"PUSH_COMMAND": "Push an image to this project:",
|
||||
"COPY_ERROR": "Copy failed, please try to manually copy the command references.",
|
||||
"COPY_PULL_COMMAND": "COPY PULL COMMAND"
|
||||
"COPY_ERROR": "Copy failed, please try to manually copy the command references."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filter Artifacts",
|
||||
|
|
|
@ -151,8 +151,15 @@
|
|||
"SAVE_SUCCESS": "Contraseña de usuario guardada satisfactoriamente.",
|
||||
"PASS_TIPS": "8-128 caracteres con 1 letra mayúscula, 1 minúscula y 1 número"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "Perfil de usuario",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Cambiar contraseña",
|
||||
"ABOUT": "Acerca de",
|
||||
"LOGOUT": "Cerrar sesión"
|
||||
|
@ -774,7 +781,7 @@
|
|||
"ARTIFACT_TOOTIP": "Haga clic para ver la lista de artefactos de este índice OCI",
|
||||
"ARTIFACTS_COUNT": "Artifacts",
|
||||
"PULL_COUNT": "Pulls",
|
||||
"PULL_COMMAND": "Comando Pull",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "Tiempo de Pull",
|
||||
"PUSH_TIME": "Tiempo de Push",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1145,8 +1152,7 @@
|
|||
"TOOLTIP": "Referencias de comandos para enviar un artefacto a este proyecto.",
|
||||
"TAG_COMMAND": "Tag a imagen para este proyecto:",
|
||||
"PUSH_COMMAND": "Push a imagen para este proyecto:",
|
||||
"COPY_ERROR": "Copiar fallido, por favor intenta copiar manualmente las referencias del comando.",
|
||||
"COPY_PULL_COMMAND": "COPY PULL COMMAND"
|
||||
"COPY_ERROR": "Copiar fallido, por favor intenta copiar manualmente las referencias del comando."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filtrar Artifacts",
|
||||
|
@ -2009,4 +2015,4 @@
|
|||
"INVALID_VALUE": "El CVSS3 score debe estar entre el rango de 0 y 10",
|
||||
"PAGE_TITLE_TOOLTIP": "El recuento completo de artefactos comprende el total acumulado de artefactos individuales, incluidos los accesorios de los artefactos, así como los artefactos secundarios asociados con el índice de imagen y los artefactos CNAB"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -151,8 +151,15 @@
|
|||
"SAVE_SUCCESS": "Mot de passe utilisateur modifié avec succès.",
|
||||
"PASS_TIPS": "8-128 caractères avec au moins 1 majuscule, 1 minuscule et 1 chiffre"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "Profil Utilisateur",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Modifier le mot de passe",
|
||||
"ABOUT": "À propos",
|
||||
"LOGOUT": "Se déconnecter"
|
||||
|
@ -773,7 +780,7 @@
|
|||
"ARTIFACT_TOOTIP": "Cliquez pour voir la liste des index des artefacts de cet OCI",
|
||||
"ARTIFACTS_COUNT": "Artefacts",
|
||||
"PULL_COUNT": "Pulls",
|
||||
"PULL_COMMAND": "Commande de pull",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "Date/Heure de pull",
|
||||
"PUSH_TIME": "Date/Heure de push",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1148,8 +1155,7 @@
|
|||
"TOOLTIP": "Commandes pour push un artefact dans ce projet.",
|
||||
"TAG_COMMAND": "Taguer une image pour ce projet :",
|
||||
"PUSH_COMMAND": "Push une image dans ce projet :",
|
||||
"COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement les commandes de référence.",
|
||||
"COPY_PULL_COMMAND": "COMMANDE COPY PULL"
|
||||
"COPY_ERROR": "Copie échouée, veuillez essayer de copier manuellement les commandes de référence."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filtrer les artefacts",
|
||||
|
|
|
@ -143,6 +143,12 @@
|
|||
"CONFIRM_TITLE_CLI_GENERATE": "시크릿을 다시 생성할 수 있습니까?",
|
||||
"CONFIRM_BODY_CLI_GENERATE": "Cli 시크릿을 재생성하면 이전 Cli 시크릿이 삭제됩니다"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "비밀번호 변경",
|
||||
"CURRENT_PWD": "현재 비밀번호",
|
||||
|
@ -153,6 +159,7 @@
|
|||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "사용자 프로필",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "비밀번호 변경",
|
||||
"ABOUT": "About",
|
||||
"LOGOUT": "로그아웃"
|
||||
|
@ -770,7 +777,7 @@
|
|||
"ARTIFACT_TOOTIP": "이 OCI 인덱스의 아티팩트 목록을 보려면 클릭하세요.",
|
||||
"ARTIFACTS_COUNT": "아티팩트",
|
||||
"PULL_COUNT": "풀(Pull) 수",
|
||||
"PULL_COMMAND": "풀(Pull) 명령어",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "풀(Pull) 시간",
|
||||
"PUSH_TIME": "푸시 시간",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1143,8 +1150,7 @@
|
|||
"TOOLTIP": "이 프로젝트에 아티팩트를 푸시하기 위한 명령 참조입니다.",
|
||||
"TAG_COMMAND": "이 프로젝트의 이미지에 태그를 지정:",
|
||||
"PUSH_COMMAND": "이 프로젝트에 이미지 푸시:",
|
||||
"COPY_ERROR": "복사에 실패했습니다. 명령 참조를 수동으로 복사해 보세요.",
|
||||
"COPY_PULL_COMMAND": "풀(PULL) 명령어 복사"
|
||||
"COPY_ERROR": "복사에 실패했습니다. 명령 참조를 수동으로 복사해 보세요."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "아티팩트 필터",
|
||||
|
|
|
@ -142,6 +142,12 @@
|
|||
"CONFIRM_TITLE_CLI_GENERATE": "Gostaria de redefinir o segredo?",
|
||||
"CONFIRM_BODY_CLI_GENERATE": "Ao fazer isso, o segredo atual não poderá ser recuperado"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "Alterar Senha",
|
||||
"CURRENT_PWD": "Senha atual",
|
||||
|
@ -152,6 +158,7 @@
|
|||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "Perfil do usuário",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Alterar senha",
|
||||
"ABOUT": "Informativo",
|
||||
"LOGOUT": "Sair"
|
||||
|
@ -772,7 +779,7 @@
|
|||
"ARTIFACT_TOOTIP": "Clique para ver a lista de artefatos OCI",
|
||||
"ARTIFACTS_COUNT": "Artefatos",
|
||||
"PULL_COUNT": "Pulls",
|
||||
"PULL_COMMAND": "Comando de Pull",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "Horário de Envio",
|
||||
"PUSH_TIME": "Horário de Recebimento",
|
||||
"IMMUTABLE": "Imutável",
|
||||
|
@ -1143,8 +1150,7 @@
|
|||
"TOOLTIP": "Referência de comandos para enviar artefatos a este projeto.",
|
||||
"TAG_COMMAND": "Colocar tag em uma imagem deste projeto:",
|
||||
"PUSH_COMMAND": "Envia uma imagem a esse projeto:",
|
||||
"COPY_ERROR": "Cópia falhou, por favor tente copiar o comando de referência manualmente.",
|
||||
"COPY_PULL_COMMAND": "COPY PULL COMMAND"
|
||||
"COPY_ERROR": "Cópia falhou, por favor tente copiar o comando de referência manualmente."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filtrar",
|
||||
|
|
|
@ -143,6 +143,12 @@
|
|||
"CONFIRM_TITLE_CLI_GENERATE": "Şifreyi yeniden oluşturabileceğine emin misin?",
|
||||
"CONFIRM_BODY_CLI_GENERATE": "Eğer cli şifresini yeniden oluşturursanız, eski cli şifresi atılır"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "Şifreyi değiştir",
|
||||
"CURRENT_PWD": "Mevcut Şifre",
|
||||
|
@ -151,8 +157,10 @@
|
|||
"SAVE_SUCCESS": "Kullanıcı şifresi başarıyla değiştirildi.",
|
||||
"PASS_TIPS": "1 büyük harf, 1 küçük harf ve 1 sayı ile 8-128 karakter"
|
||||
},
|
||||
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "Kullanıcı Profili",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "Şifreyi Değiştir",
|
||||
"ABOUT": "Hakkında",
|
||||
"LOGOUT": "Çıkış"
|
||||
|
@ -773,7 +781,7 @@
|
|||
"ARTIFACT_TOOTIP": "Click to view this OCI index's artifact list",
|
||||
"ARTIFACTS_COUNT": "Artifacts",
|
||||
"PULL_COUNT": "İndirmeler",
|
||||
"PULL_COMMAND": "İndirme Komutu",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "İndirme Zamanı",
|
||||
"PUSH_TIME": "Yükleme Zamanı",
|
||||
"IMMUTABLE": "Immutable",
|
||||
|
@ -1146,8 +1154,7 @@
|
|||
"TOOLTIP": "Command references for pushing an artifact to this project.",
|
||||
"TAG_COMMAND": "Bu proje için bir imaj etiketleyin:",
|
||||
"PUSH_COMMAND": "Bu projeye bir imaj gönder:",
|
||||
"COPY_ERROR": "Kopyalama başarısız oldu, lütfen komut referanslarını el ile kopyalamayı deneyin.",
|
||||
"COPY_PULL_COMMAND": "COPY PULL COMMAND"
|
||||
"COPY_ERROR": "Kopyalama başarısız oldu, lütfen komut referanslarını el ile kopyalamayı deneyin."
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filter Artifacts",
|
||||
|
|
|
@ -142,6 +142,12 @@
|
|||
"CONFIRM_TITLE_CLI_GENERATE": "您确定需要重新生成cli secret吗?",
|
||||
"CONFIRM_BODY_CLI_GENERATE": "如果您重新生成cli secret,那么旧的cli secret将会被弃用"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"CHANGE_PWD": {
|
||||
"TITLE": "修改密码",
|
||||
"CURRENT_PWD": "当前密码",
|
||||
|
@ -152,6 +158,7 @@
|
|||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "用户设置",
|
||||
"PREFERENCES": "Preferences",
|
||||
"CHANGE_PWD": "修改密码",
|
||||
"ABOUT": "关于",
|
||||
"LOGOUT": "退出"
|
||||
|
@ -772,7 +779,7 @@
|
|||
"ARTIFACT_TOOTIP": "点击查看此 OCI 索引的 Artifact 列表",
|
||||
"ARTIFACTS_COUNT": "Artifacts",
|
||||
"PULL_COUNT": "下载数",
|
||||
"PULL_COMMAND": "拉取命令",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "拉取时间",
|
||||
"PUSH_TIME": "推送时间",
|
||||
"IMMUTABLE": "不可变的",
|
||||
|
@ -1147,8 +1154,7 @@
|
|||
"TOOLTIP": "推送一个 artifact 到当前项目的参考命令。",
|
||||
"TAG_COMMAND": "在项目中标记镜像:",
|
||||
"PUSH_COMMAND": "推送镜像到当前项目:",
|
||||
"COPY_ERROR": "拷贝失败,请尝试手动拷贝参考命令。",
|
||||
"COPY_PULL_COMMAND": "复制拉取命令"
|
||||
"COPY_ERROR": "拷贝失败,请尝试手动拷贝参考命令。"
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "Filter Artifacts",
|
||||
|
|
|
@ -150,9 +150,16 @@
|
|||
"SAVE_SUCCESS": "成功更改使用者密碼。",
|
||||
"PASS_TIPS": "密碼長度需介於 8 到 128 個字元之間,且至少包含一個大寫字母、小寫字母或數字。"
|
||||
},
|
||||
"CHANGE_PREF": {
|
||||
"TITLE": "Preferences",
|
||||
"LANGUAGE": "Language",
|
||||
"DATE_TIME_FORMAT": "Date/Time Format",
|
||||
"PULL_CMD_PREFIX": "Pull Command Prefix"
|
||||
},
|
||||
"ACCOUNT_SETTINGS": {
|
||||
"PROFILE": "使用者設定",
|
||||
"CHANGE_PWD": "修改密碼",
|
||||
"PREFERENCES": "Preferences",
|
||||
"ABOUT": "關於",
|
||||
"LOGOUT": "登出"
|
||||
},
|
||||
|
@ -772,7 +779,7 @@
|
|||
"ARTIFACT_TOOTIP": "點選此圖示進入引用的 Artifact 列表",
|
||||
"ARTIFACTS_COUNT": "Artifact 數量",
|
||||
"PULL_COUNT": "下載數",
|
||||
"PULL_COMMAND": "Pull 命令",
|
||||
"COPY_SUCCESS": "{{ param }} copied to Clipboard",
|
||||
"PULL_TIME": "拉取時間",
|
||||
"PUSH_TIME": "推送時間",
|
||||
"IMMUTABLE": "不可變的",
|
||||
|
@ -1144,8 +1151,7 @@
|
|||
"TOOLTIP": "推送映像擋至此專案的參考命令。",
|
||||
"TAG_COMMAND": "為此專案標記映像檔:",
|
||||
"PUSH_COMMAND": "將映像檔推送至此專案:",
|
||||
"COPY_ERROR": "複製失敗,請嘗試手動複製參考命令。",
|
||||
"COPY_PULL_COMMAND": "複製拉取命令"
|
||||
"COPY_ERROR": "複製失敗,請嘗試手動複製參考命令。"
|
||||
},
|
||||
"ARTIFACT": {
|
||||
"FILTER_FOR_ARTIFACTS": "篩選 Artifact(s)",
|
||||
|
|
Loading…
Reference in New Issue
Block a user