✨ Simple User Quota (#367)
* ✨Simple User Quota * 👷Fix build with OpenSearch
This commit is contained in:
@@ -7,6 +7,7 @@ import { DriveItemAtom, DriveItemChildrenAtom } from '../state/store';
|
||||
import { BrowseFilter, DriveItem, DriveItemVersion } from '../types';
|
||||
import { SharedWithMeFilterState } from '../state/shared-with-me-filter';
|
||||
import Languages from 'features/global/services/languages-service';
|
||||
import { useUserQuota } from "features/users/hooks/use-user-quota";
|
||||
|
||||
/**
|
||||
* Returns the children of a drive item
|
||||
@@ -15,6 +16,7 @@ import Languages from 'features/global/services/languages-service';
|
||||
export const useDriveActions = () => {
|
||||
const companyId = useRouterCompany();
|
||||
const sharedFilter = useRecoilValue(SharedWithMeFilterState);
|
||||
const { getQuota } = useUserQuota();
|
||||
|
||||
const refresh = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
@@ -52,6 +54,7 @@ export const useDriveActions = () => {
|
||||
try {
|
||||
driveFile = await DriveApiClient.create(companyId, { item, version });
|
||||
await refresh(driveFile.parent_id!);
|
||||
await getQuota();
|
||||
} catch (e) {
|
||||
ToasterService.error(Languages.t('hooks.use-drive-actions.unable_create_file'));
|
||||
}
|
||||
@@ -89,6 +92,7 @@ export const useDriveActions = () => {
|
||||
try {
|
||||
await DriveApiClient.remove(companyId, id);
|
||||
await refresh(parentId || '');
|
||||
await getQuota();
|
||||
} catch (e) {
|
||||
ToasterService.error(Languages.t('hooks.use-drive-actions.unable_remove_file'));
|
||||
}
|
||||
|
||||
@@ -9,3 +9,15 @@ export const formatBytes = (bytes: number, decimals = 2) => {
|
||||
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
export const formatBytesToInt = (bytes: number, decimals = 2) => {
|
||||
if (!+bytes) return '0 KB';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return `${parseInt((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ export enum FeatureNames {
|
||||
COMPANY_SEARCH_USERS = 'company:search_users',
|
||||
COMPANY_SHARED_DRIVE = 'company:shared_drive',
|
||||
COMPANY_DISPLAY_EMAIL = 'company:display_email',
|
||||
COMPANY_USER_QUOTA = 'company:user_quota',
|
||||
}
|
||||
|
||||
export type FeatureValueType = boolean | number;
|
||||
@@ -29,6 +30,7 @@ availableFeaturesWithDefaults.set(FeatureNames.COMPANY_INVITE_MEMBER, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_SEARCH_USERS, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_SHARED_DRIVE, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_DISPLAY_EMAIL, true);
|
||||
availableFeaturesWithDefaults.set(FeatureNames.COMPANY_USER_QUOTA, false);
|
||||
|
||||
/**
|
||||
* ChannelServiceImpl that allow you to manage feature flipping in Tdrive using react feature toggles
|
||||
@@ -70,7 +72,6 @@ class FeatureTogglesService {
|
||||
}
|
||||
|
||||
public isActiveFeatureName(featureName: FeatureNames) {
|
||||
console.debug(this.activeFeatureNames)
|
||||
const b = this.activeFeatureNames.includes(featureName);
|
||||
console.debug(`Feature ${featureName} is ${b}`);
|
||||
return b;
|
||||
|
||||
@@ -7,6 +7,7 @@ import WorkspaceAPIClient from '../../workspaces/api/workspace-api-client';
|
||||
import CurrentUser from '../../../deprecated/user/CurrentUser';
|
||||
import { setUserList } from '../hooks/use-user-list';
|
||||
import Logger from 'features/global/framework/logger-service';
|
||||
import { UserQuota } from "features/users/types/user-quota";
|
||||
|
||||
export type SearchContextType = {
|
||||
scope: 'company' | 'workspace' | 'all';
|
||||
@@ -141,6 +142,19 @@ class UserAPIClientService {
|
||||
});
|
||||
}
|
||||
|
||||
async getQuota(userId: string): Promise<UserQuota> {
|
||||
return Api.get<UserQuota>(
|
||||
`/internal/services/users/v1/users/${userId}/quota`,
|
||||
undefined,
|
||||
false
|
||||
).then(result => {
|
||||
return result;
|
||||
}).catch(e => {
|
||||
console.log(`Error getting quota:: ${e.message}`)
|
||||
return { } as UserQuota;
|
||||
});
|
||||
}
|
||||
|
||||
async getCompany(companyId: string): Promise<CompanyType> {
|
||||
return Api.get<{ resource: CompanyType }>(
|
||||
`/internal/services/users/v1/companies/${companyId}`,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { UserQuota } from '@features/users/types/user-quota';
|
||||
import UserAPIClient from '@features/users/api/user-api-client';
|
||||
import { useCurrentUser } from "features/users/hooks/use-current-user";
|
||||
import { atom, useRecoilState } from "recoil";
|
||||
|
||||
export const QuotaState = atom<UserQuota>({
|
||||
key: 'QuotaState',
|
||||
default: {
|
||||
used: 0,
|
||||
remaining: 1,
|
||||
total: 1
|
||||
},
|
||||
});
|
||||
|
||||
export const useUserQuota = () => {
|
||||
const nullQuota = {
|
||||
used: 0,
|
||||
remaining: 1,
|
||||
total: 1
|
||||
}
|
||||
const {user } = useCurrentUser();
|
||||
const [quota, setQuota] = useRecoilState(QuotaState);
|
||||
|
||||
const getQuota = useCallback(async () => {
|
||||
let data: UserQuota = nullQuota;
|
||||
if (user?.id) {
|
||||
data = await UserAPIClient.getQuota(user.id);
|
||||
} else {
|
||||
data = nullQuota;
|
||||
}
|
||||
setQuota(data)
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getQuota();
|
||||
}, []);
|
||||
|
||||
|
||||
return { quota, getQuota };
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
export type UserQuota = {
|
||||
used: number;
|
||||
remaining: number;
|
||||
total: number;
|
||||
}
|
||||
@@ -1,25 +1,76 @@
|
||||
import { Base, Title } from '@atoms/text';
|
||||
import { useDriveItem } from '@features/drive/hooks/use-drive-item';
|
||||
import { formatBytes } from '@features/drive/utils';
|
||||
import { useCurrentUser } from 'app/features/users/hooks/use-current-user';
|
||||
import { Base } from "@atoms/text";
|
||||
import { formatBytesToInt } from "@features/drive/utils";
|
||||
import Languages from "features/global/services/languages-service";
|
||||
import { useUserQuota } from "@features/users/hooks/use-user-quota";
|
||||
import RouterServices from "features/router/services/router-service";
|
||||
import { useEffect, useState } from "react";
|
||||
import FeatureTogglesService, { FeatureNames } from "@features/global/services/feature-toggles-service";
|
||||
import { useDriveItem } from "features/drive/hooks/use-drive-item";
|
||||
|
||||
|
||||
const DiskUsage = () => {
|
||||
const { viewId } = RouterServices.getStateFromRoute();
|
||||
console.log("VIEW-iD::" + viewId);
|
||||
|
||||
const [used, setUsed] = useState(0);
|
||||
const [usedBytes, setUsedBytes] = useState(0);
|
||||
const [totalBytes, setTotalBytes] = useState(0);
|
||||
|
||||
if (FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_USER_QUOTA)) {
|
||||
const { quota } = useUserQuota()
|
||||
useEffect(() => {
|
||||
setUsed(Math.round(quota.used / quota.total * 100))
|
||||
setUsedBytes(quota.used);
|
||||
setTotalBytes(quota.total);
|
||||
}, [quota]);
|
||||
} else if (viewId) {
|
||||
const { item } = useDriveItem(viewId);
|
||||
useEffect(() => {
|
||||
setUsedBytes(item?.size || 0);
|
||||
}, [viewId, item])
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const { access, item } = useDriveItem('root');
|
||||
const { item: trash } = useDriveItem('trash');
|
||||
const { user } = useCurrentUser();
|
||||
return (
|
||||
<>
|
||||
{access !== 'read' && (
|
||||
{FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_USER_QUOTA) && (
|
||||
<div className="bg-zinc-500 dark:bg-zinc-800 bg-opacity-10 rounded-md p-4 w-auto max-w-md">
|
||||
<div className="w-full">
|
||||
<Title>
|
||||
{formatBytes(item?.size || 0)}
|
||||
<Base> { Languages.t('components.disk_usage.used')} </Base> <Base>{formatBytes(trash?.size || 0)} {Languages.t('components.disk_usage.in_trash')}</Base>
|
||||
</Title>
|
||||
<div className="overflow-hidden h-4 mb-4 text-xs flex rounded bg-emerald-200">
|
||||
{used > 90 && (
|
||||
<div style={{ width: used + '%',}} className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-red-500"></div>
|
||||
)}
|
||||
{used < 80 && (
|
||||
<div style={{ width: used + '%',}} className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-500"></div>
|
||||
)}
|
||||
{ (used >= 80 && used <= 90 )&& (
|
||||
<div style={{ width: used + '%',}} className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-yellow-500"></div>
|
||||
)}
|
||||
|
||||
<div style={{ width: (100 - used) + '%' }} className="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-grey-500"></div>
|
||||
</div>
|
||||
{/*<div className="bg-blue-600 h-1.5 rounded-full dark:bg-blue-500" style={usedStyle}></div>*/}
|
||||
<Base>
|
||||
{formatBytesToInt(usedBytes)}
|
||||
<Base> { Languages.t('components.disk_usage.of')} </Base>
|
||||
{formatBytesToInt(totalBytes || 0)}
|
||||
<Base> { Languages.t('components.disk_usage.used')} </Base>
|
||||
{/*<Base>{formatBytes(trash?.size || 0)} {Languages.t('components.disk_usage.in_trash')}</Base>*/}
|
||||
</Base>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!FeatureTogglesService.isActiveFeatureName(FeatureNames.COMPANY_USER_QUOTA) && (
|
||||
<div className="bg-zinc-500 dark:bg-zinc-800 bg-opacity-10 rounded-md p-4 w-auto max-w-md">
|
||||
<div className="w-full">
|
||||
<Base>
|
||||
{formatBytesToInt(usedBytes)}
|
||||
<Base> { Languages.t('components.disk_usage.used')} </Base>
|
||||
</Base>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export default DiskUsage;
|
||||
Reference in New Issue
Block a user