addressed comments

This commit is contained in:
harsh mahajan
2026-04-23 16:46:55 +05:30
parent cca73e6ab3
commit 9e8ff77a21
4 changed files with 64 additions and 24 deletions
@@ -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>
+41 -14
View File
@@ -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 {
+13 -5
View File
@@ -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;
+6
View File
@@ -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);