mirror of
https://github.com/docmost/docmost.git
synced 2026-08-29 10:05:03 +08:00
merge main
This commit is contained in:
@@ -11,6 +11,9 @@ import { getLicenseInfo } from "@/ee/licence/services/license-service.ts";
|
||||
import { getSsoProviders } from "@/ee/security/services/security-service.ts";
|
||||
import { getShares } from "@/features/share/services/share-service.ts";
|
||||
import { getApiKeys } from "@/ee/api-key";
|
||||
import { getAuditLogs } from "@/ee/audit/services/audit-service";
|
||||
import { getVerificationList } from "@/ee/page-verification/services/page-verification-service";
|
||||
import { getScimTokens } from "@/ee/scim/services/scim-token-service";
|
||||
|
||||
export const prefetchWorkspaceMembers = () => {
|
||||
const params: QueryParams = { limit: 100, query: "" };
|
||||
@@ -80,3 +83,26 @@ export const prefetchApiKeyManagement = () => {
|
||||
queryFn: () => getApiKeys({ adminView: true }),
|
||||
});
|
||||
};
|
||||
|
||||
export const prefetchAuditLogs = () => {
|
||||
const params = { limit: 50 };
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ["audit-logs", params],
|
||||
queryFn: () => getAuditLogs(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const prefetchVerifiedPages = () => {
|
||||
const params = { limit: 50 };
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ["verification-list", params],
|
||||
queryFn: () => getVerificationList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const prefetchScimTokens = () => {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: ["scim-token-list", { cursor: undefined }],
|
||||
queryFn: () => getScimTokens({}),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
IconWorld,
|
||||
IconSparkles,
|
||||
IconPlug,
|
||||
IconHistory,
|
||||
IconShieldCheck,
|
||||
} from "@tabler/icons-react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import classes from "./settings.module.css";
|
||||
@@ -21,38 +23,41 @@ import { useTranslation } from "react-i18next";
|
||||
import { isCloud } from "@/lib/config.ts";
|
||||
import useUserRole from "@/hooks/use-user-role.tsx";
|
||||
import { useAtom } from "jotai";
|
||||
import { workspaceAtom } from "@/features/user/atoms/current-user-atom.ts";
|
||||
import { entitlementAtom } from "@/ee/entitlement/entitlement-atom";
|
||||
import { Feature } from "@/ee/features";
|
||||
import { useUpgradeLabel } from "@/ee/hooks/use-upgrade-label";
|
||||
import {
|
||||
prefetchApiKeyManagement,
|
||||
prefetchApiKeys,
|
||||
prefetchBilling,
|
||||
prefetchGroups,
|
||||
prefetchLicense,
|
||||
prefetchScimTokens,
|
||||
prefetchShares,
|
||||
prefetchSpaces,
|
||||
prefetchSsoProviders,
|
||||
prefetchWorkspaceMembers,
|
||||
prefetchAuditLogs,
|
||||
prefetchVerifiedPages,
|
||||
} from "@/components/settings/settings-queries.tsx";
|
||||
import AppVersion from "@/components/settings/app-version.tsx";
|
||||
import { mobileSidebarAtom } from "@/components/layouts/global/hooks/atoms/sidebar-atom.ts";
|
||||
import { useToggleSidebar } from "@/components/layouts/global/hooks/hooks/use-toggle-sidebar.ts";
|
||||
import { useSettingsNavigation } from "@/hooks/use-settings-navigation";
|
||||
|
||||
interface DataItem {
|
||||
type DataItem = {
|
||||
label: string;
|
||||
icon: React.ElementType;
|
||||
path: string;
|
||||
isCloud?: boolean;
|
||||
isEnterprise?: boolean;
|
||||
isAdmin?: boolean;
|
||||
isSelfhosted?: boolean;
|
||||
showDisabledInNonEE?: boolean;
|
||||
}
|
||||
feature?: string;
|
||||
role?: "admin" | "owner";
|
||||
env?: "cloud" | "selfhosted";
|
||||
};
|
||||
|
||||
interface DataGroup {
|
||||
type DataGroup = {
|
||||
heading: string;
|
||||
items: DataItem[];
|
||||
}
|
||||
};
|
||||
|
||||
const groupedData: DataGroup[] = [
|
||||
{
|
||||
@@ -68,9 +73,7 @@ const groupedData: DataGroup[] = [
|
||||
label: "API keys",
|
||||
icon: IconKey,
|
||||
path: "/settings/account/api-keys",
|
||||
isCloud: true,
|
||||
isEnterprise: true,
|
||||
showDisabledInNonEE: true,
|
||||
feature: Feature.API_KEYS,
|
||||
},
|
||||
{
|
||||
label: "Connections",
|
||||
@@ -83,50 +86,56 @@ const groupedData: DataGroup[] = [
|
||||
heading: "Workspace",
|
||||
items: [
|
||||
{ label: "General", icon: IconSettings, path: "/settings/workspace" },
|
||||
{
|
||||
label: "Members",
|
||||
icon: IconUsers,
|
||||
path: "/settings/members",
|
||||
},
|
||||
{ label: "Members", icon: IconUsers, path: "/settings/members" },
|
||||
{
|
||||
label: "Billing",
|
||||
icon: IconCoin,
|
||||
path: "/settings/billing",
|
||||
isCloud: true,
|
||||
isAdmin: true,
|
||||
role: "admin",
|
||||
env: "cloud",
|
||||
},
|
||||
{
|
||||
label: "Security & SSO",
|
||||
icon: IconLock,
|
||||
path: "/settings/security",
|
||||
isCloud: true,
|
||||
isEnterprise: true,
|
||||
isAdmin: true,
|
||||
showDisabledInNonEE: true,
|
||||
feature: Feature.SECURITY_SETTINGS,
|
||||
role: "admin",
|
||||
},
|
||||
{ label: "Groups", icon: IconUsersGroup, path: "/settings/groups" },
|
||||
{ label: "Spaces", icon: IconSpaces, path: "/settings/spaces" },
|
||||
{ label: "Public sharing", icon: IconWorld, path: "/settings/sharing" },
|
||||
{
|
||||
label: "Verified pages",
|
||||
icon: IconShieldCheck,
|
||||
path: "/settings/verifications",
|
||||
feature: Feature.PAGE_VERIFICATION,
|
||||
},
|
||||
{
|
||||
label: "API management",
|
||||
icon: IconKey,
|
||||
path: "/settings/api-keys",
|
||||
isCloud: true,
|
||||
isEnterprise: true,
|
||||
isAdmin: true,
|
||||
showDisabledInNonEE: true,
|
||||
feature: Feature.API_KEYS,
|
||||
role: "admin",
|
||||
},
|
||||
{
|
||||
label: "AI settings",
|
||||
icon: IconSparkles,
|
||||
path: "/settings/ai",
|
||||
isAdmin: true,
|
||||
role: "admin",
|
||||
},
|
||||
{
|
||||
label: "Audit log",
|
||||
icon: IconHistory,
|
||||
path: "/settings/audit",
|
||||
feature: Feature.AUDIT_LOGS,
|
||||
role: "owner",
|
||||
env: "selfhosted",
|
||||
},
|
||||
{
|
||||
label: "Integrations",
|
||||
icon: IconPlug,
|
||||
path: "/settings/integrations",
|
||||
isAdmin: true,
|
||||
role: "admin",
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -147,8 +156,9 @@ export default function SettingsSidebar() {
|
||||
const location = useLocation();
|
||||
const [active, setActive] = useState(location.pathname);
|
||||
const { goBack } = useSettingsNavigation();
|
||||
const { isAdmin } = useUserRole();
|
||||
const [workspace] = useAtom(workspaceAtom);
|
||||
const { isAdmin, isOwner } = useUserRole();
|
||||
const [entitlements] = useAtom(entitlementAtom);
|
||||
const upgradeLabel = useUpgradeLabel();
|
||||
const [mobileSidebarOpened] = useAtom(mobileSidebarAtom);
|
||||
const toggleMobileSidebar = useToggleSidebar(mobileSidebarAtom);
|
||||
|
||||
@@ -156,41 +166,20 @@ export default function SettingsSidebar() {
|
||||
setActive(location.pathname);
|
||||
}, [location.pathname]);
|
||||
|
||||
const hasFeature = (f: string) =>
|
||||
entitlements?.features?.includes(f) ?? false;
|
||||
|
||||
const canShowItem = (item: DataItem) => {
|
||||
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||
// Check admin permission regardless of license
|
||||
return item.isAdmin ? isAdmin : true;
|
||||
}
|
||||
|
||||
if (item.isCloud && item.isEnterprise) {
|
||||
if (!(isCloud() || workspace?.hasLicenseKey)) return false;
|
||||
return item.isAdmin ? isAdmin : true;
|
||||
}
|
||||
|
||||
if (item.isCloud) {
|
||||
return isCloud() ? (item.isAdmin ? isAdmin : true) : false;
|
||||
}
|
||||
|
||||
if (item.isSelfhosted) {
|
||||
return !isCloud() ? (item.isAdmin ? isAdmin : true) : false;
|
||||
}
|
||||
|
||||
if (item.isEnterprise) {
|
||||
return workspace?.hasLicenseKey ? (item.isAdmin ? isAdmin : true) : false;
|
||||
}
|
||||
|
||||
if (item.isAdmin) {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
if (item.env === "cloud" && !isCloud()) return false;
|
||||
if (item.env === "selfhosted" && isCloud()) return false;
|
||||
if (item.role === "admin" && !isAdmin) return false;
|
||||
if (item.role === "owner" && !isOwner) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const isItemDisabled = (item: DataItem) => {
|
||||
if (item.showDisabledInNonEE && item.isEnterprise) {
|
||||
return !(isCloud() || workspace?.hasLicenseKey);
|
||||
}
|
||||
return false;
|
||||
if (!item.feature) return false;
|
||||
return !hasFeature(item.feature);
|
||||
};
|
||||
|
||||
const menuItems = groupedData.map((group) => {
|
||||
@@ -223,12 +212,15 @@ export default function SettingsSidebar() {
|
||||
prefetchHandler = prefetchBilling;
|
||||
break;
|
||||
case "License & Edition":
|
||||
if (workspace?.hasLicenseKey) {
|
||||
if (entitlements?.tier !== "free") {
|
||||
prefetchHandler = prefetchLicense;
|
||||
}
|
||||
break;
|
||||
case "Security & SSO":
|
||||
prefetchHandler = prefetchSsoProviders;
|
||||
prefetchHandler = () => {
|
||||
prefetchSsoProviders();
|
||||
prefetchScimTokens();
|
||||
};
|
||||
break;
|
||||
case "Public sharing":
|
||||
prefetchHandler = prefetchShares;
|
||||
@@ -239,52 +231,61 @@ export default function SettingsSidebar() {
|
||||
case "API management":
|
||||
prefetchHandler = prefetchApiKeyManagement;
|
||||
break;
|
||||
case "Audit log":
|
||||
prefetchHandler = prefetchAuditLogs;
|
||||
break;
|
||||
case "Verified pages":
|
||||
prefetchHandler = prefetchVerifiedPages;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const isDisabled = isItemDisabled(item);
|
||||
const linkElement = (
|
||||
|
||||
if (isDisabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
key={item.label}
|
||||
label={upgradeLabel}
|
||||
position="right"
|
||||
withArrow
|
||||
>
|
||||
<span
|
||||
className={classes.link}
|
||||
data-disabled
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
tabIndex={0}
|
||||
style={{
|
||||
opacity: 0.5,
|
||||
cursor: "not-allowed",
|
||||
}}
|
||||
>
|
||||
<item.icon className={classes.linkIcon} stroke={2} />
|
||||
<span>{t(item.label)}</span>
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Link
|
||||
onMouseEnter={!isDisabled ? prefetchHandler : undefined}
|
||||
onMouseEnter={prefetchHandler}
|
||||
className={classes.link}
|
||||
data-active={active.startsWith(item.path) || undefined}
|
||||
data-disabled={isDisabled || undefined}
|
||||
key={item.label}
|
||||
to={isDisabled ? "#" : item.path}
|
||||
onClick={(e) => {
|
||||
if (isDisabled) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
to={item.path}
|
||||
onClick={() => {
|
||||
if (mobileSidebarOpened) {
|
||||
toggleMobileSidebar();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
opacity: isDisabled ? 0.5 : 1,
|
||||
cursor: isDisabled ? "not-allowed" : "pointer",
|
||||
}}
|
||||
>
|
||||
<item.icon className={classes.linkIcon} stroke={2} />
|
||||
<span>{t(item.label)}</span>
|
||||
</Link>
|
||||
);
|
||||
|
||||
if (isDisabled) {
|
||||
return (
|
||||
<Tooltip
|
||||
key={item.label}
|
||||
label={t("Available in enterprise edition")}
|
||||
position="right"
|
||||
withArrow
|
||||
>
|
||||
{linkElement}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return linkElement;
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
@@ -302,7 +303,7 @@ export default function SettingsSidebar() {
|
||||
}}
|
||||
variant="transparent"
|
||||
c="gray"
|
||||
aria-label="Back"
|
||||
aria-label={t("Back")}
|
||||
>
|
||||
<IconArrowLeft stroke={2} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Divider, Title } from '@mantine/core';
|
||||
export default function SettingsTitle({ title }: { title: string }) {
|
||||
return (
|
||||
<>
|
||||
<Title order={3}>
|
||||
<Title order={1} size="h3">
|
||||
{title}
|
||||
</Title>
|
||||
<Divider my="md" />
|
||||
|
||||
Reference in New Issue
Block a user