feat: improve admin panel styles (#14318)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
Args,
|
Args,
|
||||||
Field,
|
Field,
|
||||||
@@ -189,6 +189,12 @@ class AdminUpdateWorkspaceInput extends PartialType(
|
|||||||
export class AdminWorkspaceResolver {
|
export class AdminWorkspaceResolver {
|
||||||
constructor(private readonly models: Models) {}
|
constructor(private readonly models: Models) {}
|
||||||
|
|
||||||
|
private assertCloudOnly() {
|
||||||
|
if (env.selfhosted) {
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Query(() => [AdminWorkspace], {
|
@Query(() => [AdminWorkspace], {
|
||||||
description: 'List workspaces for admin',
|
description: 'List workspaces for admin',
|
||||||
})
|
})
|
||||||
@@ -196,6 +202,7 @@ export class AdminWorkspaceResolver {
|
|||||||
@Args('filter', { type: () => ListWorkspaceInput })
|
@Args('filter', { type: () => ListWorkspaceInput })
|
||||||
filter: ListWorkspaceInput
|
filter: ListWorkspaceInput
|
||||||
) {
|
) {
|
||||||
|
this.assertCloudOnly();
|
||||||
const { rows } = await this.models.workspace.adminListWorkspaces({
|
const { rows } = await this.models.workspace.adminListWorkspaces({
|
||||||
first: filter.first,
|
first: filter.first,
|
||||||
skip: filter.skip,
|
skip: filter.skip,
|
||||||
@@ -219,6 +226,7 @@ export class AdminWorkspaceResolver {
|
|||||||
@Args('filter', { type: () => ListWorkspaceInput })
|
@Args('filter', { type: () => ListWorkspaceInput })
|
||||||
filter: ListWorkspaceInput
|
filter: ListWorkspaceInput
|
||||||
) {
|
) {
|
||||||
|
this.assertCloudOnly();
|
||||||
const total = await this.models.workspace.adminCountWorkspaces({
|
const total = await this.models.workspace.adminCountWorkspaces({
|
||||||
keyword: filter.keyword,
|
keyword: filter.keyword,
|
||||||
features: filter.features,
|
features: filter.features,
|
||||||
@@ -238,6 +246,7 @@ export class AdminWorkspaceResolver {
|
|||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
async adminWorkspace(@Args('id') id: string) {
|
async adminWorkspace(@Args('id') id: string) {
|
||||||
|
this.assertCloudOnly();
|
||||||
const { rows } = await this.models.workspace.adminListWorkspaces({
|
const { rows } = await this.models.workspace.adminListWorkspaces({
|
||||||
first: 1,
|
first: 1,
|
||||||
skip: 0,
|
skip: 0,
|
||||||
@@ -318,6 +327,7 @@ export class AdminWorkspaceResolver {
|
|||||||
@Args('input', { type: () => AdminUpdateWorkspaceInput })
|
@Args('input', { type: () => AdminUpdateWorkspaceInput })
|
||||||
input: AdminUpdateWorkspaceInput
|
input: AdminUpdateWorkspaceInput
|
||||||
) {
|
) {
|
||||||
|
this.assertCloudOnly();
|
||||||
const { id, features, ...updates } = input;
|
const { id, features, ...updates } = input;
|
||||||
|
|
||||||
if (Object.keys(updates).length) {
|
if (Object.keys(updates).length) {
|
||||||
|
|||||||
@@ -99,7 +99,13 @@ export const App = () => {
|
|||||||
<Route path={ROUTES.admin.accounts} element={<Accounts />} />
|
<Route path={ROUTES.admin.accounts} element={<Accounts />} />
|
||||||
<Route
|
<Route
|
||||||
path={ROUTES.admin.workspaces}
|
path={ROUTES.admin.workspaces}
|
||||||
element={<Workspaces />}
|
element={
|
||||||
|
environment.isSelfHosted ? (
|
||||||
|
<Navigate to={ROUTES.admin.accounts} replace />
|
||||||
|
) : (
|
||||||
|
<Workspaces />
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Route path={`${ROUTES.admin.queue}/*`} element={<Queue />} />
|
<Route path={`${ROUTES.admin.queue}/*`} element={<Queue />} />
|
||||||
<Route path={ROUTES.admin.ai} element={<AI />} />
|
<Route path={ROUTES.admin.ai} element={<AI />} />
|
||||||
|
|||||||
@@ -8,14 +8,17 @@ export const NormalSubItem = ({
|
|||||||
module,
|
module,
|
||||||
title,
|
title,
|
||||||
changeModule,
|
changeModule,
|
||||||
|
indent = 'normal',
|
||||||
}: {
|
}: {
|
||||||
module: string;
|
module: string;
|
||||||
title: string;
|
title: string;
|
||||||
changeModule?: (module: string) => void;
|
changeModule?: (module: string) => void;
|
||||||
|
indent?: 'normal' | 'nested';
|
||||||
}) => {
|
}) => {
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
changeModule?.(module);
|
changeModule?.(module);
|
||||||
}, [changeModule, module]);
|
}, [changeModule, module]);
|
||||||
|
const indentClassName = indent === 'nested' ? 'ml-12' : 'ml-8';
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex">
|
<div className="w-full flex">
|
||||||
<NavLink
|
<NavLink
|
||||||
@@ -25,7 +28,11 @@ export const NormalSubItem = ({
|
|||||||
return cn(
|
return cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
className: `ml-8 px-2 w-full justify-start ${isActive ? 'bg-zinc-100' : ''}`,
|
className: cn(
|
||||||
|
indentClassName,
|
||||||
|
'px-2 w-full justify-start',
|
||||||
|
isActive && 'bg-zinc-100'
|
||||||
|
),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -91,12 +91,14 @@ export function Nav({ isCollapsed = false }: NavProps) {
|
|||||||
label="Accounts"
|
label="Accounts"
|
||||||
isCollapsed={isCollapsed}
|
isCollapsed={isCollapsed}
|
||||||
/>
|
/>
|
||||||
<NavItem
|
{environment.isSelfHosted ? null : (
|
||||||
to="/admin/workspaces"
|
<NavItem
|
||||||
icon={<LayoutDashboardIcon size={18} />}
|
to="/admin/workspaces"
|
||||||
label="Workspaces"
|
icon={<LayoutDashboardIcon size={18} />}
|
||||||
isCollapsed={isCollapsed}
|
label="Workspaces"
|
||||||
/>
|
isCollapsed={isCollapsed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<NavItem
|
<NavItem
|
||||||
to="/admin/queue"
|
to="/admin/queue"
|
||||||
icon={<ListChecksIcon size={18} />}
|
icon={<ListChecksIcon size={18} />}
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
{UNKNOWN_CONFIG_GROUPS.length ? (
|
||||||
|
<li className="flex px-2 pt-1 pb-0.5 text-xs font-medium opacity-70">
|
||||||
|
Experimental
|
||||||
|
</li>
|
||||||
|
) : null}
|
||||||
{UNKNOWN_CONFIG_GROUPS.map(group => (
|
{UNKNOWN_CONFIG_GROUPS.map(group => (
|
||||||
<li key={group.module} className="flex">
|
<li key={group.module} className="flex">
|
||||||
<NavLink
|
<NavLink
|
||||||
@@ -84,7 +89,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
className:
|
className:
|
||||||
'p-2 rounded-[6px] text-[14px] w-full justify-start font-normal',
|
'p-2 pl-6 rounded-[6px] text-[14px] w-full justify-start font-normal',
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
style={({ isActive }) => ({
|
style={({ isActive }) => ({
|
||||||
@@ -154,23 +159,26 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
|
|||||||
changeModule={setCurrentModule}
|
changeModule={setCurrentModule}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<Accordion type="multiple" className="w-full">
|
{UNKNOWN_CONFIG_GROUPS.length ? (
|
||||||
<AccordionItem value="item-1" className="border-b-0">
|
<Accordion type="multiple" className="w-full">
|
||||||
<AccordionTrigger className="ml-8 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360">
|
<AccordionItem value="item-1" className="border-b-0">
|
||||||
Experimental
|
<AccordionTrigger className="ml-8 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360">
|
||||||
</AccordionTrigger>
|
Experimental
|
||||||
<AccordionContent className="flex flex-col gap-1 py-1">
|
</AccordionTrigger>
|
||||||
{UNKNOWN_CONFIG_GROUPS.map(group => (
|
<AccordionContent className="flex flex-col gap-1 py-1">
|
||||||
<NormalSubItem
|
{UNKNOWN_CONFIG_GROUPS.map(group => (
|
||||||
key={group.module}
|
<NormalSubItem
|
||||||
module={group.module}
|
key={group.module}
|
||||||
title={group.name}
|
module={group.module}
|
||||||
changeModule={setCurrentModule}
|
title={group.name}
|
||||||
/>
|
changeModule={setCurrentModule}
|
||||||
))}
|
indent="nested"
|
||||||
</AccordionContent>
|
/>
|
||||||
</AccordionItem>
|
))}
|
||||||
</Accordion>
|
</AccordionContent>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
) : null}
|
||||||
</ScrollAreaPrimitive.Viewport>
|
</ScrollAreaPrimitive.Viewport>
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -37,7 +37,13 @@ type ConfigGroup<T extends AppConfigModule> = {
|
|||||||
const IGNORED_MODULES: (keyof AppConfig)[] = [];
|
const IGNORED_MODULES: (keyof AppConfig)[] = [];
|
||||||
|
|
||||||
if (environment.isSelfHosted) {
|
if (environment.isSelfHosted) {
|
||||||
IGNORED_MODULES.push('payment');
|
IGNORED_MODULES.push(
|
||||||
|
'payment',
|
||||||
|
'customerIo',
|
||||||
|
'captcha',
|
||||||
|
'telemetry',
|
||||||
|
'metrics'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALL_CONFIGURABLE_MODULES = Object.keys(CONFIG_DESCRIPTORS).filter(
|
const ALL_CONFIGURABLE_MODULES = Object.keys(CONFIG_DESCRIPTORS).filter(
|
||||||
|
|||||||
Reference in New Issue
Block a user