Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions console/src/config/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
getEnvironmentdWebsocketScheme,
getFronteggUrl,
} from "./apiUrls";
import { type ConsoleAppearance } from "./appearance";
import { buildConstants } from "./buildConstants";
import { getCloudRegions } from "./cloudRegions";
import { getConsoleEnvironment } from "./consoleEnvironment";
Expand Down Expand Up @@ -95,6 +96,10 @@ interface IBaseAppConfig {
environmentdWebsocketScheme: WebsocketScheme;
// Whether query retries in react-query are enabled
reactQueryRetriesEnabled: boolean;
// How this instance's console distinguishes itself from the consoles of
// other instances. Never set in cloud mode, which serves one console for all
// of an organization's regions.
appearance: ConsoleAppearance | undefined;
}

export class CloudAppConfig implements IBaseAppConfig {
Expand Down Expand Up @@ -200,6 +205,8 @@ export class CloudAppConfig implements IBaseAppConfig {
// Whether the current environment requires user registration outside of the Console. This occurs in production
// when the Console's 'sign up' button links to the Marketing site.
requiresExternalRegistration = this.#consoleEnvironment === "production";

appearance = undefined;
}

export class SelfManagedAppConfig implements IBaseAppConfig {
Expand All @@ -209,6 +216,8 @@ export class SelfManagedAppConfig implements IBaseAppConfig {

balancerdDnsNames: string[] | undefined = appConfigJson.balancerdDnsNames;

appearance: ConsoleAppearance | undefined = appConfigJson.appearance;

environmentdScheme = getEnvironmentdScheme({
buildConstants,
isLocalImpersonation: false,
Expand Down
42 changes: 42 additions & 0 deletions console/src/config/appearance.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

import { documentTitle, parseConsoleAppearance } from "./appearance";

describe("documentTitle", () => {
it("names the instance when it has a display name", () => {
expect(documentTitle({ displayName: "prod" })).toEqual(
"Materialize Console · prod",
);
});

it("falls back to the plain title", () => {
expect(documentTitle(undefined)).toEqual("Materialize Console");
expect(documentTitle({})).toEqual("Materialize Console");
});
});

describe("parseConsoleAppearance", () => {
it("returns undefined when unconfigured", () => {
expect(parseConsoleAppearance(undefined)).toBeUndefined();
expect(parseConsoleAppearance(null)).toBeUndefined();
});

it("keeps a display name", () => {
expect(parseConsoleAppearance({ displayName: "prod" })).toEqual({
displayName: "prod",
});
});

it("treats an empty display name as unset", () => {
expect(parseConsoleAppearance({ displayName: "" })).toEqual({
displayName: undefined,
});
});
});
38 changes: 38 additions & 0 deletions console/src/config/appearance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

/**
* @module
* Per-instance appearance, set on the Materialize resource and delivered to
* the browser in app-config.json. Self-managed only, since Cloud serves one
* console for all of a user's regions.
*/

export interface ConsoleAppearance {
/** A short name for the instance, such as "dev" or "prod". */
displayName?: string;
}

const BASE_DOCUMENT_TITLE = "Materialize Console";

/** Builds the browser tab title, which names the instance when configured. */
export const documentTitle = (appearance: ConsoleAppearance | undefined) =>
appearance?.displayName
? `${BASE_DOCUMENT_TITLE} · ${appearance.displayName}`
: BASE_DOCUMENT_TITLE;

/** Reads the appearance out of app-config.json. */
export const parseConsoleAppearance = (
appearance: { displayName?: string } | null | undefined,
): ConsoleAppearance | undefined => {
if (!appearance) {
return undefined;
}
return { displayName: appearance.displayName || undefined };
};
4 changes: 4 additions & 0 deletions console/src/config/importAppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
// by the Apache License, Version 2.0.

import { type SelfManagedAuthMode } from "./AppConfig";
import { type ConsoleAppearance, parseConsoleAppearance } from "./appearance";

const DEFAULT_APP_CONFIG = {
auth: {
Expand Down Expand Up @@ -35,6 +36,7 @@ export function importAppConfig(): {
mode: SelfManagedAuthMode;
};
balancerdDnsNames?: string[];
appearance?: ConsoleAppearance;
} {
if (process.env.NODE_ENV === "test") {
return DEFAULT_APP_CONFIG;
Expand All @@ -45,9 +47,11 @@ export function importAppConfig(): {
mode: SelfManagedAuthMode;
};
balancerd_dns_names?: string[];
appearance?: { displayName?: string };
};
return {
auth: json.auth,
balancerdDnsNames: json.balancerd_dns_names,
appearance: parseConsoleAppearance(json.appearance),
};
}
4 changes: 4 additions & 0 deletions console/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ import "~/sentry";
import React from "react";
import { createRoot } from "react-dom/client";

import { appConfig } from "~/config/AppConfig";
import { documentTitle } from "~/config/appearance";
import { App } from "~/platform/App";

import { addChunkLoadErrorListener } from "./utils/chunkLoadErrorHandler";

document.title = documentTitle(appConfig.appearance);

const rootEl = document.createElement("div");
document.body.appendChild(rootEl);
const root = createRoot(rootEl);
Expand Down
21 changes: 21 additions & 0 deletions doc/user/data/self_managed/materialize_crd_descriptions_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
"required": false,
"deprecated": false
},
{
"name": "consoleAppearance",
"type": "ConsoleAppearance",
"description": "Appearance overrides for this instance's console.\n\nThis field is excluded from the rollout hash and changes will not trigger a rollout.",
"default": null,
"required": false,
"deprecated": false
},
{
"name": "consoleExternalCertificateSpec",
"type": "MaterializeCertSpec",
Expand Down Expand Up @@ -331,6 +339,19 @@
}
]
],
[
"ConsoleAppearance",
[
{
"name": "displayName",
"type": "String",
"description": "A short name for this instance, such as `dev` or `prod`. The console\nappends it to its browser tab title.",
"default": null,
"required": false,
"deprecated": false
}
]
],
[
"io.k8s.api.core.v1.ResourceRequirements",
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
"required": false,
"deprecated": false
},
{
"name": "consoleAppearance",
"type": "ConsoleAppearance",
"description": "Appearance overrides for this instance's console.",
"default": null,
"required": false,
"deprecated": false
},
{
"name": "consoleExternalCertificateSpec",
"type": "MaterializeCertSpec",
Expand Down Expand Up @@ -355,6 +363,19 @@
}
]
],
[
"ConsoleAppearance",
[
{
"name": "displayName",
"type": "String",
"description": "A short name for this instance, such as `dev` or `prod`. The console\nappends it to its browser tab title.",
"default": null,
"required": false,
"deprecated": false
}
]
],
[
"io.k8s.api.core.v1.ResourceRequirements",
[
Expand Down
14 changes: 14 additions & 0 deletions src/cloud-resources/src/crd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,20 @@ pub struct MaterializeCertSpec {
pub private_key_size: Option<i64>,
}

/// Appearance overrides for an instance's console.
///
/// Consoles are otherwise identical, so an operator running several
/// Materialize instances cannot tell from a browser tab which instance a
/// console is pointed at.
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ConsoleAppearance {
/// A short name for this instance, such as `dev` or `prod`. The console
/// appends it to its browser tab title.
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
}

pub trait ManagedResource: Resource<DynamicType = ()> + Sized {
fn default_labels(&self) -> BTreeMap<String, String> {
BTreeMap::new()
Expand Down
6 changes: 5 additions & 1 deletion src/cloud-resources/src/crd/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use kube::{CustomResource, Resource, ResourceExt};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::crd::{ManagedResource, MaterializeCertSpec, new_resource_id};
use crate::crd::{ConsoleAppearance, ManagedResource, MaterializeCertSpec, new_resource_id};
use mz_server_core::listeners::AuthenticatorKind;

pub mod v1alpha1 {
Expand Down Expand Up @@ -88,6 +88,10 @@ pub mod v1alpha1 {
#[serde(default)]
pub authenticator_kind: AuthenticatorKind,

/// Appearance overrides for this console.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub appearance: Option<ConsoleAppearance>,

// This can be set to override the randomly chosen resource id
pub resource_id: Option<String>,
}
Expand Down
Loading
Loading