diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5629d4533..8d401771f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,4 +37,5 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} repository: sctx/overseerr + build_args: COMMIT_TAG=${{ github.sha }} tags: develop diff --git a/Dockerfile b/Dockerfile index d157d429b..35fb08b1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,9 @@ RUN yarn cache clean FROM node:12.18-alpine +ARG COMMIT_TAG +ENV COMMIT_TAG=${COMMIT_TAG} + COPY . /app WORKDIR /app diff --git a/overseerr-api.yml b/overseerr-api.yml index 4b92a9eef..07da768af 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -1478,6 +1478,29 @@ paths: application/json: schema: $ref: '#/components/schemas/DiscordSettings' + /settings/about: + get: + summary: Return current about stats + description: Returns current server stats in JSON format + tags: + - settings + responses: + '200': + description: Returned about settings + content: + application/json: + schema: + type: object + properties: + version: + type: string + example: '1.0.0' + totalRequests: + type: number + example: 100 + totalMediaItems: + type: number + example: 100 /auth/me: get: summary: Returns the currently logged in user diff --git a/server/interfaces/api/settingsInterfaces.ts b/server/interfaces/api/settingsInterfaces.ts new file mode 100644 index 000000000..bc6dcf024 --- /dev/null +++ b/server/interfaces/api/settingsInterfaces.ts @@ -0,0 +1,5 @@ +export interface SettingsAboutResponse { + version: string; + totalRequests: number; + totalMediaItems: number; +} diff --git a/server/routes/settings.ts b/server/routes/settings.ts index 422510f2e..93a69b40c 100644 --- a/server/routes/settings.ts +++ b/server/routes/settings.ts @@ -17,6 +17,9 @@ import { scheduledJobs } from '../job/schedule'; import { Permission } from '../lib/permissions'; import { isAuthenticated } from '../middleware/auth'; import { merge } from 'lodash'; +import { version } from '../../package.json'; +import Media from '../entity/Media'; +import { MediaRequest } from '../entity/MediaRequest'; const settingsRoutes = Router(); @@ -431,4 +434,23 @@ settingsRoutes.post('/notifications/email', (req, res) => { res.status(200).json(settings.notifications.agents.email); }); +settingsRoutes.get('/about', async (req, res) => { + const mediaRepository = getRepository(Media); + const mediaRequestRepository = getRepository(MediaRequest); + + const totalMediaItems = await mediaRepository.count(); + const totalRequests = await mediaRequestRepository.count(); + + let finalVersion = version; + + if (version === '0.1.0') { + finalVersion = `develop-${process.env.COMMIT_TAG ?? 'local'}`; + } + return res.status(200).json({ + version: finalVersion, + totalMediaItems, + totalRequests, + }); +}); + export default settingsRoutes; diff --git a/src/components/Common/List/index.tsx b/src/components/Common/List/index.tsx new file mode 100644 index 000000000..462f6ce84 --- /dev/null +++ b/src/components/Common/List/index.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { withProperties } from '../../../utils/typeHelpers'; + +interface ListItemProps { + title: string; +} + +const ListItem: React.FC = ({ title, children }) => { + return ( +
+
{title}
+
+ {children} +
+
+ ); +}; + +interface ListProps { + title: string; + subTitle?: string; +} + +const List: React.FC = ({ title, subTitle, children }) => { + return ( + <> +
+

{title}

+ {subTitle && ( +

{subTitle}

+ )} +
+
+
{children}
+
+ + ); +}; + +export default withProperties(List, { Item: ListItem }); diff --git a/src/components/Settings/SettingsAbout/index.tsx b/src/components/Settings/SettingsAbout/index.tsx new file mode 100644 index 000000000..b11d64add --- /dev/null +++ b/src/components/Settings/SettingsAbout/index.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import useSWR from 'swr'; +import Error from '../../../pages/_error'; +import List from '../../Common/List'; +import LoadingSpinner from '../../Common/LoadingSpinner'; +import { SettingsAboutResponse } from '../../../../server/interfaces/api/settingsInterfaces'; +import { FormattedNumber } from 'react-intl'; + +const SettingsAbout: React.FC = () => { + const { data, error } = useSWR( + '/api/v1/settings/about' + ); + + if (error) { + return ; + } + + if (!data && !error) { + return ; + } + + if (!data) { + return ; + } + + return ( + <> +
+ + {data.version} + + + + + + + +
+ + + ); +}; + +export default SettingsAbout; diff --git a/src/pages/settings/about.tsx b/src/pages/settings/about.tsx new file mode 100644 index 000000000..442669d9c --- /dev/null +++ b/src/pages/settings/about.tsx @@ -0,0 +1,17 @@ +import { NextPage } from 'next'; +import React from 'react'; +import SettingsAbout from '../../components/Settings/SettingsAbout'; +import SettingsLayout from '../../components/Settings/SettingsLayout'; +import useRouteGuard from '../../hooks/useRouteGuard'; +import { Permission } from '../../hooks/useUser'; + +const SettingsAboutPage: NextPage = () => { + useRouteGuard(Permission.MANAGE_SETTINGS); + return ( + + + + ); +}; + +export default SettingsAboutPage;