mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 02:05:41 +01:00
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 <sshijun@vmware.com>
This commit is contained in:
parent
8fe561865d
commit
ef96c729c0
@ -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)
|
||||
[![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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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`},
|
||||
}
|
||||
)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -1,4 +1,13 @@
|
||||
<clr-alerts>
|
||||
<clr-alert
|
||||
*ngIf="hasValidBannerMessage() && isLogin()"
|
||||
[clrAlertType]="getBannerMessageType()"
|
||||
[clrAlertAppLevel]="true"
|
||||
[clrAlertClosable]="getBannerMessageClosable()">
|
||||
<clr-alert-item>
|
||||
<span class="alert-text">{{ getBannerMessage() }}</span>
|
||||
</clr-alert-item>
|
||||
</clr-alert>
|
||||
<clr-alert
|
||||
*ngIf="showReadOnly && isLogin()"
|
||||
[clrAlertType]="'warning'"
|
||||
|
@ -15,6 +15,10 @@ import { MessageService } from '../../../shared/components/global-message/messag
|
||||
import { Message } from '../../../shared/components/global-message/message';
|
||||
import { JobServiceDashboardHealthCheckService } from '../../left-side-nav/job-service-dashboard/job-service-dashboard-health-check.service';
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import {
|
||||
BannerMessage,
|
||||
BannerMessageType,
|
||||
} from '../../left-side-nav/config/config';
|
||||
const HAS_SHOWED_SCANNER_INFO: string = 'hasShowScannerInfo';
|
||||
const YES: string = 'yes';
|
||||
@Component({
|
||||
@ -185,4 +189,79 @@ export class AppLevelAlertsComponent implements OnInit, OnDestroy {
|
||||
isLogin(): boolean {
|
||||
return !!this.session.getCurrentUser();
|
||||
}
|
||||
|
||||
hasValidBannerMessage(): boolean {
|
||||
if (
|
||||
this.appConfigService.getConfig()?.banner_message &&
|
||||
this.appConfigService.getConfig()?.current_time
|
||||
) {
|
||||
const current = new Date(
|
||||
this.appConfigService.getConfig()?.current_time
|
||||
);
|
||||
const bm = JSON.parse(
|
||||
this.appConfigService.getConfig()?.banner_message
|
||||
) as BannerMessage;
|
||||
if (bm?.fromDate && bm?.toDate) {
|
||||
return (
|
||||
new Date(current) <= new Date(bm.toDate) &&
|
||||
new Date(current) >= 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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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',
|
||||
};
|
||||
|
@ -346,6 +346,127 @@
|
||||
" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label">{{
|
||||
'BANNER_MESSAGE.BANNER_MESSAGE' | translate
|
||||
}}</label>
|
||||
<div class="clr-control-container flex-baseline">
|
||||
<div class="clr-textarea-wrapper">
|
||||
<textarea
|
||||
id="banner-message"
|
||||
placeholder="{{
|
||||
'BANNER_MESSAGE.ENTER_MESSAGE' | translate
|
||||
}}"
|
||||
autocomplete="off"
|
||||
class="clr-textarea"
|
||||
[(ngModel)]="messageText"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[disabled]="
|
||||
!currentConfig.banner_message.editable
|
||||
"></textarea>
|
||||
</div>
|
||||
<div class="message-type">
|
||||
<div
|
||||
class="clr-select-wrapper"
|
||||
[ngClass]="{
|
||||
'clr-form-control-disable':
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
}">
|
||||
<label class="message-label">{{
|
||||
'BANNER_MESSAGE.MESSAGE_TYPE' | translate
|
||||
}}</label>
|
||||
<select
|
||||
id="banner-message-type"
|
||||
class="clr-select message-select"
|
||||
[(ngModel)]="messageType"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[disabled]="
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
">
|
||||
<option
|
||||
*ngFor="let t of bannerMessageTypes"
|
||||
value="{{ t }}">
|
||||
{{ translateMessageType(t) | translate }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="clr-checkbox-wrapper ml-1"
|
||||
[ngClass]="{
|
||||
'clr-form-control-disable':
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
}">
|
||||
<input
|
||||
class="clr-checkbox-inline"
|
||||
type="checkbox"
|
||||
[(ngModel)]="messageClosable"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[disabled]="
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
"
|
||||
id="banner-message-closable" />
|
||||
<label
|
||||
class="clr-control-label"
|
||||
for="banner-message-closable"
|
||||
>{{ 'BANNER_MESSAGE.CLOSABLE' | translate }}</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clr-form-control">
|
||||
<label class="clr-control-label"></label>
|
||||
<div class="clr-control-container flex-baseline">
|
||||
<div class="clr-textarea-wrapper duration">
|
||||
<label>{{ 'REPLICATION.DURATION' | translate }}</label>
|
||||
</div>
|
||||
<div
|
||||
class="clr-input-wrapper flex message-type"
|
||||
[ngClass]="{
|
||||
'clr-form-control-disable':
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
}">
|
||||
<label>{{ 'BANNER_MESSAGE.FROM' | translate }}</label>
|
||||
<input
|
||||
class="date"
|
||||
type="date"
|
||||
id="from"
|
||||
clrDate
|
||||
[(ngModel)]="messageFromDate"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
[disabled]="
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
" />
|
||||
</div>
|
||||
<div
|
||||
class="clr-checkbox-wrapper flex ml-1"
|
||||
[ngClass]="{
|
||||
'clr-form-control-disable':
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
}">
|
||||
<label>{{ 'BANNER_MESSAGE.TO' | translate }}</label>
|
||||
<input
|
||||
class="date"
|
||||
clrDate
|
||||
type="date"
|
||||
id="to"
|
||||
[(ngModel)]="messageToDate"
|
||||
[disabled]="
|
||||
!currentConfig.banner_message.editable ||
|
||||
!messageText
|
||||
"
|
||||
[ngModelOptions]="{ standalone: true }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<div>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 = '';
|
||||
}
|
||||
}
|
||||
|
@ -81,4 +81,5 @@ export enum HarborEvent {
|
||||
REFRESH_EXPORT_JOBS = 'refreshExportJobs',
|
||||
DELETE_ACCESSORY = 'deleteAccessory',
|
||||
COPY_DIGEST = 'copyDigest',
|
||||
REFRESH_BANNER_MESSAGE = 'refreshBannerMessage',
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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": "请输入消息内容"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user