diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html
index d2107ef1ea..52e6e98b0d 100644
--- a/src/app/_layout/app-header/app-header.component.html
+++ b/src/app/_layout/app-header/app-header.component.html
@@ -209,4 +209,8 @@
+
diff --git a/src/app/_layout/app-header/app-header.component.ts b/src/app/_layout/app-header/app-header.component.ts
index 836b9f07ca..b0afe868d4 100644
--- a/src/app/_layout/app-header/app-header.component.ts
+++ b/src/app/_layout/app-header/app-header.component.ts
@@ -27,6 +27,7 @@ import { map, Observable, Subscription } from "rxjs";
})
export class AppHeaderComponent implements OnInit {
private sub: Subscription;
+ showStatusBanner: boolean;
config = this.appConfigService.getConfig();
facility = this.config.facility ?? "";
@@ -89,6 +90,8 @@ export class AppHeaderComponent implements OnInit {
this.isSiteHeaderLogoUrlExternal$ = this.siteHeaderLogoUrl$.pipe(
map((siteHeaderLogoUrl) => this.isFullUrl(siteHeaderLogoUrl)),
);
+
+ this.showStatusBanner = !!this.config.statusBannerMessage;
}
logout(): void {
diff --git a/src/app/_layout/app-header/status-banner/status-banner.component.html b/src/app/_layout/app-header/status-banner/status-banner.component.html
new file mode 100644
index 0000000000..3c3bb58f40
--- /dev/null
+++ b/src/app/_layout/app-header/status-banner/status-banner.component.html
@@ -0,0 +1,12 @@
+
+
+
+ {{ code === "INFO" ? "info" : "warning" }}
+
+
+
+
+
+
diff --git a/src/app/_layout/app-header/status-banner/status-banner.component.scss b/src/app/_layout/app-header/status-banner/status-banner.component.scss
new file mode 100644
index 0000000000..5e3b45ce35
--- /dev/null
+++ b/src/app/_layout/app-header/status-banner/status-banner.component.scss
@@ -0,0 +1,37 @@
+.spacer {
+ flex: 1 1 auto;
+}
+
+.toolbar {
+ height: fit-content;
+ border: none;
+ box-shadow: none;
+}
+
+.status-message {
+ text-align: center;
+ text-wrap: wrap;
+ width: 100%;
+ font-size: 16px;
+ line-height: 20px;
+ padding: 10px 0;
+}
+.status-icon {
+ margin-right: 6px;
+ font-size: 20px;
+ vertical-align: middle;
+}
+.toolbar-WARN {
+ background-color: #feefb3;
+ color: #9f6000;
+}
+
+.toolbar-INFO {
+ color: #4f8a10;
+ background-color: #dff2bf;
+}
+
+:host ::ng-deep a {
+ text-decoration: underline;
+ color: inherit;
+}
diff --git a/src/app/_layout/app-header/status-banner/status-banner.component.spec.ts b/src/app/_layout/app-header/status-banner/status-banner.component.spec.ts
new file mode 100644
index 0000000000..b4c8430574
--- /dev/null
+++ b/src/app/_layout/app-header/status-banner/status-banner.component.spec.ts
@@ -0,0 +1,61 @@
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+
+import { StatusBannerComponent } from "./status-banner.component";
+import { CommonModule } from "@angular/common";
+import { MatButtonModule } from "@angular/material/button";
+import { MatIconModule } from "@angular/material/icon";
+import { MatToolbarModule } from "@angular/material/toolbar";
+import { AppConfigInterface, AppConfigService } from "app-config.service";
+
+describe("StatusBannerComponent", () => {
+ let component: StatusBannerComponent;
+ let fixture: ComponentFixture;
+ let appConfigServiceSpy: jasmine.SpyObj;
+
+ beforeEach(async () => {
+ appConfigServiceSpy = jasmine.createSpyObj("AppConfigService", [
+ "getConfig",
+ ]);
+ appConfigServiceSpy.getConfig.and.returnValue({
+ statusBannerMessage: "test",
+ statusBannerCode: "INFO",
+ } as AppConfigInterface);
+ await TestBed.configureTestingModule({
+ declarations: [StatusBannerComponent],
+ imports: [CommonModule, MatToolbarModule, MatButtonModule, MatIconModule],
+ providers: [
+ {
+ provide: AppConfigService,
+ useValue: appConfigServiceSpy,
+ },
+ ],
+ }).compileComponents();
+ });
+
+ function initComponent() {
+ fixture = TestBed.createComponent(StatusBannerComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ }
+
+ it("should create", () => {
+ initComponent();
+ expect(component).toBeTruthy();
+ });
+
+ it("should set the status banner message and code from the app config", () => {
+ initComponent();
+ expect(component.message).toBe("test");
+ expect(component.code).toBe("INFO");
+ });
+
+ it("should set default code to INFO when neither INFO or WARN is provided", () => {
+ appConfigServiceSpy.getConfig.and.returnValue({
+ statusBannerMessage: "test123",
+ statusBannerCode: "ERROR",
+ } as unknown as AppConfigInterface);
+ initComponent();
+ expect(component.code).toBe("INFO");
+ expect(component.message).toBe("test123");
+ });
+});
diff --git a/src/app/_layout/app-header/status-banner/status-banner.component.ts b/src/app/_layout/app-header/status-banner/status-banner.component.ts
new file mode 100644
index 0000000000..2bf43ee93b
--- /dev/null
+++ b/src/app/_layout/app-header/status-banner/status-banner.component.ts
@@ -0,0 +1,28 @@
+import { Component, Output, EventEmitter } from "@angular/core";
+import { AppConfigService } from "app-config.service";
+
+@Component({
+ selector: "app-status-banner",
+ templateUrl: "./status-banner.component.html",
+ styleUrls: ["./status-banner.component.scss"],
+ standalone: false,
+})
+export class StatusBannerComponent {
+ @Output() dismiss = new EventEmitter();
+ message: string;
+ code: "INFO" | "WARN";
+
+ constructor(private appConfigService: AppConfigService) {
+ const appConfig = this.appConfigService.getConfig();
+ this.message = appConfig.statusBannerMessage;
+ if (["INFO", "WARN"].includes(appConfig.statusBannerCode)) {
+ this.code = appConfig.statusBannerCode;
+ } else {
+ this.code = "INFO";
+ }
+ }
+
+ onDismiss() {
+ this.dismiss.emit();
+ }
+}
diff --git a/src/app/_layout/layout.module.ts b/src/app/_layout/layout.module.ts
index ea544d467b..5bac31ee29 100644
--- a/src/app/_layout/layout.module.ts
+++ b/src/app/_layout/layout.module.ts
@@ -13,10 +13,12 @@ import { BatchCardModule } from "datasets/batch-card/batch-card.module";
import { BreadcrumbModule } from "shared/modules/breadcrumb/breadcrumb.module";
import { UsersModule } from "../users/users.module";
import { MatChipsModule } from "@angular/material/chips";
+import { StatusBannerComponent } from "./app-header/status-banner/status-banner.component";
import { MatTooltipModule } from "@angular/material/tooltip";
@NgModule({
declarations: [
+ StatusBannerComponent,
AppLayoutComponent,
AppHeaderComponent,
AppMainLayoutComponent,
diff --git a/src/app/admin/schema/frontend.config.jsonforms.json b/src/app/admin/schema/frontend.config.jsonforms.json
index e57e631d2b..336c7e57a0 100644
--- a/src/app/admin/schema/frontend.config.jsonforms.json
+++ b/src/app/admin/schema/frontend.config.jsonforms.json
@@ -99,6 +99,12 @@
"metadataEditingUnitListDisabled": { "type": "boolean" },
"hideEmptyMetadataTable": { "type": "boolean" },
"datafilesActionsEnabled": { "type": "boolean" },
+ "statusBannerMessage": { "type": "string" },
+ "statusBannerCode": {
+ "type": "string",
+ "enum": ["INFO", "WARN"],
+ "default": "INFO"
+ },
"datafilesActions": {
"type": "array",
"items": {
@@ -409,6 +415,8 @@
{ "type": "Control", "scope": "#/properties/siteIcon" },
{ "type": "Control", "scope": "#/properties/siteTitle" },
{ "type": "Control", "scope": "#/properties/siteSciCatLogo" },
+ { "type": "Control", "scope": "#/properties/statusBannerMessage" },
+ { "type": "Control", "scope": "#/properties/statusBannerCode" },
{
"type": "Control",
"scope": "#/properties/checkBoxFilterClickTrigger"
diff --git a/src/app/app-config.service.ts b/src/app/app-config.service.ts
index 1ba2952809..7debb96695 100644
--- a/src/app/app-config.service.ts
+++ b/src/app/app-config.service.ts
@@ -167,6 +167,8 @@ export interface AppConfigInterface {
hideEmptyMetadataTable?: boolean;
ingestorComponent?: IngestorComponentConfig;
defaultTab?: DefaultTab;
+ statusBannerMessage?: string;
+ statusBannerCode?: "INFO" | "WARN";
}
function isMainPageConfiguration(obj: any): obj is MainPageConfiguration {
@@ -275,7 +277,6 @@ export class AppConfigService {
if (!this.appConfig) {
console.error("AppConfigService: Configuration not loaded!");
}
-
return this.appConfig as AppConfigInterface;
}
}