mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-11-04 09:25:15 +00:00 
			
		
		
		
	Log in as user support
This commit is contained in:
		@@ -37,6 +37,7 @@ export * from "./getToken";
 | 
			
		||||
export * from "./getUser";
 | 
			
		||||
export * from "./getUsers";
 | 
			
		||||
export * from "./helpers";
 | 
			
		||||
export * from "./loginAsUser";
 | 
			
		||||
export * from "./models";
 | 
			
		||||
export * from "./refreshToken";
 | 
			
		||||
export * from "./renewCertificate";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								frontend/src/api/backend/loginAsUser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/src/api/backend/loginAsUser.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
import * as api from "./base";
 | 
			
		||||
import type { LoginAsTokenResponse } from "./responseTypes";
 | 
			
		||||
 | 
			
		||||
export async function loginAsUser(id: number): Promise<LoginAsTokenResponse> {
 | 
			
		||||
	return await api.post({
 | 
			
		||||
		url: `/users/${id}/login`,
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import type { AppVersion } from "./models";
 | 
			
		||||
import type { AppVersion, User } from "./models";
 | 
			
		||||
 | 
			
		||||
export interface HealthResponse {
 | 
			
		||||
	status: string;
 | 
			
		||||
@@ -15,3 +15,7 @@ export interface ValidatedCertificateResponse {
 | 
			
		||||
	certificate: Record<string, any>;
 | 
			
		||||
	certificateKey: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LoginAsTokenResponse extends TokenResponse {
 | 
			
		||||
	user: User;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
 | 
			
		||||
import { LocalePicker, ThemeSwitcher, NavLink } from "src/components";
 | 
			
		||||
import { LocalePicker, NavLink, ThemeSwitcher } from "src/components";
 | 
			
		||||
import { useAuthState } from "src/context";
 | 
			
		||||
import { useUser } from "src/hooks";
 | 
			
		||||
import { T } from "src/locale";
 | 
			
		||||
@@ -26,18 +26,18 @@ export function SiteHeader() {
 | 
			
		||||
					<span className="navbar-toggler-icon" />
 | 
			
		||||
				</button>
 | 
			
		||||
				<div className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
 | 
			
		||||
                    <NavLink to="/">
 | 
			
		||||
                        <div className={styles.logo}>
 | 
			
		||||
                            <img
 | 
			
		||||
                                src="/images/logo-no-text.svg"
 | 
			
		||||
                                width={40}
 | 
			
		||||
                                height={40}
 | 
			
		||||
                                className="navbar-brand-image"
 | 
			
		||||
                                alt="Logo"
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
                        Nginx Proxy Manager
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
					<NavLink to="/">
 | 
			
		||||
						<div className={styles.logo}>
 | 
			
		||||
							<img
 | 
			
		||||
								src="/images/logo-no-text.svg"
 | 
			
		||||
								width={40}
 | 
			
		||||
								height={40}
 | 
			
		||||
								className="navbar-brand-image"
 | 
			
		||||
								alt="Logo"
 | 
			
		||||
							/>
 | 
			
		||||
						</div>
 | 
			
		||||
						Nginx Proxy Manager
 | 
			
		||||
					</NavLink>
 | 
			
		||||
				</div>
 | 
			
		||||
				<div className="navbar-nav flex-row order-md-last">
 | 
			
		||||
					<div className="d-none d-md-flex">
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,14 @@
 | 
			
		||||
import { useQueryClient } from "@tanstack/react-query";
 | 
			
		||||
import { createContext, type ReactNode, useContext, useState } from "react";
 | 
			
		||||
import { useIntervalWhen } from "rooks";
 | 
			
		||||
import { getToken, refreshToken, type TokenResponse } from "src/api/backend";
 | 
			
		||||
import { getToken, loginAsUser, refreshToken, type TokenResponse } from "src/api/backend";
 | 
			
		||||
import AuthStore from "src/modules/AuthStore";
 | 
			
		||||
 | 
			
		||||
// Context
 | 
			
		||||
export interface AuthContextType {
 | 
			
		||||
	authenticated: boolean;
 | 
			
		||||
	login: (username: string, password: string) => Promise<void>;
 | 
			
		||||
	loginAs: (id: number) => Promise<void>;
 | 
			
		||||
	logout: () => void;
 | 
			
		||||
	token?: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -34,7 +35,20 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
 | 
			
		||||
		handleTokenUpdate(response);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const loginAs = async (id: number) => {
 | 
			
		||||
		const response = await loginAsUser(id);
 | 
			
		||||
		AuthStore.add(response);
 | 
			
		||||
		queryClient.clear();
 | 
			
		||||
		window.location.reload();
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const logout = () => {
 | 
			
		||||
		if (AuthStore.count() >= 2) {
 | 
			
		||||
			AuthStore.drop();
 | 
			
		||||
			queryClient.clear();
 | 
			
		||||
			window.location.reload();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		AuthStore.clear();
 | 
			
		||||
		setAuthenticated(false);
 | 
			
		||||
		queryClient.clear();
 | 
			
		||||
@@ -55,7 +69,7 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
 | 
			
		||||
		true,
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const value = { authenticated, login, logout };
 | 
			
		||||
	const value = { authenticated, login, logout, loginAs };
 | 
			
		||||
 | 
			
		||||
	return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -201,6 +201,7 @@
 | 
			
		||||
  "user.current-password": "Current Password",
 | 
			
		||||
  "user.edit-profile": "Edit Profile",
 | 
			
		||||
  "user.full-name": "Full Name",
 | 
			
		||||
  "user.login-as": "Sign in as {name}",
 | 
			
		||||
  "user.logout": "Logout",
 | 
			
		||||
  "user.new-password": "New Password",
 | 
			
		||||
  "user.nickname": "Nickname",
 | 
			
		||||
 
 | 
			
		||||
@@ -605,6 +605,9 @@
 | 
			
		||||
	"user.full-name": {
 | 
			
		||||
		"defaultMessage": "Full Name"
 | 
			
		||||
	},
 | 
			
		||||
	"user.login-as": {
 | 
			
		||||
		"defaultMessage": "Sign in as {name}"
 | 
			
		||||
	},
 | 
			
		||||
	"user.logout": {
 | 
			
		||||
		"defaultMessage": "Logout"
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,7 @@ export class AuthStore {
 | 
			
		||||
	// 	const t = this.tokens;
 | 
			
		||||
	// 	return t.length > 0;
 | 
			
		||||
	// }
 | 
			
		||||
	// Start from the END of the stack and work backwards
 | 
			
		||||
	hasActiveToken() {
 | 
			
		||||
		const t = this.tokens;
 | 
			
		||||
		if (!t.length) {
 | 
			
		||||
@@ -68,22 +69,27 @@ export class AuthStore {
 | 
			
		||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }]));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add a token to the stack
 | 
			
		||||
	// Add a token to the END of the stack
 | 
			
		||||
	add({ token, expires }: TokenResponse) {
 | 
			
		||||
		const t = this.tokens;
 | 
			
		||||
		t.push({ token, expires });
 | 
			
		||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Drop a token from the stack
 | 
			
		||||
	// Drop a token from the END of the stack
 | 
			
		||||
	drop() {
 | 
			
		||||
		const t = this.tokens;
 | 
			
		||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify(t.splice(-1, 1)));
 | 
			
		||||
		t.splice(-1, 1);
 | 
			
		||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clear() {
 | 
			
		||||
		localStorage.removeItem(TOKEN_KEY);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	count() {
 | 
			
		||||
		return this.tokens.length;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new AuthStore();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react";
 | 
			
		||||
import { useNavigate } from "react-router-dom";
 | 
			
		||||
import { HasPermission } from "src/components";
 | 
			
		||||
import { useHostReport } from "src/hooks";
 | 
			
		||||
import { T } from "src/locale";
 | 
			
		||||
 | 
			
		||||
@@ -15,100 +16,111 @@ const Dashboard = () => {
 | 
			
		||||
			<div className="row row-deck row-cards">
 | 
			
		||||
				<div className="col-12 my-4">
 | 
			
		||||
					<div className="row row-cards">
 | 
			
		||||
						<div className="col-sm-6 col-lg-3">
 | 
			
		||||
							<a
 | 
			
		||||
								href="/nginx/proxy"
 | 
			
		||||
								className="card card-sm card-link card-link-pop"
 | 
			
		||||
								onClick={(e) => {
 | 
			
		||||
									e.preventDefault();
 | 
			
		||||
									navigate("/nginx/proxy");
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<div className="card-body">
 | 
			
		||||
									<div className="row align-items-center">
 | 
			
		||||
										<div className="col-auto">
 | 
			
		||||
											<span className="bg-green text-white avatar">
 | 
			
		||||
												<IconBolt />
 | 
			
		||||
											</span>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div className="col">
 | 
			
		||||
											<div className="font-weight-medium">
 | 
			
		||||
												<T id="proxy-hosts.count" data={{ count: hostReport?.proxy }} />
 | 
			
		||||
						<HasPermission permission="proxyHosts" type="view" hideError>
 | 
			
		||||
							<div className="col-sm-6 col-lg-3">
 | 
			
		||||
								<a
 | 
			
		||||
									href="/nginx/proxy"
 | 
			
		||||
									className="card card-sm card-link card-link-pop"
 | 
			
		||||
									onClick={(e) => {
 | 
			
		||||
										e.preventDefault();
 | 
			
		||||
										navigate("/nginx/proxy");
 | 
			
		||||
									}}
 | 
			
		||||
								>
 | 
			
		||||
									<div className="card-body">
 | 
			
		||||
										<div className="row align-items-center">
 | 
			
		||||
											<div className="col-auto">
 | 
			
		||||
												<span className="bg-green text-white avatar">
 | 
			
		||||
													<IconBolt />
 | 
			
		||||
												</span>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div className="col">
 | 
			
		||||
												<div className="font-weight-medium">
 | 
			
		||||
													<T id="proxy-hosts.count" data={{ count: hostReport?.proxy }} />
 | 
			
		||||
												</div>
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</a>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div className="col-sm-6 col-lg-3">
 | 
			
		||||
							<a
 | 
			
		||||
								href="/nginx/redirection"
 | 
			
		||||
								className="card card-sm card-link card-link-pop"
 | 
			
		||||
								onClick={(e) => {
 | 
			
		||||
									e.preventDefault();
 | 
			
		||||
									navigate("/nginx/redirection");
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<div className="card-body">
 | 
			
		||||
									<div className="row align-items-center">
 | 
			
		||||
										<div className="col-auto">
 | 
			
		||||
											<span className="bg-yellow text-white avatar">
 | 
			
		||||
												<IconArrowsCross />
 | 
			
		||||
											</span>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div className="col">
 | 
			
		||||
											<T id="redirection-hosts.count" data={{ count: hostReport?.redirection }} />
 | 
			
		||||
								</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</HasPermission>
 | 
			
		||||
						<HasPermission permission="redirectionHosts" type="view" hideError>
 | 
			
		||||
							<div className="col-sm-6 col-lg-3">
 | 
			
		||||
								<a
 | 
			
		||||
									href="/nginx/redirection"
 | 
			
		||||
									className="card card-sm card-link card-link-pop"
 | 
			
		||||
									onClick={(e) => {
 | 
			
		||||
										e.preventDefault();
 | 
			
		||||
										navigate("/nginx/redirection");
 | 
			
		||||
									}}
 | 
			
		||||
								>
 | 
			
		||||
									<div className="card-body">
 | 
			
		||||
										<div className="row align-items-center">
 | 
			
		||||
											<div className="col-auto">
 | 
			
		||||
												<span className="bg-yellow text-white avatar">
 | 
			
		||||
													<IconArrowsCross />
 | 
			
		||||
												</span>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div className="col">
 | 
			
		||||
												<T
 | 
			
		||||
													id="redirection-hosts.count"
 | 
			
		||||
													data={{ count: hostReport?.redirection }}
 | 
			
		||||
												/>
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</a>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div className="col-sm-6 col-lg-3">
 | 
			
		||||
							<a
 | 
			
		||||
								href="/nginx/stream"
 | 
			
		||||
								className="card card-sm card-link card-link-pop"
 | 
			
		||||
								onClick={(e) => {
 | 
			
		||||
									e.preventDefault();
 | 
			
		||||
									navigate("/nginx/stream");
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<div className="card-body">
 | 
			
		||||
									<div className="row align-items-center">
 | 
			
		||||
										<div className="col-auto">
 | 
			
		||||
											<span className="bg-blue text-white avatar">
 | 
			
		||||
												<IconDisc />
 | 
			
		||||
											</span>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div className="col">
 | 
			
		||||
											<T id="streams.count" data={{ count: hostReport?.stream }} />
 | 
			
		||||
								</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</HasPermission>
 | 
			
		||||
						<HasPermission permission="streams" type="view" hideError>
 | 
			
		||||
							<div className="col-sm-6 col-lg-3">
 | 
			
		||||
								<a
 | 
			
		||||
									href="/nginx/stream"
 | 
			
		||||
									className="card card-sm card-link card-link-pop"
 | 
			
		||||
									onClick={(e) => {
 | 
			
		||||
										e.preventDefault();
 | 
			
		||||
										navigate("/nginx/stream");
 | 
			
		||||
									}}
 | 
			
		||||
								>
 | 
			
		||||
									<div className="card-body">
 | 
			
		||||
										<div className="row align-items-center">
 | 
			
		||||
											<div className="col-auto">
 | 
			
		||||
												<span className="bg-blue text-white avatar">
 | 
			
		||||
													<IconDisc />
 | 
			
		||||
												</span>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div className="col">
 | 
			
		||||
												<T id="streams.count" data={{ count: hostReport?.stream }} />
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</a>
 | 
			
		||||
						</div>
 | 
			
		||||
						<div className="col-sm-6 col-lg-3">
 | 
			
		||||
							<a
 | 
			
		||||
								href="/nginx/404"
 | 
			
		||||
								className="card card-sm card-link card-link-pop"
 | 
			
		||||
								onClick={(e) => {
 | 
			
		||||
									e.preventDefault();
 | 
			
		||||
									navigate("/nginx/404");
 | 
			
		||||
								}}
 | 
			
		||||
							>
 | 
			
		||||
								<div className="card-body">
 | 
			
		||||
									<div className="row align-items-center">
 | 
			
		||||
										<div className="col-auto">
 | 
			
		||||
											<span className="bg-red text-white avatar">
 | 
			
		||||
												<IconBoltOff />
 | 
			
		||||
											</span>
 | 
			
		||||
										</div>
 | 
			
		||||
										<div className="col">
 | 
			
		||||
											<T id="dead-hosts.count" data={{ count: hostReport?.dead }} />
 | 
			
		||||
								</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</HasPermission>
 | 
			
		||||
						<HasPermission permission="deadHosts" type="view" hideError>
 | 
			
		||||
							<div className="col-sm-6 col-lg-3">
 | 
			
		||||
								<a
 | 
			
		||||
									href="/nginx/404"
 | 
			
		||||
									className="card card-sm card-link card-link-pop"
 | 
			
		||||
									onClick={(e) => {
 | 
			
		||||
										e.preventDefault();
 | 
			
		||||
										navigate("/nginx/404");
 | 
			
		||||
									}}
 | 
			
		||||
								>
 | 
			
		||||
									<div className="card-body">
 | 
			
		||||
										<div className="row align-items-center">
 | 
			
		||||
											<div className="col-auto">
 | 
			
		||||
												<span className="bg-red text-white avatar">
 | 
			
		||||
													<IconBoltOff />
 | 
			
		||||
												</span>
 | 
			
		||||
											</div>
 | 
			
		||||
											<div className="col">
 | 
			
		||||
												<T id="dead-hosts.count" data={{ count: hostReport?.dead }} />
 | 
			
		||||
											</div>
 | 
			
		||||
										</div>
 | 
			
		||||
									</div>
 | 
			
		||||
								</div>
 | 
			
		||||
							</a>
 | 
			
		||||
						</div>
 | 
			
		||||
								</a>
 | 
			
		||||
							</div>
 | 
			
		||||
						</HasPermission>
 | 
			
		||||
					</div>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,12 @@
 | 
			
		||||
import { IconDotsVertical, IconEdit, IconLock, IconPower, IconShield, IconTrash } from "@tabler/icons-react";
 | 
			
		||||
import {
 | 
			
		||||
	IconDotsVertical,
 | 
			
		||||
	IconEdit,
 | 
			
		||||
	IconLock,
 | 
			
		||||
	IconLogin2,
 | 
			
		||||
	IconPower,
 | 
			
		||||
	IconShield,
 | 
			
		||||
	IconTrash,
 | 
			
		||||
} from "@tabler/icons-react";
 | 
			
		||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import type { User } from "src/api/backend";
 | 
			
		||||
@@ -24,6 +32,7 @@ interface Props {
 | 
			
		||||
	onDeleteUser?: (id: number) => void;
 | 
			
		||||
	onDisableToggle?: (id: number, enabled: boolean) => void;
 | 
			
		||||
	onNewUser?: () => void;
 | 
			
		||||
	onLoginAs?: (id: number) => void;
 | 
			
		||||
}
 | 
			
		||||
export default function Table({
 | 
			
		||||
	data,
 | 
			
		||||
@@ -36,6 +45,7 @@ export default function Table({
 | 
			
		||||
	onDeleteUser,
 | 
			
		||||
	onDisableToggle,
 | 
			
		||||
	onNewUser,
 | 
			
		||||
	onLoginAs,
 | 
			
		||||
}: Props) {
 | 
			
		||||
	const columnHelper = createColumnHelper<User>();
 | 
			
		||||
	const columns = useMemo(
 | 
			
		||||
@@ -153,6 +163,24 @@ export default function Table({
 | 
			
		||||
											<IconPower size={16} />
 | 
			
		||||
											<T id={info.row.original.isDisabled ? "action.enable" : "action.disable"} />
 | 
			
		||||
										</a>
 | 
			
		||||
										{info.row.original.isDisabled ? (
 | 
			
		||||
											<div className="dropdown-item text-muted">
 | 
			
		||||
												<IconLogin2 size={16} />
 | 
			
		||||
												<T id="user.login-as" data={{ name: info.row.original.name }} />
 | 
			
		||||
											</div>
 | 
			
		||||
										) : (
 | 
			
		||||
											<a
 | 
			
		||||
												className="dropdown-item"
 | 
			
		||||
												href="#"
 | 
			
		||||
												onClick={(e) => {
 | 
			
		||||
													e.preventDefault();
 | 
			
		||||
													onLoginAs?.(info.row.original.id);
 | 
			
		||||
												}}
 | 
			
		||||
											>
 | 
			
		||||
												<IconLogin2 size={16} />
 | 
			
		||||
												<T id="user.login-as" data={{ name: info.row.original.name }} />
 | 
			
		||||
											</a>
 | 
			
		||||
										)}
 | 
			
		||||
										<div className="dropdown-divider" />
 | 
			
		||||
										<a
 | 
			
		||||
											className="dropdown-item"
 | 
			
		||||
@@ -176,7 +204,16 @@ export default function Table({
 | 
			
		||||
				},
 | 
			
		||||
			}),
 | 
			
		||||
		],
 | 
			
		||||
		[columnHelper, currentUserId, onEditUser, onDisableToggle, onDeleteUser, onEditPermissions, onSetPassword],
 | 
			
		||||
		[
 | 
			
		||||
			columnHelper,
 | 
			
		||||
			currentUserId,
 | 
			
		||||
			onEditUser,
 | 
			
		||||
			onDisableToggle,
 | 
			
		||||
			onDeleteUser,
 | 
			
		||||
			onEditPermissions,
 | 
			
		||||
			onSetPassword,
 | 
			
		||||
			onLoginAs,
 | 
			
		||||
		],
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const tableInstance = useReactTable<User>({
 | 
			
		||||
 
 | 
			
		||||
@@ -4,14 +4,16 @@ import { useState } from "react";
 | 
			
		||||
import Alert from "react-bootstrap/Alert";
 | 
			
		||||
import { deleteUser, toggleUser } from "src/api/backend";
 | 
			
		||||
import { Button, LoadingPage } from "src/components";
 | 
			
		||||
import { useAuthState } from "src/context";
 | 
			
		||||
import { useUser, useUsers } from "src/hooks";
 | 
			
		||||
import { T } from "src/locale";
 | 
			
		||||
import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals";
 | 
			
		||||
import { showObjectSuccess } from "src/notifications";
 | 
			
		||||
import { showError, showObjectSuccess } from "src/notifications";
 | 
			
		||||
import Table from "./Table";
 | 
			
		||||
 | 
			
		||||
export default function TableWrapper() {
 | 
			
		||||
	const queryClient = useQueryClient();
 | 
			
		||||
	const { loginAs } = useAuthState();
 | 
			
		||||
	const [search, setSearch] = useState("");
 | 
			
		||||
	const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]);
 | 
			
		||||
	const { data: currentUser } = useUser("me");
 | 
			
		||||
@@ -24,6 +26,16 @@ export default function TableWrapper() {
 | 
			
		||||
		return <Alert variant="danger">{error?.message || "Unknown error"}</Alert>;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const handleLoginAs = async (id: number) => {
 | 
			
		||||
		try {
 | 
			
		||||
			await loginAs(id);
 | 
			
		||||
		} catch (err) {
 | 
			
		||||
			if (err instanceof Error) {
 | 
			
		||||
				showError(err.message);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const handleDelete = async (id: number) => {
 | 
			
		||||
		await deleteUser(id);
 | 
			
		||||
		showObjectSuccess("user", "deleted");
 | 
			
		||||
@@ -103,6 +115,7 @@ export default function TableWrapper() {
 | 
			
		||||
					}
 | 
			
		||||
					onDisableToggle={handleDisableToggle}
 | 
			
		||||
					onNewUser={() => showUserModal("new")}
 | 
			
		||||
					onLoginAs={handleLoginAs}
 | 
			
		||||
				/>
 | 
			
		||||
			</div>
 | 
			
		||||
		</div>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user