feat: Enhance public dashboard functionality with access control and UI updates

This commit is contained in:
Nawaz Dhandala
2026-03-26 19:41:48 +00:00
parent 5b579fa55c
commit 7419ff4437
5 changed files with 147 additions and 115 deletions

View File

@@ -5,12 +5,14 @@ import CardModelDetail from "Common/UI/Components/ModelDetail/CardModelDetail";
import FieldType from "Common/UI/Components/Types/FieldType";
import Navigation from "Common/UI/Utils/Navigation";
import Dashboard from "Common/Models/DatabaseModels/Dashboard";
import React, { Fragment, FunctionComponent, ReactElement } from "react";
import React, { Fragment, FunctionComponent, ReactElement, useState } from "react";
import DashboardPreviewLink from "./DashboardPreviewLink";
const DashboardAuthenticationSettings: FunctionComponent<
PageComponentProps
> = (): ReactElement => {
const modelId: ObjectID = Navigation.getLastParamAsObjectID(1);
const [isPublicDashboard, setIsPublicDashboard] = useState<boolean>(false);
return (
<Fragment>
@@ -47,112 +49,121 @@ const DashboardAuthenticationSettings: FunctionComponent<
},
],
modelId: modelId,
onItemLoaded: (item: Dashboard) => {
setIsPublicDashboard(Boolean(item.isPublicDashboard));
},
}}
/>
<CardModelDetail<Dashboard>
name="Dashboard > Master Password"
cardProps={{
title: "Master Password",
description:
"Rotate the password required to unlock a private dashboard. This value is stored as a secure hash and cannot be retrieved.",
}}
editButtonText="Update Master Password"
isEditable={true}
formFields={[
{
field: {
enableMasterPassword: true,
},
title: "Require Master Password",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
"When enabled, visitors must enter the master password before viewing a private dashboard.",
},
{
field: {
masterPassword: true,
},
title: "Master Password",
fieldType: FormFieldSchemaType.Password,
required: false,
placeholder: "Enter a new master password",
description:
"Updating this value immediately replaces the existing master password.",
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Dashboard,
id: "model-detail-dashboard-master-password",
fields: [
{
field: {
enableMasterPassword: true,
},
fieldType: FieldType.Boolean,
title: "Require Master Password",
placeholder: "No",
},
{
{isPublicDashboard && (
<>
<DashboardPreviewLink modelId={modelId} />
<CardModelDetail<Dashboard>
name="Dashboard > Master Password"
cardProps={{
title: "Master Password",
fieldType: FieldType.Element,
placeholder: "Hidden",
getElement: (): ReactElement => {
return (
<p className="text-sm text-gray-500">
For security reasons, the current master password is never
displayed. Use the update button to set a new password at
any time.
</p>
);
description:
"Rotate the password required to unlock a public dashboard. This value is stored as a secure hash and cannot be retrieved.",
}}
editButtonText="Update Master Password"
isEditable={true}
formFields={[
{
field: {
enableMasterPassword: true,
},
title: "Require Master Password",
fieldType: FormFieldSchemaType.Toggle,
required: false,
description:
"When enabled, visitors must enter the master password before viewing this public dashboard.",
},
},
],
modelId: modelId,
}}
/>
{
field: {
masterPassword: true,
},
title: "Master Password",
fieldType: FormFieldSchemaType.Password,
required: false,
placeholder: "Enter a new master password",
description:
"Updating this value immediately replaces the existing master password.",
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Dashboard,
id: "model-detail-dashboard-master-password",
fields: [
{
field: {
enableMasterPassword: true,
},
fieldType: FieldType.Boolean,
title: "Require Master Password",
placeholder: "No",
},
{
title: "Master Password",
fieldType: FieldType.Element,
placeholder: "Hidden",
getElement: (): ReactElement => {
return (
<p className="text-sm text-gray-500">
For security reasons, the current master password is never
displayed. Use the update button to set a new password at
any time.
</p>
);
},
},
],
modelId: modelId,
}}
/>
<CardModelDetail<Dashboard>
name="Dashboard > IP Whitelist"
cardProps={{
title: "IP Whitelist",
description:
"IP Whitelist for this dashboard. If the dashboard is public then only IP addresses in this whitelist will be able to access the dashboard. If the dashboard is not public then only users who have access from the IP addresses in this whitelist will be able to access the dashboard.",
}}
editButtonText="Edit IP Whitelist"
isEditable={true}
formFields={[
{
field: {
ipWhitelist: true,
},
title: "IP Whitelist",
fieldType: FormFieldSchemaType.LongText,
required: false,
placeholder:
"Please enter the IP addresses or CIDR ranges to whitelist. One per line. This can be IPv4 or IPv6 addresses.",
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Dashboard,
id: "model-detail-dashboard-ip-whitelist",
fields: [
{
field: {
ipWhitelist: true,
},
fieldType: FieldType.LongText,
<CardModelDetail<Dashboard>
name="Dashboard > IP Whitelist"
cardProps={{
title: "IP Whitelist",
placeholder:
"No IP addresses or CIDR ranges whitelisted. This will allow all IP addresses to access the dashboard.",
},
],
modelId: modelId,
}}
/>
description:
"IP Whitelist for this dashboard. Only IP addresses in this whitelist will be able to access the public dashboard.",
}}
editButtonText="Edit IP Whitelist"
isEditable={true}
formFields={[
{
field: {
ipWhitelist: true,
},
title: "IP Whitelist",
fieldType: FormFieldSchemaType.LongText,
required: false,
placeholder:
"Please enter the IP addresses or CIDR ranges to whitelist. One per line. This can be IPv4 or IPv6 addresses.",
},
]}
modelDetailProps={{
showDetailsInNumberOfColumns: 1,
modelType: Dashboard,
id: "model-detail-dashboard-ip-whitelist",
fields: [
{
field: {
ipWhitelist: true,
},
fieldType: FieldType.LongText,
title: "IP Whitelist",
placeholder:
"No IP addresses or CIDR ranges whitelisted. This will allow all IP addresses to access the dashboard.",
},
],
modelId: modelId,
}}
/>
</>
)}
</Fragment>
);
};

View File

@@ -8,7 +8,6 @@ import Navigation from "Common/UI/Utils/Navigation";
import Label from "Common/Models/DatabaseModels/Label";
import Dashboard from "Common/Models/DatabaseModels/Dashboard";
import React, { Fragment, FunctionComponent, ReactElement } from "react";
import DashboardPreviewLink from "./DashboardPreviewLink";
const DashboardView: FunctionComponent<
PageComponentProps
@@ -17,7 +16,6 @@ const DashboardView: FunctionComponent<
return (
<Fragment>
<DashboardPreviewLink modelId={modelId} />
{/* Dashboard View */}
<CardModelDetail<Dashboard>
name="Dashboard > Dashboard Details"

View File

@@ -65,6 +65,7 @@ const App: () => JSX.Element = () => {
const [error, setError] = useState<string | null>(null);
const [dashboardId, setDashboardId] = useState<ObjectID | null>(null);
const [dashboardName, setDashboardName] = useState<string>("Dashboard");
const [isPublicDashboard, setIsPublicDashboard] = useState<boolean>(false);
type GetIdFunction = () => Promise<ObjectID>;
const getId: GetIdFunction = async (): Promise<ObjectID> => {
@@ -121,11 +122,13 @@ const App: () => JSX.Element = () => {
const enableMasterPassword: boolean = Boolean(
response.data["enableMasterPassword"],
);
const isPublicDashboard: boolean = Boolean(
const isPublic: boolean = Boolean(
response.data["isPublicDashboard"],
);
if (!isPublicDashboard && enableMasterPassword) {
setIsPublicDashboard(isPublic);
if (isPublic && enableMasterPassword) {
PublicDashboardUtil.setRequiresMasterPassword(true);
} else {
PublicDashboardUtil.setRequiresMasterPassword(false);
@@ -151,6 +154,15 @@ const App: () => JSX.Element = () => {
);
}
// If dashboard is not public, show 404
if (!isPublicDashboard) {
return (
<Suspense fallback={<PageLoader isVisible={true} />}>
<NotFoundPage />
</Suspense>
);
}
// Check if master password is required and not validated
if (
dashboardId &&

View File

@@ -243,9 +243,9 @@ export default class DashboardAPI extends BaseAPI<
throw new NotFoundException("Dashboard not found");
}
if (dashboard.isPublicDashboard) {
if (!dashboard.isPublicDashboard) {
throw new BadDataException(
"This dashboard is already visible to everyone.",
"This dashboard is not publicly accessible.",
);
}

View File

@@ -87,6 +87,16 @@ export class Service extends DatabaseService<Model> {
},
});
// If dashboard is not public, deny access
if (dashboard && !dashboard.isPublicDashboard) {
return {
hasReadAccess: false,
error: new NotAuthenticatedException(
"This dashboard is not available.",
),
};
}
if (dashboard?.ipWhitelist && dashboard.ipWhitelist.length > 0) {
const ipWhitelist: Array<string> = dashboard.ipWhitelist?.split("\n");
@@ -129,17 +139,11 @@ export class Service extends DatabaseService<Model> {
}
}
if (dashboard && dashboard.isPublicDashboard) {
return {
hasReadAccess: true,
};
}
const shouldEnforceMasterPassword: boolean = Boolean(
dashboard &&
dashboard.isPublicDashboard &&
dashboard.enableMasterPassword &&
dashboard.masterPassword &&
!dashboard.isPublicDashboard,
dashboard.masterPassword,
);
if (shouldEnforceMasterPassword) {
@@ -162,6 +166,13 @@ export class Service extends DatabaseService<Model> {
),
};
}
// Public dashboard without master password - grant access
if (dashboard && dashboard.isPublicDashboard) {
return {
hasReadAccess: true,
};
}
} catch (err) {
logger.error(err);
}
@@ -169,7 +180,7 @@ export class Service extends DatabaseService<Model> {
return {
hasReadAccess: false,
error: new NotAuthenticatedException(
"You do not have access to this dashboard. Please login to view the dashboard.",
"You do not have access to this dashboard.",
),
};
}