mirror of
https://github.com/umami-software/umami.git
synced 2026-02-12 16:45:35 +01:00
Compare commits
5 commits
26bd498a05
...
d8b3c8d13c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b3c8d13c | ||
|
|
656fb8a3d1 | ||
|
|
5c9f97182e | ||
|
|
48c7028a3a | ||
|
|
9897c725de |
23 changed files with 43 additions and 90 deletions
|
|
@ -5,7 +5,7 @@ const TRACKER_SCRIPT = '/script.js';
|
|||
|
||||
const basePath = process.env.BASE_PATH;
|
||||
const collectApiEndpoint = process.env.COLLECT_API_ENDPOINT;
|
||||
const cloudUrl = process.env.CLOUD_URL;
|
||||
const cloudMode = !!process.env.CLOUD_MODE;
|
||||
const corsMaxAge = process.env.CORS_MAX_AGE;
|
||||
const defaultLocale = process.env.DEFAULT_LOCALE;
|
||||
const forceSSL = process.env.FORCE_SSL;
|
||||
|
|
@ -157,20 +157,12 @@ if (trackerScriptName) {
|
|||
}
|
||||
}
|
||||
|
||||
if (cloudUrl) {
|
||||
redirects.push({
|
||||
source: '/login',
|
||||
destination: cloudUrl,
|
||||
permanent: false,
|
||||
});
|
||||
}
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
export default {
|
||||
reactStrictMode: false,
|
||||
env: {
|
||||
basePath,
|
||||
cloudUrl,
|
||||
cloudMode,
|
||||
currentVersion: pkg.version,
|
||||
defaultLocale,
|
||||
},
|
||||
|
|
|
|||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
|
|
@ -364,6 +364,8 @@ importers:
|
|||
specifier: ^5.9.2
|
||||
version: 5.9.2
|
||||
|
||||
dist: {}
|
||||
|
||||
packages:
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ slug
|
|||
|
||||
const req = new Request(request.url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: request.headers,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ slug
|
|||
|
||||
const req = new Request(request.url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: request.headers,
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export function UpdateNotice({ user, config }) {
|
|||
!config?.updatesDisabled &&
|
||||
!config?.privateMode &&
|
||||
!pathname.includes('/share/') &&
|
||||
!process.env.cloudUrl &&
|
||||
!process.env.cloudMode &&
|
||||
!dismissed;
|
||||
|
||||
const updateCheck = useCallback(() => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function AdminLayout({ children }: { children: ReactNode }) {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
const { pathname } = useNavigation();
|
||||
|
||||
if (!user.isAdmin || process.env.cloudUrl) {
|
||||
if (!user.isAdmin || process.env.cloudMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Metadata } from 'next';
|
|||
import { AdminLayout } from './AdminLayout';
|
||||
|
||||
export default function ({ children }) {
|
||||
if (process.env.cloudUrl) {
|
||||
if (process.env.cloudMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text } from '@umami/react-zen';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { LinkEditForm } from './LinkEditForm';
|
||||
|
||||
export function LinkAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
touch('links');
|
||||
};
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -23,7 +16,7 @@ export function LinkAddButton({ teamId }: { teamId?: string }) {
|
|||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addLink)} style={{ width: 600 }}>
|
||||
{({ close }) => <LinkEditForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
{({ close }) => <LinkEditForm teamId={teamId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ export function LinkEditForm({
|
|||
{formatMessage(labels.cancel)}
|
||||
</Button>
|
||||
)}
|
||||
<FormSubmitButton isDisabled={false}>{formatMessage(labels.save)}</FormSubmitButton>
|
||||
<FormSubmitButton isDisabled={false} isLoading={isPending}>
|
||||
{formatMessage(labels.save)}
|
||||
</FormSubmitButton>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
import { useMessages, useModified } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text, useToast } from '@umami/react-zen';
|
||||
import { useMessages } from '@/components/hooks';
|
||||
import { Button, Icon, Modal, Dialog, DialogTrigger, Text } from '@umami/react-zen';
|
||||
import { Plus } from '@/components/icons';
|
||||
import { PixelEditForm } from './PixelEditForm';
|
||||
|
||||
export function PixelAddButton({ teamId }: { teamId?: string }) {
|
||||
const { formatMessage, labels, messages } = useMessages();
|
||||
const { toast } = useToast();
|
||||
const { touch } = useModified();
|
||||
|
||||
const handleSave = async () => {
|
||||
toast(formatMessage(messages.saved));
|
||||
touch('pixels');
|
||||
};
|
||||
const { formatMessage, labels } = useMessages();
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
|
|
@ -23,7 +16,7 @@ export function PixelAddButton({ teamId }: { teamId?: string }) {
|
|||
</Button>
|
||||
<Modal>
|
||||
<Dialog title={formatMessage(labels.addPixel)} style={{ width: 600 }}>
|
||||
{({ close }) => <PixelEditForm teamId={teamId} onSave={handleSave} onClose={close} />}
|
||||
{({ close }) => <PixelEditForm teamId={teamId} onClose={close} />}
|
||||
</Dialog>
|
||||
</Modal>
|
||||
</DialogTrigger>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Metadata } from 'next';
|
|||
import { SettingsLayout } from './SettingsLayout';
|
||||
|
||||
export default function ({ children }) {
|
||||
if (process.env.cloudUrl) {
|
||||
if (process.env.cloudMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -114,10 +114,7 @@ export function CohortEditForm({
|
|||
|
||||
<Column>
|
||||
<Label>{formatMessage(labels.filters)}</Label>
|
||||
<FormField
|
||||
name="parameters.filters"
|
||||
rules={{ required: formatMessage(labels.required) }}
|
||||
>
|
||||
<FormField name="parameters.filters">
|
||||
<FieldFilters websiteId={websiteId} exclude={['path', 'event']} />
|
||||
</FormField>
|
||||
</Column>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ export async function GET(request: Request) {
|
|||
}
|
||||
|
||||
return json({
|
||||
cloudMode: !!process.env.CLOUD_URL,
|
||||
cloudUrl: process.env.CLOUD_URL,
|
||||
cloudMode: !!process.env.CLOUD_MODE,
|
||||
faviconUrl: process.env.FAVICON_URL,
|
||||
linksUrl: process.env.LINKS_URL,
|
||||
pixelsUrl: process.env.PIXELS_URL,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Metadata } from 'next';
|
|||
import { LoginPage } from './LoginPage';
|
||||
|
||||
export default async function () {
|
||||
if (process.env.DISABLE_LOGIN) {
|
||||
if (process.env.DISABLE_LOGIN || process.env.CLOUD_MODE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { LogoutPage } from './LogoutPage';
|
||||
import { Metadata } from 'next';
|
||||
import { LogoutPage } from './LogoutPage';
|
||||
|
||||
export default function () {
|
||||
if (process.env.DISABLE_LOGIN) {
|
||||
if (process.env.DISABLE_LOGIN || process.env.CLOUD_MODE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useApi } from '@/components/hooks/useApi';
|
|||
|
||||
export type Config = {
|
||||
cloudMode: boolean;
|
||||
cloudUrl?: string;
|
||||
faviconUrl?: string;
|
||||
linksUrl?: string;
|
||||
pixelsUrl?: string;
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ import {
|
|||
Text,
|
||||
Row,
|
||||
} from '@umami/react-zen';
|
||||
import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks';
|
||||
import { useMessages, useLoginQuery, useNavigation } from '@/components/hooks';
|
||||
import { LogOut, UserCircle, LockKeyhole } from '@/components/icons';
|
||||
|
||||
export function ProfileButton() {
|
||||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useLoginQuery();
|
||||
const { renderUrl } = useNavigation();
|
||||
const { cloudUrl } = useConfig();
|
||||
|
||||
const items = [
|
||||
{
|
||||
|
|
@ -28,7 +27,7 @@ export function ProfileButton() {
|
|||
icon: <UserCircle />,
|
||||
},
|
||||
user.isAdmin &&
|
||||
!cloudUrl && {
|
||||
!process.env.cloudMode && {
|
||||
id: 'admin',
|
||||
label: formatMessage(labels.admin),
|
||||
path: '/admin',
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ export function SettingsButton() {
|
|||
const { formatMessage, labels } = useMessages();
|
||||
const { user } = useLoginQuery();
|
||||
const { router, renderUrl } = useNavigation();
|
||||
const { cloudMode, cloudUrl } = useConfig();
|
||||
const { cloudMode } = useConfig();
|
||||
|
||||
const handleAction = (id: Key) => {
|
||||
if (id === 'settings') {
|
||||
if (cloudMode) {
|
||||
window.location.href = `${cloudUrl}/settings`;
|
||||
window.location.href = `/settings`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -359,7 +359,7 @@ export const labels = defineMessages({
|
|||
invalidUrl: { id: 'label.invalid-url', defaultMessage: 'Invalid URL' },
|
||||
environment: { id: 'label.environment', defaultMessage: 'Environment' },
|
||||
criteria: { id: 'label.criteria', defaultMessage: 'Criteria' },
|
||||
share: { defaultMessage: 'label.share', id: 'Share' },
|
||||
share: { id: 'label.share', defaultMessage: 'Share' },
|
||||
});
|
||||
|
||||
export const messages = defineMessages({
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ export function getBearerToken(request: Request) {
|
|||
export async function checkAuth(request: Request) {
|
||||
const token = getBearerToken(request);
|
||||
const payload = parseSecureToken(token, secret());
|
||||
const shareToken = await parseShareToken(request.headers);
|
||||
const shareToken = await parseShareToken(request);
|
||||
|
||||
let user = null;
|
||||
const { userId, authKey, grant } = payload || {};
|
||||
const { userId, authKey } = payload || {};
|
||||
|
||||
if (userId) {
|
||||
user = await getUser(userId);
|
||||
|
|
@ -33,7 +33,7 @@ export async function checkAuth(request: Request) {
|
|||
}
|
||||
}
|
||||
|
||||
log({ token, shareToken, payload, user, grant });
|
||||
log({ token, payload, authKey, shareToken, user });
|
||||
|
||||
if (!user?.id && !shareToken) {
|
||||
log('User not authorized');
|
||||
|
|
@ -45,11 +45,10 @@ export async function checkAuth(request: Request) {
|
|||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
grant,
|
||||
token,
|
||||
shareToken,
|
||||
authKey,
|
||||
shareToken,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -71,9 +70,9 @@ export async function hasPermission(role: string, permission: string | string[])
|
|||
return ensureArray(permission).some(e => ROLE_PERMISSIONS[role]?.includes(e));
|
||||
}
|
||||
|
||||
export function parseShareToken(headers: Headers) {
|
||||
export function parseShareToken(request: Request) {
|
||||
try {
|
||||
return parseToken(headers.get(SHARE_TOKEN_HEADER), secret());
|
||||
return parseToken(request.headers.get(SHARE_TOKEN_HEADER), secret());
|
||||
} catch (e) {
|
||||
log(e);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { DATA_TYPE, PERMISSIONS, ROLES, OPERATORS } from './constants';
|
||||
import { DATA_TYPE, ROLES, OPERATORS } from './constants';
|
||||
import { TIME_UNIT } from './date';
|
||||
|
||||
export type ObjectValues<T> = T[keyof T];
|
||||
|
|
@ -7,7 +7,6 @@ export type ObjectValues<T> = T[keyof T];
|
|||
export type ReactQueryOptions<T = any> = Omit<UseQueryOptions<T, Error, T>, 'queryKey' | 'queryFn'>;
|
||||
|
||||
export type TimeUnit = ObjectValues<typeof TIME_UNIT>;
|
||||
export type Permission = ObjectValues<typeof PERMISSIONS>;
|
||||
export type Role = ObjectValues<typeof ROLES>;
|
||||
export type DynamicDataType = ObjectValues<typeof DATA_TYPE>;
|
||||
export type Operator = (typeof OPERATORS)[keyof typeof OPERATORS];
|
||||
|
|
@ -19,7 +18,6 @@ export interface Auth {
|
|||
role: string;
|
||||
isAdmin: boolean;
|
||||
};
|
||||
grant?: Permission[];
|
||||
shareToken?: {
|
||||
websiteId: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import { PERMISSIONS } from '@/lib/constants';
|
|||
import { getTeamUser } from '@/queries';
|
||||
import { hasPermission } from '@/lib/auth';
|
||||
|
||||
const cloudMode = !!process.env.CLOUD_URL;
|
||||
|
||||
export async function canViewTeam({ user }: Auth, teamId: string) {
|
||||
if (user.isAdmin) {
|
||||
return true;
|
||||
|
|
@ -13,11 +11,7 @@ export async function canViewTeam({ user }: Auth, teamId: string) {
|
|||
return getTeamUser(teamId, user.id);
|
||||
}
|
||||
|
||||
export async function canCreateTeam({ user, grant }: Auth) {
|
||||
if (cloudMode) {
|
||||
return !!grant?.find(a => a === PERMISSIONS.teamCreate);
|
||||
}
|
||||
|
||||
export async function canCreateTeam({ user }: Auth) {
|
||||
if (user.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -25,15 +19,11 @@ export async function canCreateTeam({ user, grant }: Auth) {
|
|||
return !!user;
|
||||
}
|
||||
|
||||
export async function canUpdateTeam({ user, grant }: Auth, teamId: string) {
|
||||
export async function canUpdateTeam({ user }: Auth, teamId: string) {
|
||||
if (user.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cloudMode) {
|
||||
return !!grant?.find(a => a === PERMISSIONS.teamUpdate);
|
||||
}
|
||||
|
||||
const teamUser = await getTeamUser(teamId, user.id);
|
||||
|
||||
return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamUpdate);
|
||||
|
|
@ -49,11 +39,7 @@ export async function canDeleteTeam({ user }: Auth, teamId: string) {
|
|||
return teamUser && hasPermission(teamUser.role, PERMISSIONS.teamDelete);
|
||||
}
|
||||
|
||||
export async function canAddUserToTeam({ user, grant }: Auth) {
|
||||
if (cloudMode) {
|
||||
return !!grant?.find(a => a === PERMISSIONS.teamUpdate);
|
||||
}
|
||||
|
||||
export async function canAddUserToTeam({ user }: Auth) {
|
||||
return user.isAdmin;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import { PERMISSIONS } from '@/lib/constants';
|
|||
import { hasPermission } from '@/lib/auth';
|
||||
import { getTeamUser, getWebsite } from '@/queries';
|
||||
|
||||
const cloudMode = !!process.env.CLOUD_URL;
|
||||
|
||||
export async function canViewWebsite({ user, shareToken }: Auth, websiteId: string) {
|
||||
if (user?.isAdmin) {
|
||||
return true;
|
||||
|
|
@ -33,11 +31,7 @@ export async function canViewAllWebsites({ user }: Auth) {
|
|||
return user.isAdmin;
|
||||
}
|
||||
|
||||
export async function canCreateWebsite({ user, grant }: Auth) {
|
||||
if (cloudMode) {
|
||||
return !!grant?.find(a => a === PERMISSIONS.websiteCreate);
|
||||
}
|
||||
|
||||
export async function canCreateWebsite({ user }: Auth) {
|
||||
if (user.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue