mirror of
https://github.com/umami-software/umami.git
synced 2026-02-10 07:37:11 +01:00
Merge branch 'dev' of https://github.com/umami-software/umami into feat/um-171-cloud-mode-env-variable
This commit is contained in:
commit
a777b2916f
88 changed files with 1014 additions and 945 deletions
18
pages/404.js
18
pages/404.js
|
|
@ -1,18 +1,20 @@
|
|||
import { Row, Column, Flexbox } from 'react-basics';
|
||||
import { useIntl } from 'react-intl';
|
||||
import AppLayout from 'components/layout/AppLayout';
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
notFound: { id: 'message.page-not-found', defaultMessage: 'Page not found' },
|
||||
});
|
||||
import { labels } from 'components/messages';
|
||||
|
||||
export default function Custom404() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<div className="row justify-content-center">
|
||||
<h1 style={{ textAlign: 'center' }}>{formatMessage(messages.notFound)}</h1>
|
||||
</div>
|
||||
<Row>
|
||||
<Column>
|
||||
<Flexbox alignItems="center" justifyContent="center" flex={1} style={{ minHeight: 600 }}>
|
||||
<h1>{formatMessage(labels.pageNotFound)}</h1>
|
||||
</Flexbox>
|
||||
</Column>
|
||||
</Row>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import '@fontsource/inter/600.css';
|
|||
const client = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import {
|
|||
getRandomChars,
|
||||
} from 'next-basics';
|
||||
import redis from '@umami/redis-client';
|
||||
import { getUser, User } from 'queries';
|
||||
import { getUser } from 'queries';
|
||||
import { secret } from 'lib/crypto';
|
||||
import { NextApiRequestQueryBody } from 'lib/types';
|
||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
||||
import { NextApiResponse } from 'next';
|
||||
|
||||
export interface LoginRequestBody {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface ConfigResponse {
|
|||
updatesDisabled: boolean;
|
||||
telemetryDisabled: boolean;
|
||||
adminDisabled: boolean;
|
||||
cloudMode: boolean;
|
||||
}
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse<ConfigResponse>) => {
|
||||
|
|
@ -16,7 +17,8 @@ export default async (req: NextApiRequest, res: NextApiResponse<ConfigResponse>)
|
|||
trackerScriptName: process.env.TRACKER_SCRIPT_NAME,
|
||||
updatesDisabled: !!process.env.DISABLE_UPDATES,
|
||||
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
|
||||
adminDisabled: !!process.env.CLOUD_MODE,
|
||||
adminDisabled: !!process.env.DISABLE_ADMIN,
|
||||
cloudMode: process.env.CLOUD_MODE,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
13
pages/api/me/index.ts
Normal file
13
pages/api/me/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { NextApiResponse } from 'next';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
||||
import { ok } from 'next-basics';
|
||||
|
||||
export default async (
|
||||
req: NextApiRequestQueryBody<unknown, unknown>,
|
||||
res: NextApiResponse<User>,
|
||||
) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
return ok(res, req.auth.user);
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { NextApiRequestQueryBody } from 'lib/types';
|
||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
||||
import { canUpdateUser } from 'lib/auth';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiResponse } from 'next';
|
||||
|
|
@ -7,10 +7,11 @@ import {
|
|||
checkPassword,
|
||||
hashPassword,
|
||||
methodNotAllowed,
|
||||
forbidden,
|
||||
ok,
|
||||
unauthorized,
|
||||
} from 'next-basics';
|
||||
import { getUser, updateUser, User } from 'queries';
|
||||
import { getUser, updateUser } from 'queries';
|
||||
|
||||
export interface UserPasswordRequestQuery {
|
||||
id: string;
|
||||
|
|
@ -25,6 +26,10 @@ export default async (
|
|||
req: NextApiRequestQueryBody<UserPasswordRequestQuery, UserPasswordRequestBody>,
|
||||
res: NextApiResponse<User>,
|
||||
) => {
|
||||
if (process.env.CLOUD_MODE) {
|
||||
return forbidden(res);
|
||||
}
|
||||
|
||||
await useAuth(req, res);
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
|
|
|
|||
25
pages/api/realtime/[id].ts
Normal file
25
pages/api/realtime/[id].ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { subMinutes } from 'date-fns';
|
||||
import { RealtimeInit, NextApiRequestAuth } from 'lib/types';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { methodNotAllowed, ok } from 'next-basics';
|
||||
import { getRealtimeData } from 'queries';
|
||||
|
||||
export default async (req: NextApiRequestAuth, res: NextApiResponse<RealtimeInit>) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const { id, startAt } = req.query;
|
||||
let startTime = subMinutes(new Date(), 30);
|
||||
|
||||
if (+startAt > startTime.getTime()) {
|
||||
startTime = new Date(+startAt);
|
||||
}
|
||||
|
||||
const data = await getRealtimeData(id, startTime);
|
||||
|
||||
return ok(res, data);
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
};
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import { subMinutes } from 'date-fns';
|
||||
import { RealtimeInit } from 'lib/types';
|
||||
import { NextApiRequestAuth } from 'lib/types';
|
||||
import { secret } from 'lib/crypto';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { createToken, methodNotAllowed, ok } from 'next-basics';
|
||||
import { getRealtimeData, getUserWebsites } from 'queries';
|
||||
|
||||
export default async (req: NextApiRequestAuth, res: NextApiResponse<RealtimeInit>) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const { id: userId } = req.auth.user;
|
||||
|
||||
const websites = await getUserWebsites(userId);
|
||||
const ids = websites.map(({ id }) => id);
|
||||
const token = createToken({ websites: ids }, secret());
|
||||
const data = await getRealtimeData(ids, subMinutes(new Date(), 30));
|
||||
|
||||
return ok(res, {
|
||||
websites,
|
||||
token,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
};
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import { ok, methodNotAllowed, badRequest, parseToken } from 'next-basics';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { getRealtimeData } from 'queries';
|
||||
import { SHARE_TOKEN_HEADER } from 'lib/constants';
|
||||
import { secret } from 'lib/crypto';
|
||||
import { NextApiRequestQueryBody } from 'lib/types';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { RealtimeUpdate } from 'lib/types';
|
||||
|
||||
export interface InitUpdateRequestQuery {
|
||||
startAt: string;
|
||||
}
|
||||
|
||||
export default async (
|
||||
req: NextApiRequestQueryBody<InitUpdateRequestQuery>,
|
||||
res: NextApiResponse<RealtimeUpdate>,
|
||||
) => {
|
||||
await useAuth(req, res);
|
||||
|
||||
if (req.method === 'GET') {
|
||||
const { startAt } = req.query;
|
||||
|
||||
const token = req.headers[SHARE_TOKEN_HEADER];
|
||||
|
||||
if (!token) {
|
||||
return badRequest(res);
|
||||
}
|
||||
|
||||
const { websites } = parseToken(token, secret());
|
||||
|
||||
const data = await getRealtimeData(websites, new Date(+startAt));
|
||||
|
||||
return ok(res, data);
|
||||
}
|
||||
|
||||
return methodNotAllowed(res);
|
||||
};
|
||||
|
|
@ -2,10 +2,10 @@ import { canCreateUser, canViewUsers } from 'lib/auth';
|
|||
import { ROLES } from 'lib/constants';
|
||||
import { uuid } from 'lib/crypto';
|
||||
import { useAuth } from 'lib/middleware';
|
||||
import { NextApiRequestQueryBody } from 'lib/types';
|
||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { badRequest, hashPassword, methodNotAllowed, ok, unauthorized } from 'next-basics';
|
||||
import { createUser, getUser, getUsers, User } from 'queries';
|
||||
import { createUser, getUser, getUsers } from 'queries';
|
||||
|
||||
export interface UsersRequestBody {
|
||||
username: string;
|
||||
|
|
@ -36,7 +36,7 @@ export default async (
|
|||
|
||||
const { username, password, id } = req.body;
|
||||
|
||||
const existingUser = await getUser({ username });
|
||||
const existingUser = await getUser({ username }, { showDeleted: true });
|
||||
|
||||
if (existingUser) {
|
||||
return badRequest(res, 'User already exists');
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import AppLayout from 'components/layout/AppLayout';
|
||||
import TestConsole from 'components/pages/console/TestConsole';
|
||||
|
||||
export default function ConsolePage({ pageDisabled }) {
|
||||
if (pageDisabled) {
|
||||
export default function ConsolePage({ disabled }) {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export default function ConsolePage({ pageDisabled }) {
|
|||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
pageDisabled: !process.env.ENABLE_TEST_CONSOLE,
|
||||
disabled: !process.env.ENABLE_TEST_CONSOLE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import LoginLayout from 'components/pages/login/LoginLayout';
|
||||
import LoginForm from 'components/pages/login/LoginForm';
|
||||
|
||||
export default function LoginPage({ pageDisabled }) {
|
||||
if (pageDisabled) {
|
||||
export default function LoginPage({ disabled }) {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ export default function LoginPage({ pageDisabled }) {
|
|||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
pageDisabled: !!process.env.CLOUD_MODE,
|
||||
disabled: !!(process.env.DISABLE_LOGIN || process.env.CLOUD_MODE),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import useApi from 'hooks/useApi';
|
|||
import { setUser } from 'store/app';
|
||||
import { removeClientAuthToken } from 'lib/client';
|
||||
|
||||
export default function LogoutPage() {
|
||||
export default function LogoutPage({ disabled }) {
|
||||
const router = useRouter();
|
||||
const { post } = useApi();
|
||||
|
||||
|
|
@ -13,14 +13,24 @@ export default function LogoutPage() {
|
|||
await post('/logout');
|
||||
}
|
||||
|
||||
removeClientAuthToken();
|
||||
if (!disabled) {
|
||||
removeClientAuthToken();
|
||||
|
||||
logout();
|
||||
logout();
|
||||
|
||||
router.push('/login');
|
||||
router.push('/login');
|
||||
|
||||
return () => setUser(null);
|
||||
}, []);
|
||||
return () => setUser(null);
|
||||
}
|
||||
}, [disabled, router, post]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!(process.env.DISABLE_LOGIN || process.env.CLOUD_MODE),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
18
pages/realtime/[id]/index.js
Normal file
18
pages/realtime/[id]/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import { useRouter } from 'next/router';
|
||||
import AppLayout from 'components/layout/AppLayout';
|
||||
import RealtimeDashboard from 'components/pages/realtime/RealtimeDashboard';
|
||||
|
||||
export default function RealtimeDetailsPage() {
|
||||
const router = useRouter();
|
||||
const { id: websiteId } = router.query;
|
||||
|
||||
if (!websiteId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<RealtimeDashboard key={websiteId} websiteId={websiteId} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import AppLayout from 'components/layout/AppLayout';
|
||||
import RealtimeDashboard from 'components/pages/realtime/RealtimeDashboard';
|
||||
import RealtimeHome from 'components/pages/realtime/RealtimeHome';
|
||||
|
||||
export default function RealtimePage() {
|
||||
return (
|
||||
<AppLayout>
|
||||
<RealtimeDashboard />
|
||||
<RealtimeHome />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
export default () => null;
|
||||
|
||||
export async function getServerSideProps() {
|
||||
const destination = process.env.CLOUD_MODE ? 'https://cloud.umami.is' : '/settings/websites';
|
||||
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/settings/websites',
|
||||
destination,
|
||||
permanent: true,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,13 +2,25 @@ import AppLayout from 'components/layout/AppLayout';
|
|||
import TeamSettings from 'components/pages/settings/teams/TeamSettings';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export default function TeamDetailPage() {
|
||||
export default function TeamDetailPage({ disabled }) {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
|
||||
if (!id || disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<TeamSettings teamId={id} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,10 +1,22 @@
|
|||
import AppLayout from 'components/layout/AppLayout';
|
||||
import TeamsList from 'components/pages/settings/teams/TeamsList';
|
||||
|
||||
export default function TeamsPage() {
|
||||
export default function TeamsPage({ disabled }) {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<TeamsList />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,25 @@ import AppLayout from 'components/layout/AppLayout';
|
|||
import UserSettings from 'components/pages/settings/users/UserSettings';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
export default function TeamDetailPage() {
|
||||
export default function TeamDetailPage({ disabled }) {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
|
||||
if (!id || disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<UserSettings userId={id} />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,12 +1,8 @@
|
|||
import AppLayout from 'components/layout/AppLayout';
|
||||
import useConfig from 'hooks/useConfig';
|
||||
|
||||
import UsersList from 'components/pages/settings/users/UsersList';
|
||||
|
||||
export default function UsersPage() {
|
||||
const { adminDisabled } = useConfig();
|
||||
|
||||
if (adminDisabled) {
|
||||
export default function UsersPage({ disabled }) {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -16,3 +12,11 @@ export default function UsersPage() {
|
|||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ import { useRouter } from 'next/router';
|
|||
import WebsiteSettings from 'components/pages/settings/websites/WebsiteSettings';
|
||||
import AppLayout from 'components/layout/AppLayout';
|
||||
|
||||
export default function WebsiteSettingsPage() {
|
||||
export default function WebsiteSettingsPage({ disabled }) {
|
||||
const router = useRouter();
|
||||
const { id } = router.query;
|
||||
|
||||
if (!id) {
|
||||
if (!id || disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -16,3 +16,11 @@ export default function WebsiteSettingsPage() {
|
|||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,22 @@
|
|||
import AppLayout from 'components/layout/AppLayout';
|
||||
import WebsitesList from 'components/pages/settings/websites/WebsitesList';
|
||||
|
||||
export default function WebsitesPage() {
|
||||
export default function WebsitesPage({ disabled }) {
|
||||
if (disabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AppLayout>
|
||||
<WebsitesList />
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
return {
|
||||
props: {
|
||||
disabled: !!process.env.CLOUD_MODE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue