mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-11-03 17:13:33 +00:00 
			
		
		
		
	Log in as user support
This commit is contained in:
		@@ -37,6 +37,7 @@ export * from "./getToken";
 | 
				
			|||||||
export * from "./getUser";
 | 
					export * from "./getUser";
 | 
				
			||||||
export * from "./getUsers";
 | 
					export * from "./getUsers";
 | 
				
			||||||
export * from "./helpers";
 | 
					export * from "./helpers";
 | 
				
			||||||
 | 
					export * from "./loginAsUser";
 | 
				
			||||||
export * from "./models";
 | 
					export * from "./models";
 | 
				
			||||||
export * from "./refreshToken";
 | 
					export * from "./refreshToken";
 | 
				
			||||||
export * from "./renewCertificate";
 | 
					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 {
 | 
					export interface HealthResponse {
 | 
				
			||||||
	status: string;
 | 
						status: string;
 | 
				
			||||||
@@ -15,3 +15,7 @@ export interface ValidatedCertificateResponse {
 | 
				
			|||||||
	certificate: Record<string, any>;
 | 
						certificate: Record<string, any>;
 | 
				
			||||||
	certificateKey: boolean;
 | 
						certificateKey: boolean;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface LoginAsTokenResponse extends TokenResponse {
 | 
				
			||||||
 | 
						user: User;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
import { IconLock, IconLogout, IconUser } from "@tabler/icons-react";
 | 
					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 { useAuthState } from "src/context";
 | 
				
			||||||
import { useUser } from "src/hooks";
 | 
					import { useUser } from "src/hooks";
 | 
				
			||||||
import { T } from "src/locale";
 | 
					import { T } from "src/locale";
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
import { useQueryClient } from "@tanstack/react-query";
 | 
					import { useQueryClient } from "@tanstack/react-query";
 | 
				
			||||||
import { createContext, type ReactNode, useContext, useState } from "react";
 | 
					import { createContext, type ReactNode, useContext, useState } from "react";
 | 
				
			||||||
import { useIntervalWhen } from "rooks";
 | 
					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";
 | 
					import AuthStore from "src/modules/AuthStore";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Context
 | 
					// Context
 | 
				
			||||||
export interface AuthContextType {
 | 
					export interface AuthContextType {
 | 
				
			||||||
	authenticated: boolean;
 | 
						authenticated: boolean;
 | 
				
			||||||
	login: (username: string, password: string) => Promise<void>;
 | 
						login: (username: string, password: string) => Promise<void>;
 | 
				
			||||||
 | 
						loginAs: (id: number) => Promise<void>;
 | 
				
			||||||
	logout: () => void;
 | 
						logout: () => void;
 | 
				
			||||||
	token?: string;
 | 
						token?: string;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -34,7 +35,20 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
 | 
				
			|||||||
		handleTokenUpdate(response);
 | 
							handleTokenUpdate(response);
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const loginAs = async (id: number) => {
 | 
				
			||||||
 | 
							const response = await loginAsUser(id);
 | 
				
			||||||
 | 
							AuthStore.add(response);
 | 
				
			||||||
 | 
							queryClient.clear();
 | 
				
			||||||
 | 
							window.location.reload();
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const logout = () => {
 | 
						const logout = () => {
 | 
				
			||||||
 | 
							if (AuthStore.count() >= 2) {
 | 
				
			||||||
 | 
								AuthStore.drop();
 | 
				
			||||||
 | 
								queryClient.clear();
 | 
				
			||||||
 | 
								window.location.reload();
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		AuthStore.clear();
 | 
							AuthStore.clear();
 | 
				
			||||||
		setAuthenticated(false);
 | 
							setAuthenticated(false);
 | 
				
			||||||
		queryClient.clear();
 | 
							queryClient.clear();
 | 
				
			||||||
@@ -55,7 +69,7 @@ function AuthProvider({ children, tokenRefreshInterval = 5 * 60 * 1000 }: Props)
 | 
				
			|||||||
		true,
 | 
							true,
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const value = { authenticated, login, logout };
 | 
						const value = { authenticated, login, logout, loginAs };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
 | 
						return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -201,6 +201,7 @@
 | 
				
			|||||||
  "user.current-password": "Current Password",
 | 
					  "user.current-password": "Current Password",
 | 
				
			||||||
  "user.edit-profile": "Edit Profile",
 | 
					  "user.edit-profile": "Edit Profile",
 | 
				
			||||||
  "user.full-name": "Full Name",
 | 
					  "user.full-name": "Full Name",
 | 
				
			||||||
 | 
					  "user.login-as": "Sign in as {name}",
 | 
				
			||||||
  "user.logout": "Logout",
 | 
					  "user.logout": "Logout",
 | 
				
			||||||
  "user.new-password": "New Password",
 | 
					  "user.new-password": "New Password",
 | 
				
			||||||
  "user.nickname": "Nickname",
 | 
					  "user.nickname": "Nickname",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -605,6 +605,9 @@
 | 
				
			|||||||
	"user.full-name": {
 | 
						"user.full-name": {
 | 
				
			||||||
		"defaultMessage": "Full Name"
 | 
							"defaultMessage": "Full Name"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						"user.login-as": {
 | 
				
			||||||
 | 
							"defaultMessage": "Sign in as {name}"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	"user.logout": {
 | 
						"user.logout": {
 | 
				
			||||||
		"defaultMessage": "Logout"
 | 
							"defaultMessage": "Logout"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,6 +44,7 @@ export class AuthStore {
 | 
				
			|||||||
	// 	const t = this.tokens;
 | 
						// 	const t = this.tokens;
 | 
				
			||||||
	// 	return t.length > 0;
 | 
						// 	return t.length > 0;
 | 
				
			||||||
	// }
 | 
						// }
 | 
				
			||||||
 | 
						// Start from the END of the stack and work backwards
 | 
				
			||||||
	hasActiveToken() {
 | 
						hasActiveToken() {
 | 
				
			||||||
		const t = this.tokens;
 | 
							const t = this.tokens;
 | 
				
			||||||
		if (!t.length) {
 | 
							if (!t.length) {
 | 
				
			||||||
@@ -68,22 +69,27 @@ export class AuthStore {
 | 
				
			|||||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify([{ token, expires }]));
 | 
							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) {
 | 
						add({ token, expires }: TokenResponse) {
 | 
				
			||||||
		const t = this.tokens;
 | 
							const t = this.tokens;
 | 
				
			||||||
		t.push({ token, expires });
 | 
							t.push({ token, expires });
 | 
				
			||||||
		localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
 | 
							localStorage.setItem(TOKEN_KEY, JSON.stringify(t));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Drop a token from the stack
 | 
						// Drop a token from the END of the stack
 | 
				
			||||||
	drop() {
 | 
						drop() {
 | 
				
			||||||
		const t = this.tokens;
 | 
							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() {
 | 
						clear() {
 | 
				
			||||||
		localStorage.removeItem(TOKEN_KEY);
 | 
							localStorage.removeItem(TOKEN_KEY);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						count() {
 | 
				
			||||||
 | 
							return this.tokens.length;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default new AuthStore();
 | 
					export default new AuthStore();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react";
 | 
					import { IconArrowsCross, IconBolt, IconBoltOff, IconDisc } from "@tabler/icons-react";
 | 
				
			||||||
import { useNavigate } from "react-router-dom";
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { HasPermission } from "src/components";
 | 
				
			||||||
import { useHostReport } from "src/hooks";
 | 
					import { useHostReport } from "src/hooks";
 | 
				
			||||||
import { T } from "src/locale";
 | 
					import { T } from "src/locale";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,6 +16,7 @@ const Dashboard = () => {
 | 
				
			|||||||
			<div className="row row-deck row-cards">
 | 
								<div className="row row-deck row-cards">
 | 
				
			||||||
				<div className="col-12 my-4">
 | 
									<div className="col-12 my-4">
 | 
				
			||||||
					<div className="row row-cards">
 | 
										<div className="row row-cards">
 | 
				
			||||||
 | 
											<HasPermission permission="proxyHosts" type="view" hideError>
 | 
				
			||||||
							<div className="col-sm-6 col-lg-3">
 | 
												<div className="col-sm-6 col-lg-3">
 | 
				
			||||||
								<a
 | 
													<a
 | 
				
			||||||
									href="/nginx/proxy"
 | 
														href="/nginx/proxy"
 | 
				
			||||||
@@ -40,6 +42,8 @@ const Dashboard = () => {
 | 
				
			|||||||
									</div>
 | 
														</div>
 | 
				
			||||||
								</a>
 | 
													</a>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
 | 
											</HasPermission>
 | 
				
			||||||
 | 
											<HasPermission permission="redirectionHosts" type="view" hideError>
 | 
				
			||||||
							<div className="col-sm-6 col-lg-3">
 | 
												<div className="col-sm-6 col-lg-3">
 | 
				
			||||||
								<a
 | 
													<a
 | 
				
			||||||
									href="/nginx/redirection"
 | 
														href="/nginx/redirection"
 | 
				
			||||||
@@ -57,12 +61,17 @@ const Dashboard = () => {
 | 
				
			|||||||
												</span>
 | 
																	</span>
 | 
				
			||||||
											</div>
 | 
																</div>
 | 
				
			||||||
											<div className="col">
 | 
																<div className="col">
 | 
				
			||||||
											<T id="redirection-hosts.count" data={{ count: hostReport?.redirection }} />
 | 
																	<T
 | 
				
			||||||
 | 
																		id="redirection-hosts.count"
 | 
				
			||||||
 | 
																		data={{ count: hostReport?.redirection }}
 | 
				
			||||||
 | 
																	/>
 | 
				
			||||||
											</div>
 | 
																</div>
 | 
				
			||||||
										</div>
 | 
															</div>
 | 
				
			||||||
									</div>
 | 
														</div>
 | 
				
			||||||
								</a>
 | 
													</a>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
 | 
											</HasPermission>
 | 
				
			||||||
 | 
											<HasPermission permission="streams" type="view" hideError>
 | 
				
			||||||
							<div className="col-sm-6 col-lg-3">
 | 
												<div className="col-sm-6 col-lg-3">
 | 
				
			||||||
								<a
 | 
													<a
 | 
				
			||||||
									href="/nginx/stream"
 | 
														href="/nginx/stream"
 | 
				
			||||||
@@ -86,6 +95,8 @@ const Dashboard = () => {
 | 
				
			|||||||
									</div>
 | 
														</div>
 | 
				
			||||||
								</a>
 | 
													</a>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
 | 
											</HasPermission>
 | 
				
			||||||
 | 
											<HasPermission permission="deadHosts" type="view" hideError>
 | 
				
			||||||
							<div className="col-sm-6 col-lg-3">
 | 
												<div className="col-sm-6 col-lg-3">
 | 
				
			||||||
								<a
 | 
													<a
 | 
				
			||||||
									href="/nginx/404"
 | 
														href="/nginx/404"
 | 
				
			||||||
@@ -109,6 +120,7 @@ const Dashboard = () => {
 | 
				
			|||||||
									</div>
 | 
														</div>
 | 
				
			||||||
								</a>
 | 
													</a>
 | 
				
			||||||
							</div>
 | 
												</div>
 | 
				
			||||||
 | 
											</HasPermission>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
				</div>
 | 
									</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 { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
 | 
				
			||||||
import { useMemo } from "react";
 | 
					import { useMemo } from "react";
 | 
				
			||||||
import type { User } from "src/api/backend";
 | 
					import type { User } from "src/api/backend";
 | 
				
			||||||
@@ -24,6 +32,7 @@ interface Props {
 | 
				
			|||||||
	onDeleteUser?: (id: number) => void;
 | 
						onDeleteUser?: (id: number) => void;
 | 
				
			||||||
	onDisableToggle?: (id: number, enabled: boolean) => void;
 | 
						onDisableToggle?: (id: number, enabled: boolean) => void;
 | 
				
			||||||
	onNewUser?: () => void;
 | 
						onNewUser?: () => void;
 | 
				
			||||||
 | 
						onLoginAs?: (id: number) => void;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export default function Table({
 | 
					export default function Table({
 | 
				
			||||||
	data,
 | 
						data,
 | 
				
			||||||
@@ -36,6 +45,7 @@ export default function Table({
 | 
				
			|||||||
	onDeleteUser,
 | 
						onDeleteUser,
 | 
				
			||||||
	onDisableToggle,
 | 
						onDisableToggle,
 | 
				
			||||||
	onNewUser,
 | 
						onNewUser,
 | 
				
			||||||
 | 
						onLoginAs,
 | 
				
			||||||
}: Props) {
 | 
					}: Props) {
 | 
				
			||||||
	const columnHelper = createColumnHelper<User>();
 | 
						const columnHelper = createColumnHelper<User>();
 | 
				
			||||||
	const columns = useMemo(
 | 
						const columns = useMemo(
 | 
				
			||||||
@@ -153,6 +163,24 @@ export default function Table({
 | 
				
			|||||||
											<IconPower size={16} />
 | 
																<IconPower size={16} />
 | 
				
			||||||
											<T id={info.row.original.isDisabled ? "action.enable" : "action.disable"} />
 | 
																<T id={info.row.original.isDisabled ? "action.enable" : "action.disable"} />
 | 
				
			||||||
										</a>
 | 
															</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" />
 | 
															<div className="dropdown-divider" />
 | 
				
			||||||
										<a
 | 
															<a
 | 
				
			||||||
											className="dropdown-item"
 | 
																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>({
 | 
						const tableInstance = useReactTable<User>({
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,14 +4,16 @@ import { useState } from "react";
 | 
				
			|||||||
import Alert from "react-bootstrap/Alert";
 | 
					import Alert from "react-bootstrap/Alert";
 | 
				
			||||||
import { deleteUser, toggleUser } from "src/api/backend";
 | 
					import { deleteUser, toggleUser } from "src/api/backend";
 | 
				
			||||||
import { Button, LoadingPage } from "src/components";
 | 
					import { Button, LoadingPage } from "src/components";
 | 
				
			||||||
 | 
					import { useAuthState } from "src/context";
 | 
				
			||||||
import { useUser, useUsers } from "src/hooks";
 | 
					import { useUser, useUsers } from "src/hooks";
 | 
				
			||||||
import { T } from "src/locale";
 | 
					import { T } from "src/locale";
 | 
				
			||||||
import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals";
 | 
					import { showDeleteConfirmModal, showPermissionsModal, showSetPasswordModal, showUserModal } from "src/modals";
 | 
				
			||||||
import { showObjectSuccess } from "src/notifications";
 | 
					import { showError, showObjectSuccess } from "src/notifications";
 | 
				
			||||||
import Table from "./Table";
 | 
					import Table from "./Table";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function TableWrapper() {
 | 
					export default function TableWrapper() {
 | 
				
			||||||
	const queryClient = useQueryClient();
 | 
						const queryClient = useQueryClient();
 | 
				
			||||||
 | 
						const { loginAs } = useAuthState();
 | 
				
			||||||
	const [search, setSearch] = useState("");
 | 
						const [search, setSearch] = useState("");
 | 
				
			||||||
	const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]);
 | 
						const { isFetching, isLoading, isError, error, data } = useUsers(["permissions"]);
 | 
				
			||||||
	const { data: currentUser } = useUser("me");
 | 
						const { data: currentUser } = useUser("me");
 | 
				
			||||||
@@ -24,6 +26,16 @@ export default function TableWrapper() {
 | 
				
			|||||||
		return <Alert variant="danger">{error?.message || "Unknown error"}</Alert>;
 | 
							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) => {
 | 
						const handleDelete = async (id: number) => {
 | 
				
			||||||
		await deleteUser(id);
 | 
							await deleteUser(id);
 | 
				
			||||||
		showObjectSuccess("user", "deleted");
 | 
							showObjectSuccess("user", "deleted");
 | 
				
			||||||
@@ -103,6 +115,7 @@ export default function TableWrapper() {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
					onDisableToggle={handleDisableToggle}
 | 
										onDisableToggle={handleDisableToggle}
 | 
				
			||||||
					onNewUser={() => showUserModal("new")}
 | 
										onNewUser={() => showUserModal("new")}
 | 
				
			||||||
 | 
										onLoginAs={handleLoginAs}
 | 
				
			||||||
				/>
 | 
									/>
 | 
				
			||||||
			</div>
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user