diff --git a/components/layout/AppLayout.js b/components/layout/AppLayout.js
index d89c34b9..ad744fab 100644
--- a/components/layout/AppLayout.js
+++ b/components/layout/AppLayout.js
@@ -2,13 +2,15 @@ import { Container } from 'react-basics';
import Head from 'next/head';
import NavBar from 'components/layout/NavBar';
import useRequireLogin from 'hooks/useRequireLogin';
+import useConfig from 'hooks/useConfig';
import { UI_LAYOUT_BODY } from 'lib/constants';
import styles from './AppLayout.module.css';
export default function AppLayout({ title, children }) {
const { user } = useRequireLogin();
+ const config = useConfig();
- if (!user) {
+ if (!user || !config) {
return null;
}
diff --git a/components/layout/NavBar.js b/components/layout/NavBar.js
index 07a72bc1..80307568 100644
--- a/components/layout/NavBar.js
+++ b/components/layout/NavBar.js
@@ -10,9 +10,11 @@ import { labels } from 'components/messages';
import useUser from 'hooks/useUser';
import NavGroup from './NavGroup';
import styles from './NavBar.module.css';
+import useConfig from 'hooks/useConfig';
export default function NavBar() {
const { user } = useUser();
+ const { cloudMode } = useConfig();
const { formatMessage } = useIntl();
const [minimized, setMinimized] = useState(false);
const tooltipPosition = minimized ? 'right' : 'top';
@@ -24,13 +26,21 @@ export default function NavBar() {
];
const settings = [
- { label: formatMessage(labels.websites), url: '/settings/websites', icon: },
+ !cloudMode && {
+ label: formatMessage(labels.websites),
+ url: '/settings/websites',
+ icon: ,
+ },
user?.isAdmin && {
label: formatMessage(labels.users),
url: '/settings/users',
icon: ,
},
- { label: formatMessage(labels.teams), url: '/settings/teams', icon: },
+ !cloudMode && {
+ label: formatMessage(labels.teams),
+ url: '/settings/teams',
+ icon: ,
+ },
{ label: formatMessage(labels.profile), url: '/settings/profile', icon: },
].filter(n => n);
@@ -53,7 +63,7 @@ export default function NavBar() {
-
+ {!cloudMode && }
diff --git a/components/pages/settings/profile/ProfileSettings.js b/components/pages/settings/profile/ProfileSettings.js
index 9d22cfae..d302b6b2 100644
--- a/components/pages/settings/profile/ProfileSettings.js
+++ b/components/pages/settings/profile/ProfileSettings.js
@@ -4,14 +4,16 @@ import PageHeader from 'components/layout/PageHeader';
import ProfileDetails from './ProfileDetails';
import PasswordChangeButton from './PasswordChangeButton';
import { labels } from 'components/messages';
+import useConfig from 'hooks/useConfig';
export default function ProfileSettings() {
const { formatMessage } = useIntl();
+ const { cloudMode } = useConfig();
return (
-
+ {!cloudMode && }
diff --git a/components/pages/settings/teams/TeamsList.js b/components/pages/settings/teams/TeamsList.js
index a2ed628c..b48e9971 100644
--- a/components/pages/settings/teams/TeamsList.js
+++ b/components/pages/settings/teams/TeamsList.js
@@ -65,7 +65,7 @@ export default function TeamsList() {
return (
{toast}
-
+
{hasData && (
{joinButton}
diff --git a/components/pages/settings/websites/WebsiteReset.js b/components/pages/settings/websites/WebsiteData.js
similarity index 61%
rename from components/pages/settings/websites/WebsiteReset.js
rename to components/pages/settings/websites/WebsiteData.js
index d742d113..64979fe4 100644
--- a/components/pages/settings/websites/WebsiteReset.js
+++ b/components/pages/settings/websites/WebsiteData.js
@@ -1,10 +1,10 @@
-import { Button, Form, FormRow, Modal, ModalTrigger } from 'react-basics';
+import { Button, Modal, ModalTrigger, ActionForm } from 'react-basics';
import { useIntl } from 'react-intl';
import WebsiteDeleteForm from 'components/pages/settings/websites/WebsiteDeleteForm';
import WebsiteResetForm from 'components/pages/settings/websites/WebsiteResetForm';
import { labels, messages } from 'components/messages';
-export default function WebsiteReset({ websiteId, onSave }) {
+export default function WebsiteData({ websiteId, onSave }) {
const { formatMessage } = useIntl();
const handleReset = async () => {
@@ -16,29 +16,33 @@ export default function WebsiteReset({ websiteId, onSave }) {
};
return (
-
+
+ >
);
}
diff --git a/components/pages/settings/websites/WebsiteSettings.js b/components/pages/settings/websites/WebsiteSettings.js
index e61c699d..02693c72 100644
--- a/components/pages/settings/websites/WebsiteSettings.js
+++ b/components/pages/settings/websites/WebsiteSettings.js
@@ -6,7 +6,7 @@ import Link from 'next/link';
import Page from 'components/layout/Page';
import PageHeader from 'components/layout/PageHeader';
import WebsiteEditForm from 'components/pages/settings/websites/WebsiteEditForm';
-import WebsiteReset from 'components/pages/settings/websites/WebsiteReset';
+import WebsiteData from 'components/pages/settings/websites/WebsiteData';
import TrackingCode from 'components/pages/settings/websites/TrackingCode';
import ShareUrl from 'components/pages/settings/websites/ShareUrl';
import useApi from 'hooks/useApi';
@@ -59,8 +59,8 @@ export default function WebsiteSettings({ websiteId }) {
}
>
-
-
+
+
);
}
diff --git a/lib/types.ts b/lib/types.ts
index 8b08a343..b201d1b4 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -24,6 +24,13 @@ export interface NextApiRequestAuth extends NextApiRequest {
headers: any;
}
+export interface User {
+ id: string;
+ username: string;
+ password?: string;
+ createdAt?: Date;
+}
+
export interface Website {
id: string;
userId: string;
diff --git a/package.json b/package.json
index a9cb51b0..a660e670 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "umami",
- "version": "2.0.0-beta.3",
+ "version": "2.0.0-beta.4",
"description": "A simple, fast, privacy-focused alternative to Google Analytics.",
"author": "Mike Cao ",
"license": "MIT",
diff --git a/pages/api/auth/login.ts b/pages/api/auth/login.ts
index d81017da..4a0ff4cf 100644
--- a/pages/api/auth/login.ts
+++ b/pages/api/auth/login.ts
@@ -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 {
diff --git a/pages/api/config.ts b/pages/api/config.ts
index e5ae318a..32555ec5 100644
--- a/pages/api/config.ts
+++ b/pages/api/config.ts
@@ -7,6 +7,7 @@ export interface ConfigResponse {
updatesDisabled: boolean;
telemetryDisabled: boolean;
adminDisabled: boolean;
+ cloudMode: boolean;
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
@@ -17,6 +18,7 @@ export default async (req: NextApiRequest, res: NextApiResponse)
updatesDisabled: !!process.env.DISABLE_UPDATES,
telemetryDisabled: !!process.env.DISABLE_TELEMETRY,
adminDisabled: !!process.env.DISABLE_ADMIN,
+ cloudMode: true, //!!process.env.CLOUD_MODE,
});
}
diff --git a/pages/api/me/index.ts b/pages/api/me/index.ts
new file mode 100644
index 00000000..93e97067
--- /dev/null
+++ b/pages/api/me/index.ts
@@ -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,
+ res: NextApiResponse,
+) => {
+ await useAuth(req, res);
+
+ return ok(res, req.auth.user);
+};
diff --git a/pages/api/me/password.ts b/pages/api/me/password.ts
index 862609e1..55a9993b 100644
--- a/pages/api/me/password.ts
+++ b/pages/api/me/password.ts
@@ -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,
res: NextApiResponse,
) => {
+ if (process.env.CLOUD_MODE) {
+ return forbidden(res);
+ }
+
await useAuth(req, res);
const { currentPassword, newPassword } = req.body;
diff --git a/pages/console/[[...id]].js b/pages/console/[[...id]].js
index 664f87fb..b4bcf254 100644
--- a/pages/console/[[...id]].js
+++ b/pages/console/[[...id]].js
@@ -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,
},
};
}
diff --git a/pages/login.js b/pages/login.js
index eb837e21..88c19121 100644
--- a/pages/login.js
+++ b/pages/login.js
@@ -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.DISABLE_LOGIN,
+ disabled: !!(process.env.DISABLE_LOGIN || process.env.CLOUD_MODE),
},
};
}
diff --git a/pages/logout.js b/pages/logout.js
index 6d486022..8f102f6a 100644
--- a/pages/logout.js
+++ b/pages/logout.js
@@ -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),
+ },
+ };
+}
diff --git a/pages/settings/index.js b/pages/settings/index.js
index 70e8d249..0c213fe6 100644
--- a/pages/settings/index.js
+++ b/pages/settings/index.js
@@ -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,
},
};
diff --git a/pages/settings/teams/[id]/index.js b/pages/settings/teams/[id]/index.js
index 5f34aa71..ba4e812c 100644
--- a/pages/settings/teams/[id]/index.js
+++ b/pages/settings/teams/[id]/index.js
@@ -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 (
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/pages/settings/teams/index.js b/pages/settings/teams/index.js
index a159d6e3..a4048454 100644
--- a/pages/settings/teams/index.js
+++ b/pages/settings/teams/index.js
@@ -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 (
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/pages/settings/users/[id]/index.js b/pages/settings/users/[id]/index.js
index d0b79f01..8631baa6 100644
--- a/pages/settings/users/[id]/index.js
+++ b/pages/settings/users/[id]/index.js
@@ -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 (
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/pages/settings/users/index.js b/pages/settings/users/index.js
index 272e166c..f4d75f29 100644
--- a/pages/settings/users/index.js
+++ b/pages/settings/users/index.js
@@ -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() {
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/pages/settings/websites/[id]/index.js b/pages/settings/websites/[id]/index.js
index 12e45e48..033bdab5 100644
--- a/pages/settings/websites/[id]/index.js
+++ b/pages/settings/websites/[id]/index.js
@@ -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() {
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/pages/settings/websites/index.js b/pages/settings/websites/index.js
index f2399f06..9944ea43 100644
--- a/pages/settings/websites/index.js
+++ b/pages/settings/websites/index.js
@@ -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 (
);
}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ disabled: !!process.env.CLOUD_MODE,
+ },
+ };
+}
diff --git a/queries/admin/user.ts b/queries/admin/user.ts
index ba6ef186..9455c311 100644
--- a/queries/admin/user.ts
+++ b/queries/admin/user.ts
@@ -1,14 +1,7 @@
import { Prisma, Team } from '@prisma/client';
import cache from 'lib/cache';
import prisma from 'lib/prisma';
-import { Website } from 'lib/types';
-
-export interface User {
- id: string;
- username: string;
- password?: string;
- createdAt?: Date;
-}
+import { Website, User } from 'lib/types';
export async function getUser(
where: Prisma.UserWhereUniqueInput,
diff --git a/styles/index.css b/styles/index.css
index 664bfabb..42b2534e 100644
--- a/styles/index.css
+++ b/styles/index.css
@@ -1,3 +1,8 @@
+html {
+ overflow-x: hidden;
+ margin-right: calc(-1 * (100vw - 100%));
+}
+
html,
body {
font-family: Inter, -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans,