Browse Source

feat: Admin dashboard

pull/35/head
Dale 3 weeks ago
parent
commit
2f2497ae77
Signed by: Deiru GPG Key ID: AA250C0277B927E1
  1. 288
      graphql.schema.json
  2. 2
      package.json
  3. 22
      src/cli/commands/stats.ts
  4. 155
      src/client/AdminPanel/AdminDashboard.tsx
  5. 19
      src/client/components/ui/ProgressWithLabel.tsx
  6. 23
      src/server/db/generate-roles.ts
  7. 27
      src/server/db/models/System.ts
  8. 4
      src/server/graphql/index.ts
  9. 46
      src/server/graphql/resolvers/system/index.ts
  10. 1
      src/server/graphql/schema/Roles.graphql
  11. 23
      src/server/graphql/schema/System.graphql
  12. 2
      src/server/types/resolver.ts
  13. 10
      src/shared/acl/utils.ts
  14. 101
      src/shared/graphql.ts
  15. 71
      src/shared/hooks.ts
  16. 10
      yarn.lock

288
graphql.schema.json

@ -79,6 +79,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "system",
"description": null,
"args": [],
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "ENUM",
"name": "AccessUnit",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userSettings",
"description": null,
@ -1426,6 +1442,16 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "Float",
"description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
"fields": null,
"inputFields": null,
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "FullUser",
@ -4104,6 +4130,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "systemLoad",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "SystemLoadInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "test",
"description": null,
@ -4198,6 +4240,22 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userStats",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "OBJECT",
"name": "UserStatsInfo",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "users",
"description": null,
@ -5083,6 +5141,161 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "SystemLoadInfo",
"description": null,
"fields": [
{
"name": "cpuPercentage",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "dbSize",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "drivePercentage",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "mediaDirSize",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "memPercentage",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalDrive",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "totalMem",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "usedDrive",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "usedMem",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Float",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "TestResponse",
@ -6118,6 +6331,81 @@
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "UserStatsInfo",
"description": null,
"fields": [
{
"name": "channelCount",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "sessionCount",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "streamKeyCount",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "userCount",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "INPUT_OBJECT",
"name": "UsersFilter",

2
package.json

@ -61,6 +61,7 @@
"md5": "^2.3.0",
"module-alias": "^2.2.2",
"next": "^11.0.1",
"node-os-utils": "^1.3.5",
"pretty-bytes": "^5.6.0",
"prompts": "^2.4.2",
"ramda": "^0.27.1",
@ -105,6 +106,7 @@
"@types/md5": "^2.3.0",
"@types/module-alias": "^2.0.0",
"@types/node": "^16.3.3",
"@types/node-os-utils": "^1.2.0",
"@types/ramda": "^0.27.40",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",

22
src/cli/commands/stats.ts

@ -6,7 +6,7 @@ import prettyBytes from "pretty-bytes";
import { readdir, stat } from "fs/promises";
import path from "path";
const dirSize = async (directory: string) => {
export const dirSize = async (directory: string) => {
const files = await readdir(directory);
const stats = files.map((file) => stat(path.join(directory, file)));
@ -16,13 +16,8 @@ const dirSize = async (directory: string) => {
);
};
export const getDbStats = async () => {
const conn = await rdb.connect({
host: config.database?.host,
port: config.database?.port,
});
const dbSize = await rdb
.db("rethinkdb")
export const getDbSize = (db: rdb.Db) => {
return db
.table("stats")
.filter(
rdb
@ -38,8 +33,15 @@ export const getDbStats = async () => {
.map((doc) => doc("storage_engine")("disk")("space_usage")("data_bytes"))
.reduce((left: any, right: any) => {
return left.add(right);
})
.run(conn);
});
};
export const getDbStats = async () => {
const conn = await rdb.connect({
host: config.database?.host,
port: config.database?.port,
});
const dbSize = await getDbSize(rdb.db("rethinkdb")).run(conn);
const userCount = await rdb
.db(config.database?.db)

155
src/client/AdminPanel/AdminDashboard.tsx

@ -1,5 +1,156 @@
import { Box } from "@chakra-ui/react";
import {
Badge,
Box,
Flex,
Heading,
Text,
SimpleGrid,
Divider,
} from "@chakra-ui/react";
import { gql } from "@apollo/client";
import { useAdminDashboardSystemLoadQuery } from "miracle-tv-shared/hooks";
import React, { useContext } from "react";
import { LiveUpdateContext } from "miracle-tv-client/context/liveUpdate";
import { Panel } from "miracle-tv-client/components/ui/Panel";
import { ProgressWithLabel } from "miracle-tv-client/components/ui/ProgressWithLabel";
import { useMediaQuery } from "miracle-tv-client/utils/css";
import { MediaQuery } from "miracle-tv-client/utils/const";
gql`
query AdminDashboardSystemLoad {
userStats {
userCount
channelCount
streamKeyCount
sessionCount
}
systemLoad {
cpuPercentage
totalMem
usedMem
memPercentage
totalDrive
usedDrive
drivePercentage
mediaDirSize
dbSize
}
}
`;
export const AdminDashboard = () => {
return <Box>Here be admin dashboard</Box>;
const { isLiveUpdate } = useContext(LiveUpdateContext);
const isMobile = useMediaQuery(MediaQuery.mobile);
const { data: { systemLoad, userStats } = {} } =
useAdminDashboardSystemLoadQuery({
fetchPolicy: "no-cache",
pollInterval: isLiveUpdate ? 5000 : 0,
});
return (
<>
<Flex justify="space-between" mb={2}>
<Box>&nbsp;</Box>
{isLiveUpdate && <Text>Updating every 5 seconds</Text>}
</Flex>
<SimpleGrid columns={isMobile ? 1 : 2} spacing={4}>
<Panel>
<Heading size="lg" mb={2}>
System Stats
</Heading>
<Divider mb={4} />
<Flex w="100%" align="center" direction="column" mb={4}>
<Box as="span" w="100%" mb={2}>
<Badge fontSize="1rem" colorScheme="primary">
CPU
</Badge>
</Box>
<ProgressWithLabel
label={`${systemLoad?.cpuPercentage}%`}
w="100%"
colorScheme="primary"
height="1.4rem"
value={systemLoad?.cpuPercentage}
textProps={{ color: "white" }}
/>
</Flex>
<Flex w="100%" align="center" direction="column" mb={4}>
<Box as="span" w="100%" mb={2}>
<Badge fontSize="1rem" colorScheme="primary">
Memory
</Badge>
</Box>
<ProgressWithLabel
label={`${systemLoad?.usedMem} MB / ${systemLoad?.totalMem} MB (${systemLoad?.memPercentage}%)`}
w="100%"
colorScheme="primary"
height="1.4rem"
value={systemLoad?.memPercentage}
textProps={{ color: "white" }}
/>
</Flex>
<Flex w="100%" align="center" direction="column" mb={4}>
<Box as="span" w="100%" mb={2}>
<Badge fontSize="1rem" colorScheme="primary">
Disk
</Badge>
</Box>
<ProgressWithLabel
label={`${systemLoad?.usedDrive} GB / ${systemLoad?.totalDrive} GB (${systemLoad?.drivePercentage}%)`}
w="100%"
colorScheme="primary"
bgColor="primary.400"
height="1.4rem"
value={systemLoad?.drivePercentage}
mb={2}
textProps={{ color: "white" }}
/>
<Box w="100%">
<Text>
Media Storage Size: {systemLoad?.mediaDirSize.toFixed(2)}Gb
</Text>
<Text>DB Storage Size: {systemLoad?.dbSize.toFixed(2)}Gb</Text>
<Text>
Total App Storage Size:{" "}
{(systemLoad?.mediaDirSize + systemLoad?.dbSize).toFixed(2)}Gb
</Text>
</Box>
</Flex>
</Panel>
<Panel>
<Heading size="lg" mb={2}>
User stats
</Heading>
<Divider mb={4} />
<Box w="100%">
<Flex align="center" mb={2}>
<Badge colorScheme="primary" mr={2} fontSize="1.1rem">
Total Users:
</Badge>
<Text>{userStats?.userCount}</Text>
</Flex>
<Flex align="center" mb={2}>
<Badge colorScheme="primary" mr={2} fontSize="1.1rem">
Total Channels:
</Badge>
<Text>{userStats?.channelCount}</Text>
</Flex>
<Flex align="center" mb={2}>
<Badge colorScheme="primary" mr={2} fontSize="1.1rem">
Total Stream Keys:
</Badge>
<Text>{userStats?.streamKeyCount}</Text>
</Flex>
<Flex align="center" mb={2}>
<Badge colorScheme="primary" mr={2} fontSize="1.1rem">
Total Sessions:
</Badge>
<Text>{userStats?.sessionCount}</Text>
</Flex>
</Box>
</Panel>
</SimpleGrid>
</>
);
};

19
src/client/components/ui/ProgressWithLabel.tsx

@ -0,0 +1,19 @@
import { Progress, ProgressProps } from "@chakra-ui/progress";
import { Box, Flex, Text, TextProps } from "@chakra-ui/react";
import React from "react";
type Props = {
label: string;
textProps?: TextProps;
} & ProgressProps;
export const ProgressWithLabel = ({ label, textProps, ...props }: Props) => {
return (
<Box w="100%" position="relative">
<Flex w="100%" position="absolute" zIndex={2} justify="center">
<Text {...textProps}>{label}</Text>
</Flex>
<Progress {...props} zIndex={1} />
</Box>
);
};

23
src/server/db/generate-roles.ts

@ -11,12 +11,13 @@ const defaultAdminRole: Role = {
parentId: "moderator",
access: {
rights: {
channels: [AccessUnit.Write],
streamKeys: [AccessUnit.Write],
roles: [AccessUnit.Write],
users: [AccessUnit.Write],
activities: [AccessUnit.Write],
userSettings: [AccessUnit.Write],
channels: [AccessUnit.Write, AccessUnit.Read],
streamKeys: [AccessUnit.Write, AccessUnit.Read],
roles: [AccessUnit.Write, AccessUnit.Read],
users: [AccessUnit.Write, AccessUnit.Read],
activities: [AccessUnit.Write, AccessUnit.Read],
userSettings: [AccessUnit.Write, AccessUnit.Read],
system: [AccessUnit.Write, AccessUnit.Read],
},
actions: {
user: {
@ -66,10 +67,10 @@ const defaultUserRole: Role = {
access: {
rights: {
streamKeys: [AccessUnit.Self],
channels: [AccessUnit.Self, AccessUnit.Read],
users: [AccessUnit.Self, AccessUnit.Read],
activities: [AccessUnit.Self, AccessUnit.Read],
roles: [AccessUnit.Self, AccessUnit.Read],
channels: [AccessUnit.Self],
users: [AccessUnit.Self],
activities: [AccessUnit.Self],
roles: [AccessUnit.Self],
userSettings: [AccessUnit.Self],
},
actions: {
@ -117,5 +118,5 @@ export const generateRoles = async () => {
}
console.info(green`-- Role [${dr.id}] ${dr.name} Ok!`);
})
);
).then(() => conn.close());
};

27
src/server/db/models/System.ts

@ -0,0 +1,27 @@
import config from "miracle-tv-server/config";
import * as rdb from "rethinkdb";
import { Model } from ".";
export class SystemModel extends Model {
table = rdb.db("rethinkdb").table("stats");
async getDbSize() {
return this.table
.filter(
rdb
.row("id")
.contains("table_server")
.and(rdb.row("db").eq(config.database?.db))
)
.pluck({
storage_engine: {
disk: { space_usage: { data_bytes: true } },
},
} as any)
.map((doc) => doc("storage_engine")("disk")("space_usage")("data_bytes"))
.reduce((left: any, right: any) => {
return left.add(right);
})
.run(this.conn);
}
}

4
src/server/graphql/index.ts

@ -94,6 +94,8 @@ import {
} from "./resolvers/full-users";
import { fullUserMutations } from "./mutations/full-users";
import { rolesMutations } from "./mutations/roles";
import { systemResolvers } from "./resolvers/system";
import { SystemModel } from "miracle-tv-server/db/models/System";
const schemaString = glob
.sync(path.resolve(__dirname, "./**/*.graphql"))
@ -134,6 +136,7 @@ let executableSchema = makeExecutableSchema({
...rolesQueryresolvers,
...fullUserResolvers,
...fileResolvers,
...systemResolvers,
},
Mutation: {
ping: () => {
@ -186,6 +189,7 @@ export const graphqlEndpoint = new ApolloServer({
context: async ({ req }) => {
const con = await connection;
const db = {
system: new SystemModel(con),
userSettings: new UserSettingsModel(con),
sessions: new SessionsModel(con),
users: new UsersModel(con),

46
src/server/graphql/resolvers/system/index.ts

@ -0,0 +1,46 @@
import { cpu, mem, drive } from "node-os-utils";
import { ResolverContext } from "miracle-tv-server/types/resolver";
import { QueryResolvers } from "miracle-tv-shared/graphql";
import config from "miracle-tv-server/config";
import { dirSize } from "src/cli/commands/stats";
export const systemResolvers: QueryResolvers<ResolverContext> = {
async userStats(_, __, { db: { users, channels, streamKeys, sessions } }) {
const userCount = await users.table
.filter({ disabled: false, suspended: false })
.count()
.run(users.conn);
const channelCount = await channels.table.count().run(users.conn);
const streamKeyCount = await streamKeys.table.count().run(users.conn);
const sessionCount = await sessions.table.count().run(users.conn);
return {
userCount,
channelCount,
streamKeyCount,
sessionCount,
};
},
async systemLoad(_, __, { db: { system } }) {
const cpuPercentage = await cpu.usage();
const memory = await mem.used();
const disk = await (drive.used as any)();
const memPercentage = Number(
((100 * memory.usedMemMb) / memory.totalMemMb).toFixed(1)
);
const mediaDirSize = await dirSize(`${config.dataDir}/media`);
const dbSize = await system.getDbSize();
return {
cpuPercentage,
usedMem: memory.usedMemMb,
totalMem: memory.totalMemMb,
memPercentage: memPercentage,
usedDrive: disk.usedGb,
totalDrive: disk.totalGb,
drivePercentage: disk.usedPercentage,
mediaDirSize: mediaDirSize / (1000 * 1000 * 1000),
dbSize: dbSize / (1000 * 1000 * 1000),
};
},
};

1
src/server/graphql/schema/Roles.graphql

@ -23,6 +23,7 @@ type AccessRights {
users: [AccessUnit]
activities: [AccessUnit]
userSettings: [AccessUnit]
system: [AccessUnit]
}
type AccessTargets {

23
src/server/graphql/schema/System.graphql

@ -0,0 +1,23 @@
type SystemLoadInfo {
cpuPercentage: Float!
totalMem: Float!
usedMem: Float!
memPercentage: Float!
totalDrive: Float!
usedDrive: Float!
drivePercentage: Float!
mediaDirSize: Float!
dbSize: Float!
}
type UserStatsInfo {
userCount: Int!
channelCount: Int!
streamKeyCount: Int!
sessionCount: Int!
}
extend type Query {
systemLoad: SystemLoadInfo! @auth(rights: [{ unit: READ, subject: "system" }])
userStats: UserStatsInfo! @auth(rights: [{ unit: READ, subject: "system" }])
}

2
src/server/types/resolver.ts

@ -11,6 +11,7 @@ import { DbSession, DbUser } from "miracle-tv-server/db/models/types";
import { UserSettingsModel } from "miracle-tv-server/db/models/UserSettings";
import { ChannelStatusModel } from "miracle-tv-server/db/models/ChannelStatus";
import { SubscriptionsModel } from "miracle-tv-server/db/models/Subscriptions";
import { SystemModel } from "miracle-tv-server/db/models/System";
export type DbRunFn = <T>(request: Operation<T>) => Promise<T>;
@ -19,6 +20,7 @@ export type ResolverContext = {
user: DbUser;
userRoles?: Role[];
db: {
system: SystemModel;
users: UsersModel;
userSettings: UserSettingsModel;
sessions: SessionsModel;

10
src/shared/acl/utils.ts

@ -64,6 +64,7 @@ export const getCompleteRights = (roles: Role[], target: Role["id"]): Role => {
channels: fetchAccess(rolesById, target, "channels"),
activities: fetchAccess(rolesById, target, "activities"),
userSettings: fetchAccess(rolesById, target, "userSettings"),
system: fetchAccess(rolesById, target, "system"),
},
actions: {
user: {
@ -84,9 +85,12 @@ export const checkRight = (
subject: string
) => {
const channelEditRightsLens = lensPath(["access", "rights", subject]);
return any((right: AccessUnit[]) => {
return right.includes(unit);
}, roles.map(view(channelEditRightsLens)));
return any(
(right: AccessUnit[]) => {
return right.includes(unit);
},
roles.map((e) => view(channelEditRightsLens, e) ?? [AccessUnit.Deny])
);
};
const adminWritePermissions: Array<keyof AccessRights> = [

101
src/shared/graphql.ts

@ -35,6 +35,7 @@ export type AccessRights = {
channels?: Maybe<Array<Maybe<AccessUnit>>>;
roles?: Maybe<Array<Maybe<AccessUnit>>>;
streamKeys?: Maybe<Array<Maybe<AccessUnit>>>;
system?: Maybe<Array<Maybe<AccessUnit>>>;
userSettings?: Maybe<Array<Maybe<AccessUnit>>>;
users?: Maybe<Array<Maybe<AccessUnit>>>;
};
@ -477,10 +478,12 @@ export type Query = {
streamKeys: Array<Maybe<StreamKey>>;
streamKeysByChannelId: Array<Maybe<StreamKey>>;
subscription?: Maybe<Subscription>;
systemLoad: SystemLoadInfo;
test: TestResponse;
user?: Maybe<User>;
userDirectory: Array<Maybe<User>>;
userSettings: UserSettings;
userStats: UserStatsInfo;
users: Array<Maybe<User>>;
};
@ -675,6 +678,19 @@ export type SubscriptionsFilter = {
targetId?: InputMaybe<Scalars["ID"]>;
};
export type SystemLoadInfo = {
__typename?: "SystemLoadInfo";
cpuPercentage: Scalars["Float"];
dbSize: Scalars["Float"];
drivePercentage: Scalars["Float"];
mediaDirSize: Scalars["Float"];
memPercentage: Scalars["Float"];
totalDrive: Scalars["Float"];
totalMem: Scalars["Float"];
usedDrive: Scalars["Float"];
usedMem: Scalars["Float"];
};
export type TestResponse = {
__typename?: "TestResponse";
secret: Scalars["String"];
@ -793,6 +809,14 @@ export type UserSettings = {
useGravatar?: Maybe<Scalars["Boolean"]>;
};
export type UserStatsInfo = {
__typename?: "UserStatsInfo";
channelCount: Scalars["Int"];
sessionCount: Scalars["Int"];
streamKeyCount: Scalars["Int"];
userCount: Scalars["Int"];
};
export type UsersFilter = {
displayName?: InputMaybe<Scalars["String"]>;
email?: InputMaybe<Scalars["String"]>;
@ -931,6 +955,7 @@ export type ResolversTypes = {
CreateUserInput: CreateUserInput;
DateTime: ResolverTypeWrapper<Scalars["DateTime"]>;
File: ResolverTypeWrapper<File>;
Float: ResolverTypeWrapper<Scalars["Float"]>;
FullUser: ResolverTypeWrapper<FullUser>;
FullUsersFilter: FullUsersFilter;
ID: ResolverTypeWrapper<Scalars["ID"]>;
@ -957,6 +982,7 @@ export type ResolversTypes = {
SubscriptionInput: SubscriptionInput;
SubscriptionTarget: SubscriptionTarget;
SubscriptionsFilter: SubscriptionsFilter;
SystemLoadInfo: ResolverTypeWrapper<SystemLoadInfo>;
TestResponse: ResolverTypeWrapper<TestResponse>;
UpdateActivityInput: UpdateActivityInput;
UpdateChannelInput: UpdateChannelInput;
@ -973,6 +999,7 @@ export type ResolversTypes = {
UserActionsInput: UserActionsInput;
UserMeta: ResolverTypeWrapper<UserMeta>;
UserSettings: ResolverTypeWrapper<UserSettings>;
UserStatsInfo: ResolverTypeWrapper<UserStatsInfo>;
UsersFilter: UsersFilter;
};
@ -1001,6 +1028,7 @@ export type ResolversParentTypes = {
CreateUserInput: CreateUserInput;
DateTime: Scalars["DateTime"];
File: File;
Float: Scalars["Float"];
FullUser: FullUser;
FullUsersFilter: FullUsersFilter;
ID: Scalars["ID"];
@ -1024,6 +1052,7 @@ export type ResolversParentTypes = {
SubscriptionByTargetId: SubscriptionByTargetId;
SubscriptionInput: SubscriptionInput;
SubscriptionsFilter: SubscriptionsFilter;
SystemLoadInfo: SystemLoadInfo;
TestResponse: TestResponse;
UpdateActivityInput: UpdateActivityInput;
UpdateChannelInput: UpdateChannelInput;
@ -1040,6 +1069,7 @@ export type ResolversParentTypes = {
UserActionsInput: UserActionsInput;
UserMeta: UserMeta;
UserSettings: UserSettings;
UserStatsInfo: UserStatsInfo;
UsersFilter: UsersFilter;
};
@ -1079,6 +1109,11 @@ export type AccessRightsResolvers<
ParentType,
ContextType
>;
system?: Resolver<
Maybe<Array<Maybe<ResolversTypes["AccessUnit"]>>>,
ParentType,
ContextType
>;
userSettings?: Resolver<
Maybe<Array<Maybe<ResolversTypes["AccessUnit"]>>>,
ParentType,
@ -1659,6 +1694,11 @@ export type QueryResolvers<
ContextType,
RequireFields<QuerySubscriptionArgs, never>
>;
systemLoad?: Resolver<
ResolversTypes["SystemLoadInfo"],
ParentType,
ContextType
>;
test?: Resolver<ResolversTypes["TestResponse"], ParentType, ContextType>;
user?: Resolver<
Maybe<ResolversTypes["User"]>,
@ -1677,6 +1717,11 @@ export type QueryResolvers<
ParentType,
ContextType
>;
userStats?: Resolver<
ResolversTypes["UserStatsInfo"],
ParentType,
ContextType
>;
users?: Resolver<
Array<Maybe<ResolversTypes["User"]>>,
ParentType,
@ -1777,6 +1822,22 @@ export type SubscriptionResolvers<
>;
};
export type SystemLoadInfoResolvers<
ContextType = any,
ParentType extends ResolversParentTypes["SystemLoadInfo"] = ResolversParentTypes["SystemLoadInfo"]
> = {
cpuPercentage?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
dbSize?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
drivePercentage?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
mediaDirSize?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
memPercentage?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
totalDrive?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
totalMem?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
usedDrive?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
usedMem?: Resolver<ResolversTypes["Float"], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type TestResponseResolvers<
ContextType = any,
ParentType extends ResolversParentTypes["TestResponse"] = ResolversParentTypes["TestResponse"]
@ -1889,6 +1950,17 @@ export type UserSettingsResolvers<
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type UserStatsInfoResolvers<
ContextType = any,
ParentType extends ResolversParentTypes["UserStatsInfo"] = ResolversParentTypes["UserStatsInfo"]
> = {
channelCount?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
sessionCount?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
streamKeyCount?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
userCount?: Resolver<ResolversTypes["Int"], ParentType, ContextType>;
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
};
export type Resolvers<ContextType = any> = {
AccessRights?: AccessRightsResolvers<ContextType>;
AccessTargets?: AccessTargetsResolvers<ContextType>;
@ -1909,6 +1981,7 @@ export type Resolvers<ContextType = any> = {
SessionResponse?: SessionResponseResolvers<ContextType>;
StreamKey?: StreamKeyResolvers<ContextType>;
Subscription?: SubscriptionResolvers<ContextType>;
SystemLoadInfo?: SystemLoadInfoResolvers<ContextType>;
TestResponse?: TestResponseResolvers<ContextType>;
Upload?: GraphQLScalarType;
User?: UserResolvers<ContextType>;
@ -1916,12 +1989,40 @@ export type Resolvers<ContextType = any> = {
UserActions?: UserActionsResolvers<ContextType>;
UserMeta?: UserMetaResolvers<ContextType>;
UserSettings?: UserSettingsResolvers<ContextType>;
UserStatsInfo?: UserStatsInfoResolvers<ContextType>;
};
export type DirectiveResolvers<ContextType = any> = {
auth?: AuthDirectiveResolver<any, any, ContextType>;
};
export type AdminDashboardSystemLoadQueryVariables = Exact<{
[key: string]: never;
}>;
export type AdminDashboardSystemLoadQuery = {
__typename?: "Query";
userStats: {
__typename?: "UserStatsInfo";
userCount: number;
channelCount: number;
streamKeyCount: number;
sessionCount: number;
};
systemLoad: {
__typename?: "SystemLoadInfo";
cpuPercentage: number;
totalMem: number;
usedMem: number;
memPercentage: number;
totalDrive: number;
usedDrive: number;
drivePercentage: number;
mediaDirSize: number;
dbSize: number;
};
};
export type AdminChannelsCountQueryVariables = Exact<{
filter?: InputMaybe<ChannelsQueryFilter>;
}>;

71
src/shared/hooks.ts

@ -326,6 +326,77 @@ export const CurrentUserFragmentDoc = gql`
}
}
`;
export const AdminDashboardSystemLoadDocument = gql`
query AdminDashboardSystemLoad {
userStats {
userCount
channelCount
streamKeyCount
sessionCount
}
systemLoad {
cpuPercentage
totalMem
usedMem
memPercentage
totalDrive
usedDrive
drivePercentage
mediaDirSize
dbSize
}
}
`;
/**
* __useAdminDashboardSystemLoadQuery__
*
* To run a query within a React component, call `useAdminDashboardSystemLoadQuery` and pass it any options that fit your needs.
* When your component renders, `useAdminDashboardSystemLoadQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useAdminDashboardSystemLoadQuery({
* variables: {
* },
* });
*/
export function useAdminDashboardSystemLoadQuery(
baseOptions?: Apollo.QueryHookOptions<
Types.AdminDashboardSystemLoadQuery,
Types.AdminDashboardSystemLoadQueryVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useQuery<
Types.AdminDashboardSystemLoadQuery,
Types.AdminDashboardSystemLoadQueryVariables
>(AdminDashboardSystemLoadDocument, options);
}
export function useAdminDashboardSystemLoadLazyQuery(
baseOptions?: Apollo.LazyQueryHookOptions<
Types.AdminDashboardSystemLoadQuery,
Types.AdminDashboardSystemLoadQueryVariables
>
) {
const options = { ...defaultOptions, ...baseOptions };
return Apollo.useLazyQuery<
Types.AdminDashboardSystemLoadQuery,
Types.AdminDashboardSystemLoadQueryVariables
>(AdminDashboardSystemLoadDocument, options);
}
export type AdminDashboardSystemLoadQueryHookResult = ReturnType<
typeof useAdminDashboardSystemLoadQuery
>;
export type AdminDashboardSystemLoadLazyQueryHookResult = ReturnType<
typeof useAdminDashboardSystemLoadLazyQuery
>;
export type AdminDashboardSystemLoadQueryResult = Apollo.QueryResult<
Types.AdminDashboardSystemLoadQuery,
Types.AdminDashboardSystemLoadQueryVariables
>;
export const AdminChannelsCountDocument = gql`
query AdminChannelsCount($filter: ChannelsQueryFilter) {
fullChannelsCount(filter: $filter)

10
yarn.lock

@ -2205,6 +2205,11 @@
resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.1.tgz#e5893236ce922152d57c5f3f978f764f4deeb45f"
integrity sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ==
"@types/node-os-utils@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@types/node-os-utils/-/node-os-utils-1.2.0.tgz#d03286eca0642f861cbee506bc1f4dd868a15826"
integrity sha512-sstDo2s8gQR/Qh2Bd6yNQ5xJv+D4ttyB9ZVjB3mBm3VkGl2RoqjV7fiw9mIHLbshHSRm2m8uf0XrIRQNWB/z6A==
"@types/node@*", "@types/node@>=10.0.0":
version "17.0.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.7.tgz#4a53d8332bb65a45470a2f9e2611f1ced637a5cb"
@ -6428,6 +6433,11 @@ node-libs-browser@^2.2.1:
util "^0.11.0"
vm-browserify "^1.0.1"
node-os-utils@^1.3.5:
version "1.3.5"
resolved "https://registry.yarnpkg.com/node-os-utils/-/node-os-utils-1.3.5.tgz#d34a22c7d15af76f13e4c1e05634b905e780b3cf"
integrity sha512-bIJIlk+hA+7/ATnu3sQMtF697iw9T/JksDhKMe9uENG0OhzIG7hLM6fbcyu18bOuajlYWnSlj0IhDo2q7k0ebg==
node-releases@^1.1.71:
version "1.1.77"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e"

Loading…
Cancel
Save