From ef96c729c029bbf7a96121803fb2a6f12b846591 Mon Sep 17 00:00:00 2001 From: Shijun Sun <30999793+AllForNothing@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:58:37 +0800 Subject: [PATCH] Add costomized banner message UI (#18827) 1.Fixes #18719 2.Add Banner Message item to configuration 3.Add banner_message property to systeminfo API Signed-off-by: AllForNothing --- README.md | 2 +- api/v2.0/swagger.yaml | 14 ++ src/common/const.go | 3 + src/controller/systeminfo/controller.go | 8 ++ src/controller/systeminfo/controller_test.go | 3 + src/lib/config/metadata/metadatalist.go | 2 + src/lib/config/userconfig.go | 5 + .../app-level-alerts.component.html | 9 ++ .../app-level-alerts.component.ts | 79 +++++++++++ .../left-side-nav/config/config.service.ts | 8 +- .../app/base/left-side-nav/config/config.ts | 30 +++++ .../system/system-settings.component.html | 121 +++++++++++++++++ .../system/system-settings.component.scss | 51 +++++++- .../system/system-settings.component.ts | 123 +++++++++++++++++- src/portal/src/app/services/app-config.ts | 3 + .../services/event-service/event.service.ts | 1 + src/portal/src/i18n/lang/de-de-lang.json | 12 ++ src/portal/src/i18n/lang/en-us-lang.json | 16 ++- src/portal/src/i18n/lang/es-es-lang.json | 12 ++ src/portal/src/i18n/lang/fr-fr-lang.json | 12 ++ src/portal/src/i18n/lang/pt-br-lang.json | 12 ++ src/portal/src/i18n/lang/tr-tr-lang.json | 12 ++ src/portal/src/i18n/lang/zh-cn-lang.json | 12 ++ src/portal/src/i18n/lang/zh-tw-lang.json | 12 ++ src/server/v2.0/handler/systeminfo.go | 1 + 25 files changed, 551 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 923eaf1fa..88d00b5a6 100644 --- a/README.md +++ b/README.md @@ -116,4 +116,4 @@ This project uses open source components which have additional licensing terms. ## Fossa Status -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fgoharbor%2Fharbor?ref=badge_large) diff --git a/api/v2.0/swagger.yaml b/api/v2.0/swagger.yaml index de635c981..8748592f9 100644 --- a/api/v2.0/swagger.yaml +++ b/api/v2.0/swagger.yaml @@ -7682,6 +7682,12 @@ definitions: GeneralInfo: type: object properties: + banner_message: + type: string + x-nullable: true + x-omitempty: true + description: The banner message for the UI. It is the stringified result of the banner message object. + example: "{\"closable\":true,\"message\":\"your banner message content\",\"type\":\"warning\",\"fromDate\":\"06/19/2023\",\"toDate\":\"06/21/2023\"}" current_time: type: string format: date-time @@ -8820,6 +8826,9 @@ definitions: session_timeout: $ref: '#/definitions/IntegerConfigItem' description: The session timeout in minutes + banner_message: + $ref: '#/definitions/StringConfigItem' + description: The banner message for the UI.It is the stringified result of the banner message object Configurations: type: object properties: @@ -9088,6 +9097,11 @@ definitions: description: Whether or not to skip update pull time for scanner x-omitempty: true x-isnullable: true + banner_message: + type: string + description: The banner message for the UI.It is the stringified result of the banner message object + x-omitempty: true + x-isnullable: true StringConfigItem: type: object properties: diff --git a/src/common/const.go b/src/common/const.go index f030dacf1..5e30b78fc 100644 --- a/src/common/const.go +++ b/src/common/const.go @@ -219,6 +219,9 @@ const ( // SessionTimeout defines the web session timeout SessionTimeout = "session_timeout" + // Customized banner message + BannerMessage = "banner_message" + // UIMaxLengthLimitedOfNumber is the max length that UI limited for type number UIMaxLengthLimitedOfNumber = 10 // ExecutionStatusRefreshIntervalSeconds is the interval seconds for refreshing execution status diff --git a/src/controller/systeminfo/controller.go b/src/controller/systeminfo/controller.go index cfca363f5..1487f036b 100644 --- a/src/controller/systeminfo/controller.go +++ b/src/controller/systeminfo/controller.go @@ -47,6 +47,7 @@ type Data struct { PrimaryAuthMode bool SelfRegistration bool HarborVersion string + BannerMessage string AuthProxySettings *models.HTTPAuthProxy Protected *protectedData } @@ -90,11 +91,18 @@ func (c *controller) GetInfo(ctx context.Context, opt Options) (*Data, error) { logger.Errorf("Error occurred getting config: %v", err) return nil, err } + mgr := config.GetCfgManager(ctx) + err = mgr.Load(ctx) + if err != nil { + logger.Errorf("Error occurred loading config: %v", err) + return nil, err + } res := &Data{ AuthMode: utils.SafeCastString(cfg[common.AUTHMode]), PrimaryAuthMode: utils.SafeCastBool(cfg[common.PrimaryAuthMode]), SelfRegistration: utils.SafeCastBool(cfg[common.SelfRegistration]), HarborVersion: fmt.Sprintf("%s-%s", version.ReleaseVersion, version.GitCommit), + BannerMessage: utils.SafeCastString(mgr.Get(ctx, common.BannerMessage).GetString()), } if res.AuthMode == common.HTTPAuth { if s, err := config.HTTPAuthProxySetting(ctx); err == nil { diff --git a/src/controller/systeminfo/controller_test.go b/src/controller/systeminfo/controller_test.go index a8156f1ed..a2e98a1ba 100644 --- a/src/controller/systeminfo/controller_test.go +++ b/src/controller/systeminfo/controller_test.go @@ -31,6 +31,7 @@ func (s *sysInfoCtlTestSuite) SetupTest() { common.RegistryStorageProviderName: "filesystem", common.ReadOnly: false, common.NotificationEnable: false, + common.BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", } config.InitWithSettings(conf) @@ -58,6 +59,7 @@ func (s *sysInfoCtlTestSuite) TestGetInfo() { AuthMode: "db_auth", HarborVersion: "test-fakeid", SelfRegistration: true, + BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", }, }, { @@ -66,6 +68,7 @@ func (s *sysInfoCtlTestSuite) TestGetInfo() { AuthMode: "db_auth", HarborVersion: "test-fakeid", SelfRegistration: true, + BannerMessage: "{\"closable\":false,\"message\":\"Just for test\",\"type\":\" error\"}", Protected: &protectedData{ RegistryURL: "test.goharbor.io", ExtURL: "https://test.goharbor.io", diff --git a/src/lib/config/metadata/metadatalist.go b/src/lib/config/metadata/metadatalist.go index 3f7c30812..bb3285e5f 100644 --- a/src/lib/config/metadata/metadatalist.go +++ b/src/lib/config/metadata/metadatalist.go @@ -189,5 +189,7 @@ var ( {Name: common.SessionTimeout, Scope: UserScope, Group: BasicGroup, EnvKey: "SESSION_TIMEOUT", DefaultValue: "60", ItemType: &Int64Type{}, Editable: true, Description: `The session timeout in minutes`}, {Name: common.ExecutionStatusRefreshIntervalSeconds, Scope: SystemScope, Group: BasicGroup, EnvKey: "EXECUTION_STATUS_REFRESH_INTERVAL_SECONDS", DefaultValue: "30", ItemType: &Int64Type{}, Editable: false, Description: `The interval seconds to refresh the execution status`}, + + {Name: common.BannerMessage, Scope: UserScope, Group: BasicGroup, EnvKey: "BANNER_MESSAGE", DefaultValue: "", ItemType: &StringType{}, Editable: true, Description: `The customized banner message for the UI`}, } ) diff --git a/src/lib/config/userconfig.go b/src/lib/config/userconfig.go index 863c8fb9e..e22b438f7 100644 --- a/src/lib/config/userconfig.go +++ b/src/lib/config/userconfig.go @@ -255,3 +255,8 @@ func ScannerSkipUpdatePullTime(ctx context.Context) bool { log.Infof("skip_update_pull_time:%v", DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool()) return DefaultMgr().Get(ctx, common.ScannerSkipUpdatePullTime).GetBool() } + +// BannerMessage returns the customized banner message +func BannerMessage(ctx context.Context) string { + return DefaultMgr().Get(ctx, common.BannerMessage).GetString() +} diff --git a/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html b/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html index 797d1aad3..769891065 100644 --- a/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html +++ b/src/portal/src/app/base/harbor-shell/app-level-alerts/app-level-alerts.component.html @@ -1,4 +1,13 @@ + + + {{ getBannerMessage() }} + + = new Date(bm.fromDate) + ); + } + if (bm?.fromDate && !bm?.toDate) { + return new Date(current) >= new Date(bm.fromDate); + } + + if (!bm?.fromDate && bm?.toDate) { + return new Date(current) <= new Date(bm.toDate); + } + } + return false; + } + + getBannerMessage() { + if ( + this.appConfigService.getConfig()?.banner_message && + ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.message + ) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.message; + } + return null; + } + + getBannerMessageType() { + if ( + this.appConfigService.getConfig()?.banner_message && + ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.type + ) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.type; + } + return BannerMessageType.WARNING; + } + + getBannerMessageClosable(): boolean { + if (this.appConfigService.getConfig()?.banner_message) { + return ( + JSON.parse( + this.appConfigService.getConfig()?.banner_message + ) as BannerMessage + )?.closable; + } + return true; + } } diff --git a/src/portal/src/app/base/left-side-nav/config/config.service.ts b/src/portal/src/app/base/left-side-nav/config/config.service.ts index 26ac30f2f..516eac239 100644 --- a/src/portal/src/app/base/left-side-nav/config/config.service.ts +++ b/src/portal/src/app/base/left-side-nav/config/config.service.ts @@ -11,6 +11,10 @@ import { clone } from '../../../shared/units/utils'; import { MessageHandlerService } from '../../../shared/services/message-handler.service'; import { finalize } from 'rxjs/operators'; import { Observable, Subscription } from 'rxjs'; +import { + EventService, + HarborEvent, +} from '../../../services/event-service/event.service'; const fakePass = 'aWpLOSYkIzJTTU4wMDkx'; @@ -24,7 +28,8 @@ export class ConfigService { constructor( private confirmService: ConfirmationDialogService, private configureService: ConfigureService, - private msgHandler: MessageHandlerService + private msgHandler: MessageHandlerService, + private event: EventService ) { this._confirmSub = this.confirmService.confirmationConfirm$.subscribe( confirmation => { @@ -66,6 +71,7 @@ export class ConfigService { .subscribe( res => { this._currentConfig = res as Configuration; + this.event.publish(HarborEvent.REFRESH_BANNER_MESSAGE); // Add password fields this._currentConfig.email_password = new StringValueItem( fakePass, diff --git a/src/portal/src/app/base/left-side-nav/config/config.ts b/src/portal/src/app/base/left-side-nav/config/config.ts index 30babedd0..0fedfca30 100644 --- a/src/portal/src/app/base/left-side-nav/config/config.ts +++ b/src/portal/src/app/base/left-side-nav/config/config.ts @@ -114,6 +114,7 @@ export class Configuration { skip_audit_log_database: BoolValueItem; session_timeout: NumberValueItem; scanner_skip_update_pulltime: BoolValueItem; + banner_message: StringValueItem; public constructor() { this.auth_mode = new StringValueItem('db_auth', true); this.primary_auth_mode = new BoolValueItem(false, true); @@ -190,6 +191,10 @@ export class Configuration { this.skip_audit_log_database = new BoolValueItem(false, true); this.session_timeout = new NumberValueItem(60, true); this.scanner_skip_update_pulltime = new BoolValueItem(false, true); + this.banner_message = new StringValueItem( + JSON.stringify(new BannerMessage()), + true + ); } } @@ -208,3 +213,28 @@ export enum Triggers { SCHEDULE = 'Schedule', EVENT = 'Event', } + +export class BannerMessage { + message: string; + closable: boolean; + type: string; + fromDate: Date; + toDate: Date; + constructor() { + this.closable = false; + } +} + +export enum BannerMessageType { + SUCCESS = 'success', + INFO = 'info', + WARNING = 'warning', + ERROR = 'danger', +} + +export const BannerMessageI18nMap = { + [BannerMessageType.SUCCESS]: 'BANNER_MESSAGE.SUCCESS', + [BannerMessageType.INFO]: 'BANNER_MESSAGE.INFO', + [BannerMessageType.WARNING]: 'BANNER_MESSAGE.WARNING', + [BannerMessageType.ERROR]: 'BANNER_MESSAGE.DANGER', +}; diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html index 2968cbde4..08a1e469d 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.html @@ -346,6 +346,127 @@ " /> +
+ +
+
+ +
+
+
+ + +
+
+
+ + +
+
+
+ +
+ +
+
+ +
+
+ + +
+
+ + +
+
+
diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss index e107684c7..08ffcc973 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.scss @@ -1,3 +1,6 @@ +$input-width: 12rem; + + .subtitle { font-size: 14px; font-weight: 600; @@ -148,11 +151,11 @@ } .clr-input { - width: 12rem; + width: $input-width; } .pro-creation { - width: 12rem; + width: $input-width; } input::-webkit-outer-spin-button, @@ -165,3 +168,47 @@ input::-webkit-inner-spin-button { input[type=number] { appearance: textfield; } + +.clr-textarea { + max-width: none; + width: $input-width; +} + +.flex-baseline { + display: flex; + align-items: baseline; +} + +$message-type-width: 12rem; + +.message-type { + margin-left: 2rem; + width: $message-type-width; +} + +.message-label { + margin-right: 0.25rem; +} + +.duration { + width: $input-width; + display: flex; + justify-content: right; +} + +.flex { + display: flex; +} + +:host::ng-deep clr-date-container{ + margin-top: 0; + margin-left: 0.5rem; +} + +.message-select { + width: 6.75rem; +} + +.date { + width: 6rem; +} diff --git a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts index 8888664e9..dc84b9ca6 100644 --- a/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts +++ b/src/portal/src/app/base/left-side-nav/config/system/system-settings.component.ts @@ -1,6 +1,11 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { NgForm } from '@angular/forms'; -import { Configuration } from '../config'; +import { + BannerMessage, + BannerMessageI18nMap, + BannerMessageType, + Configuration, +} from '../config'; import { CURRENT_BASE_HREF, getChanges, @@ -10,13 +15,19 @@ import { ConfigService } from '../config.service'; import { AppConfigService } from '../../../../services/app-config.service'; import { finalize } from 'rxjs/operators'; import { MessageHandlerService } from '../../../../shared/services/message-handler.service'; +import { + EventService, + HarborEvent, +} from '../../../../services/event-service/event.service'; +import { Subscription } from 'rxjs'; @Component({ selector: 'system-settings', templateUrl: './system-settings.component.html', styleUrls: ['./system-settings.component.scss'], }) -export class SystemSettingsComponent implements OnInit { +export class SystemSettingsComponent implements OnInit, OnDestroy { + bannerMessageTypes: string[] = Object.values(BannerMessageType); onGoing = false; downloadLink: string; get currentConfig(): Configuration { @@ -26,18 +37,90 @@ export class SystemSettingsComponent implements OnInit { set currentConfig(cfg: Configuration) { this.conf.setConfig(cfg); } + + messageText: string; + messageType: string; + messageClosable: boolean; + messageFromDate: Date; + messageToDate: Date; + // the copy of bannerMessage + messageTextCopy: string; + messageTypeCopy: string; + messageClosableCopy: boolean; + messageFromDateCopy: Date; + messageToDateCopy: Date; + bannerRefreshSub: Subscription; + @ViewChild('systemConfigFrom') systemSettingsForm: NgForm; constructor( private appConfigService: AppConfigService, private errorHandler: MessageHandlerService, - private conf: ConfigService + private conf: ConfigService, + private event: EventService ) { this.downloadLink = CURRENT_BASE_HREF + '/systeminfo/getcert'; } ngOnInit() { this.conf.resetConfig(); + if (!this.bannerRefreshSub) { + this.bannerRefreshSub = this.event.subscribe( + HarborEvent.REFRESH_BANNER_MESSAGE, + () => { + this.setValueForBannerMessage(); + } + ); + } + if (this.currentConfig.banner_message) { + this.setValueForBannerMessage(); + } + } + + ngOnDestroy() { + if (this.bannerRefreshSub) { + this.bannerRefreshSub.unsubscribe(); + this.bannerRefreshSub = null; + } + } + + setValueForBannerMessage() { + if (this.currentConfig.banner_message.value) { + this.messageText = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).message; + this.messageType = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).type; + this.messageClosable = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).closable; + this.messageFromDate = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).fromDate; + this.messageToDate = ( + JSON.parse( + this.currentConfig.banner_message.value + ) as BannerMessage + ).toDate; + } else { + this.messageText = null; + this.messageType = BannerMessageType.WARNING; + this.messageClosable = false; + } + this.messageTextCopy = this.messageText; + this.messageTypeCopy = this.messageType; + this.messageClosableCopy = this.messageClosable; + this.messageFromDateCopy = this.messageFromDate; + this.messageToDateCopy = this.messageToDate; } get editable(): boolean { @@ -69,7 +152,17 @@ export class SystemSettingsComponent implements OnInit { } public hasChanges(): boolean { - return !isEmpty(this.getChanges()); + return !isEmpty(this.getChanges()) || this.hasBannerMessageChanged(); + } + + hasBannerMessageChanged() { + return ( + this.messageTextCopy != this.messageText || + this.messageTypeCopy != this.messageType || + this.messageClosableCopy != this.messageClosable || + this.messageFromDateCopy != this.messageFromDate || + this.messageToDateCopy != this.messageToDate + ); } public getChanges() { @@ -96,7 +189,8 @@ export class SystemSettingsComponent implements OnInit { prop === 'audit_log_forward_endpoint' || prop === 'skip_audit_log_database' || prop === 'session_timeout' || - prop === 'scanner_skip_update_pulltime' + prop === 'scanner_skip_update_pulltime' || + prop === 'banner_message' ) { changes[prop] = allChanges[prop]; } @@ -128,6 +222,19 @@ export class SystemSettingsComponent implements OnInit { */ public save(): void { let changes = this.getChanges(); + if (this.hasBannerMessageChanged()) { + const bm = new BannerMessage(); + bm.message = this.messageText; + bm.type = this.messageType; + bm.closable = this.messageClosable; + bm.fromDate = this.messageFromDate; + bm.toDate = this.messageToDate; + if (bm.message) { + changes['banner_message'] = JSON.stringify(bm); + } else { + changes['banner_message'] = ''; + } + } if (!isEmpty(changes)) { this.onGoing = true; this.conf @@ -184,4 +291,8 @@ export class SystemSettingsComponent implements OnInit { this.currentConfig.skip_audit_log_database.value = false; } } + + translateMessageType(type: string): string { + return BannerMessageI18nMap[type] || type; + } } diff --git a/src/portal/src/app/services/app-config.ts b/src/portal/src/app/services/app-config.ts index 2e5c1cad2..0235ffa1a 100644 --- a/src/portal/src/app/services/app-config.ts +++ b/src/portal/src/app/services/app-config.ts @@ -29,6 +29,8 @@ export class AppConfig { registry_storage_provider_name: string; read_only: boolean; show_popular_repo: boolean; + banner_message: string; + current_time: string; constructor() { // Set default value @@ -49,5 +51,6 @@ export class AppConfig { this.registry_storage_provider_name = ''; this.read_only = false; this.show_popular_repo = false; + this.banner_message = ''; } } diff --git a/src/portal/src/app/services/event-service/event.service.ts b/src/portal/src/app/services/event-service/event.service.ts index e9506598f..c920edc59 100644 --- a/src/portal/src/app/services/event-service/event.service.ts +++ b/src/portal/src/app/services/event-service/event.service.ts @@ -81,4 +81,5 @@ export enum HarborEvent { REFRESH_EXPORT_JOBS = 'refreshExportJobs', DELETE_ACCESSORY = 'deleteAccessory', COPY_DIGEST = 'copyDigest', + REFRESH_BANNER_MESSAGE = 'refreshBannerMessage', } diff --git a/src/portal/src/i18n/lang/de-de-lang.json b/src/portal/src/i18n/lang/de-de-lang.json index f425cc341..c38ae7b27 100644 --- a/src/portal/src/i18n/lang/de-de-lang.json +++ b/src/portal/src/i18n/lang/de-de-lang.json @@ -1860,5 +1860,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/en-us-lang.json b/src/portal/src/i18n/lang/en-us-lang.json index e68f0ae1e..1c6e09533 100644 --- a/src/portal/src/i18n/lang/en-us-lang.json +++ b/src/portal/src/i18n/lang/en-us-lang.json @@ -804,7 +804,7 @@ "LABEL": "Labels", "REPOSITORY": "Repository", "REPO_READ_ONLY": "Repository Read Only", - "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks enabled", + "WEBHOOK_NOTIFICATION_ENABLED": "Webhooks Enabled", "SYSTEM": "System Settings", "PROJECT_QUOTAS": "Project Quotas", "VULNERABILITY": "Vulnerability", @@ -834,7 +834,7 @@ "ROOT_CERT_LINK": "Download", "REGISTRY_CERTIFICATE": "Registry certificate", "NO_CHANGE": "Save abort because nothing changed", - "SKIP_SCANNER_PULL_TIME": "Retain image \"last pull time\" on scanning", + "SKIP_SCANNER_PULL_TIME": "Retain Image \"last pull time\" On Scanning", "TOOLTIP": { "SELF_REGISTRATION_ENABLE": "Enable sign up.", "SELF_REGISTRATION_DISABLE": "Deactivate sign up.", @@ -1861,5 +1861,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/es-es-lang.json b/src/portal/src/i18n/lang/es-es-lang.json index fdf1b50ea..9e40ca8a7 100644 --- a/src/portal/src/i18n/lang/es-es-lang.json +++ b/src/portal/src/i18n/lang/es-es-lang.json @@ -1857,5 +1857,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/fr-fr-lang.json b/src/portal/src/i18n/lang/fr-fr-lang.json index 9ffa50706..24f5feb9b 100644 --- a/src/portal/src/i18n/lang/fr-fr-lang.json +++ b/src/portal/src/i18n/lang/fr-fr-lang.json @@ -1827,5 +1827,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Sélectionner le mois, le mois courant est {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Sélectionner l'année, l'année courante est {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Sélectionné" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/pt-br-lang.json b/src/portal/src/i18n/lang/pt-br-lang.json index aea1bdaf4..5f4ad2358 100644 --- a/src/portal/src/i18n/lang/pt-br-lang.json +++ b/src/portal/src/i18n/lang/pt-br-lang.json @@ -1857,5 +1857,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/tr-tr-lang.json b/src/portal/src/i18n/lang/tr-tr-lang.json index 07246c927..19277aac6 100644 --- a/src/portal/src/i18n/lang/tr-tr-lang.json +++ b/src/portal/src/i18n/lang/tr-tr-lang.json @@ -1860,5 +1860,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "Select month, the current month is {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "Select year, the current year is {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - Selected" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/portal/src/i18n/lang/zh-cn-lang.json b/src/portal/src/i18n/lang/zh-cn-lang.json index 982b7ce29..f828778df 100644 --- a/src/portal/src/i18n/lang/zh-cn-lang.json +++ b/src/portal/src/i18n/lang/zh-cn-lang.json @@ -1857,5 +1857,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "选择月, 当前月是 {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "选择年, 当前年是 {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "已选择 - {FULL_DATE}" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "横幅消息", + "MESSAGE_TYPE": "消息类型", + "CLOSABLE": "可关闭", + "FROM": "从", + "TO": "至", + "SUCCESS": "成功", + "INFO": "信息", + "WARNING": "警告", + "DANGER": "危险", + "ENTER_MESSAGE": "请输入消息内容" } } diff --git a/src/portal/src/i18n/lang/zh-tw-lang.json b/src/portal/src/i18n/lang/zh-tw-lang.json index 9c81b58e7..080d89d33 100644 --- a/src/portal/src/i18n/lang/zh-tw-lang.json +++ b/src/portal/src/i18n/lang/zh-tw-lang.json @@ -1849,5 +1849,17 @@ "DATE_PICKER_SELECT_MONTH_TEXT": "選擇月份,目前月份為 {CALENDAR_MONTH}", "DATE_PICKER_SELECT_YEAR_TEXT": "選擇年份,目前年份為 {CALENDAR_YEAR}", "DATE_PICKER_SELECTED_LABEL": "{FULL_DATE} - 已選擇" + }, + "BANNER_MESSAGE": { + "BANNER_MESSAGE": "Banner Message", + "MESSAGE_TYPE": "Message type", + "CLOSABLE": "Closable", + "FROM": "From", + "TO": "To", + "SUCCESS": "Success", + "INFO": "Info", + "WARNING": "Warning", + "DANGER": "Danger", + "ENTER_MESSAGE": "Enter your message here" } } diff --git a/src/server/v2.0/handler/systeminfo.go b/src/server/v2.0/handler/systeminfo.go index 9fa19dfb9..c2d3435ac 100644 --- a/src/server/v2.0/handler/systeminfo.go +++ b/src/server/v2.0/handler/systeminfo.go @@ -86,6 +86,7 @@ func (s *sysInfoAPI) convertInfo(d *si.Data) *models.GeneralInfo { PrimaryAuthMode: &d.PrimaryAuthMode, SelfRegistration: &d.SelfRegistration, HarborVersion: &d.HarborVersion, + BannerMessage: &d.BannerMessage, } if d.AuthProxySettings != nil { res.AuthproxySettings = &models.AuthproxySetting{