UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: вкладка хранилища в модальных настройках
This commit is contained in:
parent
d9f534efcd
commit
c3d2d78724
|
|
@ -0,0 +1,13 @@
|
|||
import { redirect } from "react-router";
|
||||
import type { Route } from "./+types/page";
|
||||
|
||||
export function clientLoader({ params }: Route.ClientLoaderArgs) {
|
||||
const { workspaceSlug } = params;
|
||||
throw redirect(`/${workspaceSlug}/?workspaceSettings=storage`);
|
||||
}
|
||||
|
||||
function StorageWorkspaceSettingsPage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
export default StorageWorkspaceSettingsPage;
|
||||
|
|
@ -281,6 +281,10 @@ export const coreRoutes: RouteConfigEntry[] = [
|
|||
":workspaceSlug/settings/exports",
|
||||
"./(all)/[workspaceSlug]/(settings)/settings/(workspace)/exports/page.tsx"
|
||||
),
|
||||
route(
|
||||
":workspaceSlug/settings/storage",
|
||||
"./(all)/[workspaceSlug]/(settings)/settings/(workspace)/storage/page.tsx"
|
||||
),
|
||||
route(
|
||||
":workspaceSlug/settings/webhooks",
|
||||
"./(all)/[workspaceSlug]/(settings)/settings/(workspace)/webhooks/page.tsx"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { cn } from "@plane/utils";
|
|||
// components
|
||||
import { AppSidebarItem } from "@/components/sidebar/sidebar-item";
|
||||
import {
|
||||
getWorkspaceSettingsModalTabFromSearch,
|
||||
openWorkspaceSettingsModal,
|
||||
WORKSPACE_SETTINGS_MODAL_EVENT,
|
||||
} from "@/components/workspace/settings/workspace-settings-modal.utils";
|
||||
|
|
@ -35,13 +36,13 @@ export const AppRailRoot = observer(() => {
|
|||
const { preferences, updateDisplayMode } = useAppRailPreferences();
|
||||
const { isCollapsed, toggleAppRail } = useAppRailVisibility();
|
||||
const [isWorkspaceSettingsModalOpen, setIsWorkspaceSettingsModalOpen] = useState(
|
||||
searchParams?.get("workspaceSettings") === "general" || searchParams?.get("workspaceSettings") === "ai-voice-tasker"
|
||||
Boolean(getWorkspaceSettingsModalTabFromSearch(searchParams?.toString() ?? ""))
|
||||
);
|
||||
// derived values
|
||||
const workspaceSettingsModalTab = getWorkspaceSettingsModalTabFromSearch(searchParams?.toString() ?? "");
|
||||
const isWorkspaceSettingsPath =
|
||||
(pathname.includes(`/${workspaceSlug}/settings`) && !projectId) ||
|
||||
searchParams?.get("workspaceSettings") === "general" ||
|
||||
searchParams?.get("workspaceSettings") === "ai-voice-tasker" ||
|
||||
Boolean(workspaceSettingsModalTab) ||
|
||||
isWorkspaceSettingsModalOpen;
|
||||
const showLabel = preferences.displayMode === "icon_with_label";
|
||||
const railWidth = showLabel ? "3.75rem" : "3rem";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import { ArrowUpToLine, Building, CreditCard, Mic, Users, Webhook } from "lucide-react";
|
||||
import { ArrowUpToLine, Building, CreditCard, Database, Mic, Users, Webhook } from "lucide-react";
|
||||
// plane imports
|
||||
import type { ISvgIcons } from "@plane/propel/icons";
|
||||
import type { TWorkspaceSettingsTabs } from "@plane/types";
|
||||
|
|
@ -15,6 +15,7 @@ export const WORKSPACE_SETTINGS_ICONS: Record<TWorkspaceSettingsTabs, LucideIcon
|
|||
members: Users,
|
||||
export: ArrowUpToLine,
|
||||
"billing-and-plans": CreditCard,
|
||||
storage: Database,
|
||||
webhooks: Webhook,
|
||||
"ai-voice-tasker": Mic,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import { AlertTriangle, Database, Files, HardDrive, Layers3, Recycle, UploadCloud } from "lucide-react";
|
||||
import type { ElementType } from "react";
|
||||
import useSWR from "swr";
|
||||
// plane imports
|
||||
import type { IWorkspaceStorageProjectSummary } from "@plane/types";
|
||||
import { cn } from "@plane/utils";
|
||||
// services
|
||||
import { WorkspaceService } from "@/services/workspace.service";
|
||||
|
||||
const workspaceService = new WorkspaceService();
|
||||
|
||||
const formatBytes = (value: number) => {
|
||||
const bytes = Number(value || 0);
|
||||
if (bytes <= 0) return "0 Б";
|
||||
|
||||
const units = ["Б", "КБ", "МБ", "ГБ", "ТБ"];
|
||||
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
||||
const size = bytes / 1024 ** index;
|
||||
|
||||
return `${size >= 10 || index === 0 ? size.toFixed(0) : size.toFixed(1)} ${units[index]}`;
|
||||
};
|
||||
|
||||
const formatCount = (value: number) => new Intl.NumberFormat("ru-RU").format(Number(value || 0));
|
||||
|
||||
const StatCard = (props: {
|
||||
title: string;
|
||||
value: string;
|
||||
caption: string;
|
||||
icon: ElementType;
|
||||
tone?: "default" | "accent" | "warning";
|
||||
}) => {
|
||||
const { title, value, caption, icon: Icon, tone = "default" } = props;
|
||||
|
||||
return (
|
||||
<div className="rounded-[28px] border border-white/5 bg-custom-background-80/85 p-5 shadow-[0_22px_80px_rgba(0,0,0,0.28)]">
|
||||
<div className="mb-5 flex items-start justify-between gap-4">
|
||||
<div className="text-[11px] font-semibold uppercase tracking-[0.18em] text-tertiary">{title}</div>
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-10 shrink-0 items-center justify-center rounded-full bg-custom-background-90 text-secondary",
|
||||
tone === "accent" && "bg-accent-primary/20 text-accent-primary",
|
||||
tone === "warning" && "bg-red-500/15 text-red-300"
|
||||
)}
|
||||
>
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-3xl font-semibold tracking-normal text-primary">{value}</div>
|
||||
<div className="mt-2 text-12 leading-5 text-secondary">{caption}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectStorageRow = (props: { project: IWorkspaceStorageProjectSummary; maxSize: number }) => {
|
||||
const { project, maxSize } = props;
|
||||
const ratio = maxSize > 0 ? Math.max((project.logical_size / maxSize) * 100, project.logical_size > 0 ? 3 : 0) : 0;
|
||||
|
||||
return (
|
||||
<tr className="border-b border-white/6 last:border-0">
|
||||
<td className="py-4 pr-4 align-middle">
|
||||
<div className="flex min-w-0 flex-col">
|
||||
<span className="truncate text-14 font-semibold text-primary">{project.name}</span>
|
||||
<span className="mt-1 text-11 uppercase tracking-[0.16em] text-tertiary">{project.identifier}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-4 align-middle text-13 text-secondary">{formatCount(project.file_count)}</td>
|
||||
<td className="px-4 py-4 align-middle text-13 text-secondary">{formatCount(project.blob_count)}</td>
|
||||
<td className="px-4 py-4 align-middle">
|
||||
<div className="flex min-w-[12rem] items-center gap-3">
|
||||
<div className="h-2 flex-1 overflow-hidden rounded-full bg-custom-background-90">
|
||||
<div className="h-full rounded-full bg-accent-primary" style={{ width: `${ratio}%` }} />
|
||||
</div>
|
||||
<span className="w-20 text-right text-13 font-medium text-primary">{formatBytes(project.logical_size)}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-4 align-middle text-13 text-secondary">{formatBytes(project.physical_size)}</td>
|
||||
<td className="px-4 py-4 align-middle text-13 text-accent-primary">{formatBytes(project.dedup_savings)}</td>
|
||||
<td className="px-4 py-4 align-middle text-13 text-secondary">{formatCount(project.failed_upload_count)}</td>
|
||||
<td className="pl-4 py-4 align-middle text-13 text-secondary">{formatCount(project.soft_deleted_count)}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
type TStorageSettingsContentProps = {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export function StorageSettingsContent({ workspaceSlug }: TStorageSettingsContentProps) {
|
||||
const { data, error, isLoading } = useSWR(
|
||||
workspaceSlug ? ["workspace-storage-summary", workspaceSlug] : null,
|
||||
([, slug]) => workspaceService.fetchWorkspaceStorageSummary(slug)
|
||||
);
|
||||
|
||||
const projects = [...(data?.projects ?? [])].sort((a, b) => b.logical_size - a.logical_size);
|
||||
const maxProjectSize = Math.max(...projects.map((project) => project.logical_size), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold tracking-normal text-primary">Хранилище</h1>
|
||||
<p className="mt-2 max-w-3xl text-14 leading-6 text-secondary">
|
||||
Контроль объема файлов, дедупликации и кандидатов на очистку по workspace и проектам.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{isLoading && (
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div key={index} className="h-36 animate-pulse rounded-[28px] bg-custom-background-80/80" />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="rounded-[28px] border border-red-500/20 bg-red-500/10 px-5 py-4 text-14 text-red-200">
|
||||
Не удалось загрузить данные хранилища.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data && (
|
||||
<>
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<StatCard
|
||||
title="Логический объем"
|
||||
value={formatBytes(data.summary.logical_size)}
|
||||
caption={`${formatCount(data.summary.file_count)} файлов во всех проектах`}
|
||||
icon={Files}
|
||||
/>
|
||||
<StatCard
|
||||
title="Физический объем"
|
||||
value={formatBytes(data.summary.physical_size)}
|
||||
caption={`${formatCount(data.summary.blob_count)} уникальных blob`}
|
||||
icon={HardDrive}
|
||||
tone="accent"
|
||||
/>
|
||||
<StatCard
|
||||
title="Экономия дедупа"
|
||||
value={formatBytes(data.summary.dedup_savings)}
|
||||
caption={`${formatCount(data.summary.uploaded_without_blob_count)} загруженных файлов без blob`}
|
||||
icon={Layers3}
|
||||
tone="accent"
|
||||
/>
|
||||
<StatCard
|
||||
title="Проблемы загрузки"
|
||||
value={formatCount(data.diagnostics.failed_upload_count)}
|
||||
caption={`${formatBytes(data.diagnostics.failed_upload_size)} неподтвержденных файлов`}
|
||||
icon={AlertTriangle}
|
||||
tone={data.diagnostics.failed_upload_count > 0 ? "warning" : "default"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 xl:grid-cols-3">
|
||||
<StatCard
|
||||
title="Зависшие загрузки"
|
||||
value={formatCount(data.diagnostics.stale_unuploaded_count)}
|
||||
caption={`${formatBytes(data.diagnostics.stale_unuploaded_size)} старше суток`}
|
||||
icon={UploadCloud}
|
||||
/>
|
||||
<StatCard
|
||||
title="Удаленные файлы"
|
||||
value={formatCount(data.diagnostics.soft_deleted_count)}
|
||||
caption={`${formatBytes(data.diagnostics.soft_deleted_size)} ожидают retention cleanup`}
|
||||
icon={Recycle}
|
||||
/>
|
||||
<StatCard
|
||||
title="Потерянные blob"
|
||||
value={`${formatCount(data.diagnostics.orphaned_blob_count)} / ${formatCount(data.diagnostics.missing_blob_count)}`}
|
||||
caption={`${formatBytes(data.diagnostics.orphaned_blob_size + data.diagnostics.missing_blob_size)} вне активных ссылок`}
|
||||
icon={Database}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<section className="rounded-[28px] border border-white/5 bg-custom-background-80/85 p-5 shadow-[0_22px_80px_rgba(0,0,0,0.28)]">
|
||||
<div className="mb-5 flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold tracking-normal text-primary">Проекты</h2>
|
||||
<p className="mt-1 text-13 text-secondary">Сортировка по логическому объему файлов.</p>
|
||||
</div>
|
||||
<div className="rounded-full bg-custom-background-90 px-4 py-2 text-12 font-medium text-secondary">
|
||||
{formatCount(projects.length)} проектов
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full min-w-[58rem] border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-white/8 text-left text-[11px] font-semibold uppercase tracking-[0.16em] text-tertiary">
|
||||
<th className="pb-3 pr-4">Проект</th>
|
||||
<th className="px-4 pb-3">Файлы</th>
|
||||
<th className="px-4 pb-3">Blob</th>
|
||||
<th className="px-4 pb-3">Логический объем</th>
|
||||
<th className="px-4 pb-3">Физический</th>
|
||||
<th className="px-4 pb-3">Дедуп</th>
|
||||
<th className="px-4 pb-3">Ошибки</th>
|
||||
<th className="pb-3 pl-4">Удалено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{projects.map((project) => (
|
||||
<ProjectStorageRow key={project.id} project={project} maxSize={maxProjectSize} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import { SettingsSidebarItem } from "@/components/settings/sidebar/item";
|
|||
import { WORKSPACE_SETTINGS_ICONS } from "@/components/settings/workspace/sidebar/item-icon";
|
||||
import { WorkspaceSettingsSidebarHeader } from "@/components/settings/workspace/sidebar/header";
|
||||
import { AIVoiceTaskerSettingsContent } from "@/components/workspace/settings/ai-voice-tasker-settings";
|
||||
import { StorageSettingsContent } from "@/components/workspace/settings/storage-settings";
|
||||
import { WorkspaceDetails } from "@/components/workspace/settings/workspace-details";
|
||||
// hooks
|
||||
import { useUserPermissions } from "@/hooks/store/user";
|
||||
|
|
@ -37,7 +38,7 @@ import {
|
|||
} from "./workspace-settings-modal.utils";
|
||||
|
||||
const HIDDEN_WORKSPACE_SETTINGS_KEYS = new Set<TWorkspaceSettingsTabs>(["billing-and-plans"]);
|
||||
const MODAL_TABS = new Set<TWorkspaceSettingsTabs>(["general", "ai-voice-tasker"]);
|
||||
const MODAL_TABS = new Set<TWorkspaceSettingsTabs>(["general", "storage", "ai-voice-tasker"]);
|
||||
|
||||
const getInitialTab = (): TWorkspaceSettingsModalTab => {
|
||||
if (typeof window === "undefined") return "general";
|
||||
|
|
@ -103,9 +104,16 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
return <AIVoiceTaskerSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
if (activeTab === "storage" && currentWorkspace?.slug) {
|
||||
return <StorageSettingsContent workspaceSlug={currentWorkspace.slug} />;
|
||||
}
|
||||
|
||||
return <WorkspaceDetails />;
|
||||
};
|
||||
|
||||
const activeTabLabel =
|
||||
activeTab === "ai-voice-tasker" ? "AI / Voice Tasker" : activeTab === "storage" ? "хранилище" : "основные параметры";
|
||||
|
||||
return (
|
||||
<ModalCore
|
||||
isOpen={isOpen}
|
||||
|
|
@ -130,7 +138,7 @@ export const WorkspaceSettingsModal = observer(function WorkspaceSettingsModal()
|
|||
<div className="min-w-0">
|
||||
<div className="text-18 font-semibold text-primary">Настройки workspace</div>
|
||||
<div className="mt-1 truncate text-12 text-tertiary">
|
||||
{currentWorkspace?.name ?? "Workspace"} / {activeTab === "ai-voice-tasker" ? "AI / Voice Tasker" : "основные параметры"}
|
||||
{currentWorkspace?.name ?? "Workspace"} / {activeTabLabel}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
|
@ -188,7 +196,6 @@ function WorkspaceModalSidebar({ activeTab, allowPermissions, onSelectItem, work
|
|||
{accessibleItems.map((item) => {
|
||||
const Icon = WORKSPACE_SETTINGS_ICONS[item.key];
|
||||
const isActive = item.key === activeTab;
|
||||
const isModalTab = MODAL_TABS.has(item.key);
|
||||
|
||||
return (
|
||||
<SettingsSidebarItem
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export const WORKSPACE_SETTINGS_MODAL_QUERY_KEY = "workspaceSettings";
|
||||
export const WORKSPACE_SETTINGS_MODAL_EVENT = "nodedc:workspace-settings-modal";
|
||||
|
||||
export type TWorkspaceSettingsModalTab = "general" | "ai-voice-tasker";
|
||||
export type TWorkspaceSettingsModalTab = "general" | "storage" | "ai-voice-tasker";
|
||||
|
||||
type TWorkspaceSettingsModalEventDetail = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -15,7 +15,7 @@ const dispatchWorkspaceSettingsModalEvent = (detail: TWorkspaceSettingsModalEven
|
|||
export const getWorkspaceSettingsModalTabFromSearch = (search: string): TWorkspaceSettingsModalTab | undefined => {
|
||||
const value = new URLSearchParams(search).get(WORKSPACE_SETTINGS_MODAL_QUERY_KEY);
|
||||
|
||||
if (value === "general" || value === "ai-voice-tasker") return value;
|
||||
if (value === "general" || value === "storage" || value === "ai-voice-tasker") return value;
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import type {
|
|||
IWorkspaceSidebarNavigationItem,
|
||||
IWorkspaceSidebarNavigation,
|
||||
IWorkspaceUserPropertiesResponse,
|
||||
IWorkspaceStorageSummaryResponse,
|
||||
} from "@plane/types";
|
||||
// services
|
||||
import { APIService } from "@/services/api.service";
|
||||
|
|
@ -52,6 +53,14 @@ export class WorkspaceService extends APIService {
|
|||
});
|
||||
}
|
||||
|
||||
async fetchWorkspaceStorageSummary(workspaceSlug: string): Promise<IWorkspaceStorageSummaryResponse> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/storage/summary/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async createWorkspace(data: Partial<IWorkspace>): Promise<IWorkspace> {
|
||||
return this.post("/api/workspaces/", data)
|
||||
.then((response) => response?.data)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,13 @@ export const WORKSPACE_SETTINGS: Record<TWorkspaceSettingsTabs, TWorkspaceSettin
|
|||
access: [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports/`,
|
||||
},
|
||||
storage: {
|
||||
key: "storage",
|
||||
i18n_label: "workspace_settings.settings.storage.title",
|
||||
href: `/settings/storage`,
|
||||
access: [EUserWorkspaceRoles.ADMIN],
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/storage/`,
|
||||
},
|
||||
webhooks: {
|
||||
key: "webhooks",
|
||||
i18n_label: "workspace_settings.settings.webhooks.title",
|
||||
|
|
@ -75,6 +82,7 @@ export const GROUPED_WORKSPACE_SETTINGS: Record<WORKSPACE_SETTINGS_CATEGORY, TWo
|
|||
WORKSPACE_SETTINGS["members"],
|
||||
WORKSPACE_SETTINGS["billing-and-plans"],
|
||||
WORKSPACE_SETTINGS["export"],
|
||||
WORKSPACE_SETTINGS["storage"],
|
||||
],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.FEATURES]: [WORKSPACE_SETTINGS["ai-voice-tasker"]],
|
||||
[WORKSPACE_SETTINGS_CATEGORY.DEVELOPER]: [WORKSPACE_SETTINGS["webhooks"]],
|
||||
|
|
|
|||
|
|
@ -1730,6 +1730,9 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
title: "Storage",
|
||||
},
|
||||
webhooks: {
|
||||
heading: "Webhooks",
|
||||
description: "Automate notifications to external services when project events occur.",
|
||||
|
|
|
|||
|
|
@ -1892,6 +1892,9 @@ export default {
|
|||
},
|
||||
},
|
||||
},
|
||||
storage: {
|
||||
title: "Хранилище",
|
||||
},
|
||||
webhooks: {
|
||||
heading: "Вебхуки",
|
||||
description: "Автоматизируйте уведомления во внешние сервисы при событиях проекта.",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type TWorkspaceSettingsTabs =
|
|||
| "members"
|
||||
| "billing-and-plans"
|
||||
| "export"
|
||||
| "storage"
|
||||
| "webhooks"
|
||||
| "ai-voice-tasker";
|
||||
export type TWorkspaceSettingsItem = {
|
||||
|
|
|
|||
|
|
@ -240,6 +240,53 @@ export interface IWorkspaceAnalyticsResponse {
|
|||
completion_chart: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface IWorkspaceStorageProjectSummary {
|
||||
id: string;
|
||||
name: string;
|
||||
identifier: string;
|
||||
file_count: number;
|
||||
blob_count: number;
|
||||
logical_size: number;
|
||||
physical_size: number;
|
||||
dedup_savings: number;
|
||||
failed_upload_count: number;
|
||||
failed_upload_size: number;
|
||||
soft_deleted_count: number;
|
||||
soft_deleted_size: number;
|
||||
uploaded_without_blob_count: number;
|
||||
}
|
||||
|
||||
export interface IWorkspaceStorageSummaryResponse {
|
||||
workspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
upload_file_size_limit_enabled: boolean;
|
||||
upload_file_size_limit: number;
|
||||
};
|
||||
summary: {
|
||||
file_count: number;
|
||||
blob_count: number;
|
||||
logical_size: number;
|
||||
physical_size: number;
|
||||
dedup_savings: number;
|
||||
uploaded_without_blob_count: number;
|
||||
};
|
||||
diagnostics: {
|
||||
failed_upload_count: number;
|
||||
failed_upload_size: number;
|
||||
stale_unuploaded_count: number;
|
||||
stale_unuploaded_size: number;
|
||||
soft_deleted_count: number;
|
||||
soft_deleted_size: number;
|
||||
orphaned_blob_count: number;
|
||||
orphaned_blob_size: number;
|
||||
missing_blob_count: number;
|
||||
missing_blob_size: number;
|
||||
};
|
||||
projects: IWorkspaceStorageProjectSummary[];
|
||||
}
|
||||
|
||||
export type TWorkspacePaginationInfo = TPaginationInfo & {
|
||||
results: IWorkspace[];
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue