Merge branch 'dev' of https://github.com/umami-software/umami into dev
# Conflicts: # public/iso-3166-2.jsonpull/1885/head
commit
e286994397
|
@ -10,6 +10,7 @@ export const labels = defineMessages({
|
||||||
leave: { id: 'label.leave', defaultMessage: 'Leave' },
|
leave: { id: 'label.leave', defaultMessage: 'Leave' },
|
||||||
users: { id: 'label.users', defaultMessage: 'Users' },
|
users: { id: 'label.users', defaultMessage: 'Users' },
|
||||||
createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
|
createUser: { id: 'label.create-user', defaultMessage: 'Create user' },
|
||||||
|
deleteUser: { id: 'label.delete-users', defaultMessage: 'Delete user' },
|
||||||
username: { id: 'label.username', defaultMessage: 'Username' },
|
username: { id: 'label.username', defaultMessage: 'Username' },
|
||||||
password: { id: 'label.password', defaultMessage: 'Password' },
|
password: { id: 'label.password', defaultMessage: 'Password' },
|
||||||
role: { id: 'label.role', defaultMessage: 'Role' },
|
role: { id: 'label.role', defaultMessage: 'Role' },
|
||||||
|
|
|
@ -16,7 +16,9 @@ import useMessages from 'hooks/useMessages';
|
||||||
export default function UserEditForm({ userId, data, onSave }) {
|
export default function UserEditForm({ userId, data, onSave }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages } = useMessages();
|
||||||
const { post, useMutation } = useApi();
|
const { post, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(({ username }) => post(`/users/${userId}`, { username }));
|
const { mutate, error } = useMutation(({ username, password, role }) =>
|
||||||
|
post(`/users/${userId}`, { username, password, role }),
|
||||||
|
);
|
||||||
|
|
||||||
const handleSubmit = async data => {
|
const handleSubmit = async data => {
|
||||||
mutate(data, {
|
mutate(data, {
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default function UsersTable({ data = [], onDelete }) {
|
||||||
</Icon>
|
</Icon>
|
||||||
<Text>{formatMessage(labels.delete)}</Text>
|
<Text>{formatMessage(labels.delete)}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Modal>
|
<Modal title={formatMessage(labels.deleteUser)}>
|
||||||
{close => (
|
{close => (
|
||||||
<UserDeleteForm
|
<UserDeleteForm
|
||||||
userId={row.id}
|
userId={row.id}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import useMessages from 'hooks/useMessages';
|
||||||
const CONFIRM_VALUE = 'DELETE';
|
const CONFIRM_VALUE = 'DELETE';
|
||||||
|
|
||||||
export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
|
export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
|
||||||
const { formatMessage, labels, messages } = useMessages();
|
const { formatMessage, labels, messages, FormattedMessage } = useMessages();
|
||||||
const { del, useMutation } = useApi();
|
const { del, useMutation } = useApi();
|
||||||
const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data));
|
const { mutate, error } = useMutation(data => del(`/websites/${websiteId}`, data));
|
||||||
|
|
||||||
|
@ -28,7 +28,12 @@ export default function WebsiteDeleteForm({ websiteId, onSave, onClose }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form onSubmit={handleSubmit} error={error}>
|
<Form onSubmit={handleSubmit} error={error}>
|
||||||
<p>{formatMessage(messages.deleteWebsite, { confirmation: CONFIRM_VALUE })}</p>
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
{...messages.deleteWebsite}
|
||||||
|
values={{ confirmation: <b>{CONFIRM_VALUE}</b> }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
<FormRow label={formatMessage(labels.confirm)}>
|
<FormRow label={formatMessage(labels.confirm)}>
|
||||||
<FormInput name="confirmation" rules={{ validate: value => value === CONFIRM_VALUE }}>
|
<FormInput name="confirmation" rules={{ validate: value => value === CONFIRM_VALUE }}>
|
||||||
<TextField autoComplete="off" />
|
<TextField autoComplete="off" />
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
"message.confirm-delete": "Are you sure you want to delete {target}?",
|
"message.confirm-delete": "Are you sure you want to delete {target}?",
|
||||||
"message.confirm-leave": "Are you sure you want to leave {target}?",
|
"message.confirm-leave": "Are you sure you want to leave {target}?",
|
||||||
"message.confirm-reset": "Are you sure you want to reset {target}'s statistics?",
|
"message.confirm-reset": "Are you sure you want to reset {target}'s statistics?",
|
||||||
"message.delete-website": "Delete website",
|
"message.delete-website": "To delete this website, type {confirmation} in the box below to confirm.",
|
||||||
"message.delete-website-warning": "All associated data will be deleted as well.",
|
"message.delete-website-warning": "All associated data will be deleted as well.",
|
||||||
"message.error": "Something went wrong.",
|
"message.error": "Something went wrong.",
|
||||||
"message.event-log": "{event} on {url}",
|
"message.event-log": "{event} on {url}",
|
||||||
|
|
|
@ -95,17 +95,11 @@ export async function canViewWebsite({ user, shareToken }: Auth, websiteId: stri
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function canCreateWebsite({ user }: Auth, teamId?: string) {
|
export async function canCreateWebsite({ user }: Auth) {
|
||||||
if (user.isAdmin) {
|
if (user.isAdmin) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (teamId) {
|
|
||||||
const teamUser = await getTeamUser(teamId, user.id);
|
|
||||||
|
|
||||||
return hasPermission(teamUser?.role, PERMISSIONS.websiteCreate);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasPermission(user.role, PERMISSIONS.websiteCreate);
|
return hasPermission(user.role, PERMISSIONS.websiteCreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
role: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,10 @@ export default async (
|
||||||
|
|
||||||
const token = createSecureToken({ userId: user.id }, secret());
|
const token = createSecureToken({ userId: user.id }, secret());
|
||||||
|
|
||||||
return ok(res, { token, user });
|
return ok(res, {
|
||||||
|
token,
|
||||||
|
user: { id: user.id, username: user.username, createdAt: user.createdAt },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return unauthorized(res, 'message.incorrect-username-password');
|
return unauthorized(res, 'message.incorrect-username-password');
|
||||||
|
|
|
@ -23,9 +23,9 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const websites = await deleteTeamWebsite(teamId, websiteId);
|
await deleteTeamWebsite(teamId, websiteId);
|
||||||
|
|
||||||
return ok(res, websites);
|
return ok(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return methodNotAllowed(res);
|
return methodNotAllowed(res);
|
||||||
|
|
|
@ -10,7 +10,6 @@ export interface TeamWebsiteRequestQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TeamWebsiteRequestBody {
|
export interface TeamWebsiteRequestBody {
|
||||||
teamWebsiteId?: string;
|
|
||||||
websiteIds?: string[];
|
websiteIds?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,9 +20,6 @@ export default async (
|
||||||
await useAuth(req, res);
|
await useAuth(req, res);
|
||||||
|
|
||||||
const { id: teamId } = req.query;
|
const { id: teamId } = req.query;
|
||||||
const {
|
|
||||||
user: { id: userId },
|
|
||||||
} = req.auth;
|
|
||||||
|
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
if (!(await canViewTeam(req.auth, teamId))) {
|
if (!(await canViewTeam(req.auth, teamId))) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { NextApiRequestQueryBody, User } from 'lib/types';
|
import { NextApiRequestQueryBody, Roles, User } from 'lib/types';
|
||||||
import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth';
|
import { canDeleteUser, canUpdateUser, canViewUser } from 'lib/auth';
|
||||||
import { useAuth } from 'lib/middleware';
|
import { useAuth } from 'lib/middleware';
|
||||||
import { NextApiResponse } from 'next';
|
import { NextApiResponse } from 'next';
|
||||||
|
@ -12,6 +12,7 @@ export interface UserRequestQuery {
|
||||||
export interface UserRequestBody {
|
export interface UserRequestBody {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
role: Roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (
|
export default async (
|
||||||
|
@ -40,17 +41,20 @@ export default async (
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { username, password } = req.body;
|
const { username, password, role } = req.body;
|
||||||
|
|
||||||
const user = await getUser({ id });
|
const user = await getUser({ id });
|
||||||
|
|
||||||
const data: any = {};
|
const data: any = {};
|
||||||
|
|
||||||
// Only admin can change these fields
|
if (password) {
|
||||||
if (password && isAdmin) {
|
|
||||||
data.password = hashPassword(password);
|
data.password = hashPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role && isAdmin) {
|
||||||
|
data.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
// Only admin can change these fields
|
// Only admin can change these fields
|
||||||
if (username && isAdmin) {
|
if (username && isAdmin) {
|
||||||
data.username = username;
|
data.username = username;
|
||||||
|
|
|
@ -41,15 +41,17 @@ export default async (
|
||||||
|
|
||||||
const { name, domain, shareId } = req.body;
|
const { name, domain, shareId } = req.body;
|
||||||
|
|
||||||
|
let website;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await updateWebsite(websiteId, { name, domain, shareId });
|
website = await updateWebsite(websiteId, { name, domain, shareId });
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.message.includes('Unique constraint') && e.message.includes('share_id')) {
|
if (e.message.includes('Unique constraint') && e.message.includes('share_id')) {
|
||||||
return serverError(res, 'That share ID is already taken.');
|
return serverError(res, 'That share ID is already taken.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok(res);
|
return ok(res, website);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
|
|
|
@ -10,7 +10,6 @@ export interface WebsitesRequestBody {
|
||||||
name: string;
|
name: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
shareId: string;
|
shareId: string;
|
||||||
teamId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (
|
export default async (
|
||||||
|
@ -31,9 +30,9 @@ export default async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
const { name, domain, shareId, teamId } = req.body;
|
const { name, domain, shareId } = req.body;
|
||||||
|
|
||||||
if (!(await canCreateWebsite(req.auth, teamId))) {
|
if (!(await canCreateWebsite(req.auth))) {
|
||||||
return unauthorized(res);
|
return unauthorized(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,11 +43,7 @@ export default async (
|
||||||
shareId,
|
shareId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (teamId) {
|
data.userId = userId;
|
||||||
data.teamId = teamId;
|
|
||||||
} else {
|
|
||||||
data.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const website = await createWebsite(data);
|
const website = await createWebsite(data);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ export default function LogoutPage({ disabled }) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function logout() {
|
async function logout() {
|
||||||
await post('/logout');
|
await post('/auth/logout');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
|
|
|
@ -758,7 +758,15 @@
|
||||||
"message.delete-website": [
|
"message.delete-website": [
|
||||||
{
|
{
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"value": "Delete website"
|
"value": "To delete this website, type "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 1,
|
||||||
|
"value": "confirmation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": 0,
|
||||||
|
"value": " in the box below to confirm."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"message.delete-website-warning": [
|
"message.delete-website-warning": [
|
||||||
|
|
|
@ -17,6 +17,7 @@ export async function getUser(
|
||||||
username: true,
|
username: true,
|
||||||
password: includePassword,
|
password: includePassword,
|
||||||
role: true,
|
role: true,
|
||||||
|
createdAt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ export async function getEventMetrics(
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
column: string;
|
|
||||||
filters: {
|
filters: {
|
||||||
url: string;
|
url: string;
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
@ -40,7 +39,6 @@ async function relationalQuery(
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
unit: string;
|
unit: string;
|
||||||
column: string;
|
|
||||||
filters: {
|
filters: {
|
||||||
url: string;
|
url: string;
|
||||||
eventName: string;
|
eventName: string;
|
||||||
|
|
Loading…
Reference in New Issue