mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
addressed comments
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { tick } from 'svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import {
|
||||
@@ -45,9 +45,8 @@
|
||||
|
||||
async function exit() {
|
||||
stopImpersonation();
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await invalidate(Dependencies.ORGANIZATIONS);
|
||||
await goto(base);
|
||||
await tick();
|
||||
await goto(base, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base, resolve } from '$app/paths';
|
||||
import { Query, type Models } from '@appwrite.io/console';
|
||||
import { Layout, Modal, Typography, Icon, Spinner, Tag } from '@appwrite.io/pink-svelte';
|
||||
import { IconDuplicate, IconSearch } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { AvatarInitials, Copy } from '$lib/components';
|
||||
import {
|
||||
readOperatorSnapshot,
|
||||
startImpersonation,
|
||||
readImpersonationTargetUserId
|
||||
readImpersonationTargetUserId,
|
||||
impersonationRevision
|
||||
} from '$lib/appwrite/impersonation';
|
||||
import type { OperatorSnapshot, TargetSnapshot } from '$lib/appwrite/impersonation';
|
||||
|
||||
@@ -20,7 +20,15 @@
|
||||
const RECENTS_KEY_PREFIX = 'console.impersonation.recents.';
|
||||
const MAX_RECENTS = 5;
|
||||
|
||||
type RecentTarget = { $id: string; name: string; email: string };
|
||||
type ImpersonationTarget = {
|
||||
$id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
prefs?: {
|
||||
organization?: string;
|
||||
};
|
||||
};
|
||||
type RecentTarget = ImpersonationTarget;
|
||||
|
||||
function recentsKey(): string | null {
|
||||
const op = readOperatorSnapshot();
|
||||
@@ -38,12 +46,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
function saveRecent(target: { $id: string; name: string; email: string }) {
|
||||
function saveRecent(target: ImpersonationTarget) {
|
||||
const key = recentsKey();
|
||||
if (!key) return;
|
||||
const existing = readRecents().filter((r) => r.$id !== target.$id);
|
||||
const updated: RecentTarget[] = [
|
||||
{ $id: target.$id, name: target.name, email: target.email },
|
||||
{
|
||||
$id: target.$id,
|
||||
name: target.name,
|
||||
email: target.email,
|
||||
prefs: {
|
||||
organization: target.prefs?.organization
|
||||
}
|
||||
},
|
||||
...existing
|
||||
].slice(0, MAX_RECENTS);
|
||||
localStorage.setItem(key, JSON.stringify(updated));
|
||||
@@ -54,6 +69,8 @@
|
||||
let loading = false;
|
||||
let debounceTimer: ReturnType<typeof setTimeout>;
|
||||
let recents: RecentTarget[] = [];
|
||||
let operatorId = readOperatorSnapshot()?.$id ?? null;
|
||||
let impersonatingId = readImpersonationTargetUserId();
|
||||
|
||||
$: if (show) {
|
||||
recents = readRecents();
|
||||
@@ -71,7 +88,7 @@
|
||||
}
|
||||
loading = true;
|
||||
try {
|
||||
const response = await sdk.forConsole.users.list({
|
||||
const response = await sdk.forConsoleAsOperator.users.list({
|
||||
queries: [
|
||||
Query.or([
|
||||
Query.startsWith('name', prefix),
|
||||
@@ -95,8 +112,12 @@
|
||||
$: debounce(() => fetchUsers(search));
|
||||
|
||||
$: activeAccountId = $user?.$id;
|
||||
$: operatorId = readOperatorSnapshot()?.$id ?? null;
|
||||
$: impersonatingId = readImpersonationTargetUserId();
|
||||
$: {
|
||||
void $impersonationRevision;
|
||||
void $user;
|
||||
operatorId = readOperatorSnapshot()?.$id ?? null;
|
||||
impersonatingId = readImpersonationTargetUserId();
|
||||
}
|
||||
|
||||
function isDisabled(id: string): boolean {
|
||||
return id === activeAccountId || (!!operatorId && id === operatorId);
|
||||
@@ -108,7 +129,15 @@
|
||||
return '';
|
||||
}
|
||||
|
||||
async function selectUser(target: { $id: string; name: string; email: string }) {
|
||||
function getTargetPath(target: ImpersonationTarget): string {
|
||||
if (!target.prefs?.organization) return base;
|
||||
|
||||
return resolve('/(console)/organization-[organization]', {
|
||||
organization: target.prefs.organization
|
||||
});
|
||||
}
|
||||
|
||||
async function selectUser(target: ImpersonationTarget) {
|
||||
if (isDisabled(target.$id)) return;
|
||||
|
||||
const existingOperator = readOperatorSnapshot();
|
||||
@@ -130,9 +159,7 @@
|
||||
results = [];
|
||||
show = false;
|
||||
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await invalidate(Dependencies.ORGANIZATIONS);
|
||||
await goto(base);
|
||||
await goto(getTargetPath(target), { invalidateAll: true });
|
||||
}
|
||||
|
||||
function displayName(u: { name: string; email: string; $id: string }): string {
|
||||
|
||||
@@ -51,7 +51,10 @@
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { isProjectBlocked as getIsProjectBlocked } from '$lib/helpers/project';
|
||||
import ImpersonationPicker from '$lib/components/impersonation/picker.svelte';
|
||||
import { readImpersonationTargetUserId } from '$lib/appwrite/impersonation';
|
||||
import {
|
||||
readImpersonationTargetUserId,
|
||||
impersonationRevision
|
||||
} from '$lib/appwrite/impersonation';
|
||||
import { IconEye } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
let showSupport = false;
|
||||
@@ -114,11 +117,16 @@
|
||||
$: currentOrg = organizations.find((org) => org.isSelected);
|
||||
$: isProjectBlocked = getIsProjectBlocked(currentProject);
|
||||
|
||||
let canImpersonate = false;
|
||||
|
||||
// Show impersonation picker when the operator has the capability or impersonation is active.
|
||||
$: canImpersonate =
|
||||
$user?.impersonator === true ||
|
||||
!!$user?.impersonatorUserId ||
|
||||
!!readImpersonationTargetUserId();
|
||||
$: {
|
||||
void $impersonationRevision;
|
||||
canImpersonate =
|
||||
$user?.impersonator === true ||
|
||||
!!$user?.impersonatorUserId ||
|
||||
!!readImpersonationTargetUserId();
|
||||
}
|
||||
|
||||
let showImpersonationPicker = false;
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ function createConsoleSdk(client: Client) {
|
||||
const endpoint = getApiEndpoint();
|
||||
|
||||
const clientConsole = new Client();
|
||||
const clientConsoleOperator = new Client();
|
||||
const scopedConsoleClient = new Client();
|
||||
|
||||
const clientProject = new Client();
|
||||
@@ -76,6 +77,7 @@ const clientRealtime = new Client();
|
||||
if (!building) {
|
||||
scopedConsoleClient.setProject('console');
|
||||
clientConsole.setEndpoint(endpoint).setProject('console');
|
||||
clientConsoleOperator.setEndpoint(endpoint).setProject('console');
|
||||
|
||||
clientProject.setEndpoint(endpoint).setMode('admin');
|
||||
clientRealtime.setEndpoint(endpoint).setProject('console');
|
||||
@@ -145,6 +147,10 @@ export const realtime = {
|
||||
|
||||
export const sdk = {
|
||||
forConsole: createConsoleSdk(clientConsole),
|
||||
// Operator-only console client. It is intentionally not registered with the
|
||||
// impersonation module so admin actions like switching targets stay scoped
|
||||
// to the real operator session.
|
||||
forConsoleAsOperator: createConsoleSdk(clientConsoleOperator),
|
||||
|
||||
forConsoleIn(region: string) {
|
||||
const regionAwareEndpoint = getApiEndpoint(region);
|
||||
|
||||
Reference in New Issue
Block a user