Skip to content

Commit

Permalink
feat: user details with usage
Browse files Browse the repository at this point in the history
  • Loading branch information
peterpeterparker committed Feb 5, 2025
1 parent 10b7dd8 commit 653e639
Show file tree
Hide file tree
Showing 15 changed files with 374 additions and 27 deletions.
20 changes: 19 additions & 1 deletion src/frontend/src/lib/api/satellites.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import type {
ListResults as ListAssets,
ListResults_1 as ListDocs,
Rule,
SetRule
SetRule,
UserUsage
} from '$declarations/satellite/satellite.did';
import { getSatelliteActor } from '$lib/api/actors/actor.juno.api';
import type { CustomDomains } from '$lib/types/custom-domain';
Expand Down Expand Up @@ -290,3 +291,20 @@ export const countCollectionAssets = async ({
const { count_collection_assets } = await getSatelliteActor({ satelliteId, identity });
return count_collection_assets(collection);
};

export const getUsageUsage = async ({
satelliteId,
collection,
collectionType,
userId,
identity
}: {
satelliteId: Principal;
collection: string;
collectionType: CollectionType;
userId: Principal;
identity: OptionIdentity;
}): Promise<[] | [UserUsage]> => {
const { get_user_usage } = await getSatelliteActor({ satelliteId, identity });
return get_user_usage(collection, collectionType, toNullable(userId));
};
35 changes: 24 additions & 11 deletions src/frontend/src/lib/components/auth/User.svelte
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
<script lang="ts">
import IconIC from '$lib/components/icons/IconIC.svelte';
import IconNFID from '$lib/components/icons/IconNFID.svelte';
import Identifier from '$lib/components/ui/Identifier.svelte';
import type { User } from '$lib/types/user';
import { formatToDate } from '$lib/utils/date.utils';
import ButtonTableAction from '$lib/components/ui/ButtonTableAction.svelte';

Check failure on line 5 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/components/ui/ButtonTableAction.svelte` import should occur before import of `$lib/components/ui/Identifier.svelte`
import { i18n } from '$lib/stores/i18n.store';

Check failure on line 6 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/stores/i18n.store` import should occur before type import of `$lib/types/user`
import { openUserDetail } from '$lib/services/user.services';

Check failure on line 7 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/services/user.services` import should occur before type import of `$lib/types/user`
import type { Principal } from '@dfinity/principal';

Check failure on line 8 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`@dfinity/principal` type import should occur before import of `$lib/components/ui/Identifier.svelte`
import { authStore } from '$lib/stores/auth.store';

Check failure on line 9 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/stores/auth.store` import should occur before type import of `$lib/types/user`
import UserProvider from '$lib/components/auth/UserProvider.svelte';

Check failure on line 10 in src/frontend/src/lib/components/auth/User.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/components/auth/UserProvider.svelte` import should occur before import of `$lib/components/ui/Identifier.svelte`
interface Props {
user: User;
satelliteId: Principal;
}
let { user }: Props = $props();
let { user, satelliteId }: Props = $props();
let { owner, created_at, data } = $derived(user);
let { owner, created_at } = $derived(user);
let { provider } = $derived(data);
const openModal = async () => {
await openUserDetail({
user,
satelliteId,
identity: $authStore.identity
});
};
</script>

<tr>
<td></td>
<td class="actions">
<ButtonTableAction
icon="visibility"
ariaLabel={$i18n.users.view_details}
onaction={openModal}
/>
</td>
<td><Identifier small={false} identifier={owner.toText()} /></td>
<td class="providers">
{#if provider === 'internet_identity'}
<IconIC title="Internet Identity" />
{:else if provider === 'nfid'}
<IconNFID />
{/if}
<UserProvider {user} />
</td>
<td class="created">{formatToDate(created_at)}</td>
</tr>
Expand Down
24 changes: 24 additions & 0 deletions src/frontend/src/lib/components/auth/UserProvider.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script lang="ts">
import type { User } from '$lib/types/user';

Check failure on line 2 in src/frontend/src/lib/components/auth/UserProvider.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/types/user` type import should occur after import of `$lib/components/icons/IconNFID.svelte`
import IconIC from '$lib/components/icons/IconIC.svelte';
import IconNFID from '$lib/components/icons/IconNFID.svelte';
interface Props {
user: User;
withText?: boolean;
}
let { user, withText = false }: Props = $props();
let { data } = $derived(user);
let { provider } = $derived(data);
</script>

{#if provider === 'internet_identity'}
<IconIC title="Internet Identity" />{#if withText}
Internet Identity{/if}
{:else if provider === 'nfid'}
<IconNFID />{#if withText}
NFID{/if}
{/if}
2 changes: 1 addition & 1 deletion src/frontend/src/lib/components/auth/Users.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
<tbody>
{#if nonNullish($paginationStore.items)}
{#each $paginationStore.items as [_key, user]}
<User {user} />
<User {user} {satelliteId} />
{/each}

{#if !empty && ($paginationStore.pages ?? 0) > 1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<div in:fade>
<Value>
{#snippet label()}
{$i18n.monitoring.usage} <small>(T Cycles)</small>
{$i18n.core.usage} <small>(T Cycles)</small>
{/snippet}

<div class="chart-container">
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/src/lib/components/icons/IconVisibility.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- source: https://fonts.google.com/icons?selected=Material+Icons+Outlined:visibility:&icon.size=24&icon.color=%23000000&icon.query=visi&icon.set=Material+Icons -->
<script lang="ts">
interface Props {
size?: string;
}
let { size = '24px' }: Props = $props();
</script>

<svg
xmlns="http://www.w3.org/2000/svg"
height={size}
width={size}
fill="currentColor"
viewBox="0 0 24 24"
><path d="M0 0h24v24H0V0z" fill="none" /><path
d="M12 6c3.79 0 7.17 2.13 8.82 5.5C19.17 14.87 15.79 17 12 17s-7.17-2.13-8.82-5.5C4.83 8.13 8.21 6 12 6m0-2C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 5c1.38 0 2.5 1.12 2.5 2.5S13.38 14 12 14s-2.5-1.12-2.5-2.5S10.62 9 12 9m0-2c-2.48 0-4.5 2.02-4.5 4.5S9.52 16 12 16s4.5-2.02 4.5-4.5S14.48 7 12 7z"
/></svg
>
5 changes: 5 additions & 0 deletions src/frontend/src/lib/components/modals/Modals.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import SendTokensModal from '$lib/components/modals/SendTokensModal.svelte';
import StopMonitoringStrategyModal from '$lib/components/modals/StopMonitoringStrategyModal.svelte';
import type { JunoModal, JunoModalDetail } from '$lib/types/modal';
import UserDetailsModal from '$lib/components/modals/UserDetailsModal.svelte';

Check failure on line 28 in src/frontend/src/lib/components/modals/Modals.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/components/modals/UserDetailsModal.svelte` import should occur before type import of `$lib/types/modal`
let modal: JunoModal<JunoModalDetail> | undefined = $state(undefined);
Expand Down Expand Up @@ -128,3 +129,7 @@
{#if modal?.type === 'show_monitoring_details' && nonNullish(modal.detail)}
<MonitoringDetailsModal onclose={close} detail={modal.detail} />
{/if}

{#if modal?.type === 'show_user_details' && nonNullish(modal.detail)}
<UserDetailsModal onclose={close} detail={modal.detail} />
{/if}
121 changes: 121 additions & 0 deletions src/frontend/src/lib/components/modals/UserDetailsModal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script lang="ts">
import type { JunoModalDetail, JunoModalShowUserDetail } from '$lib/types/modal';
import Modal from '$lib/components/ui/Modal.svelte';

Check failure on line 3 in src/frontend/src/lib/components/modals/UserDetailsModal.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/components/ui/Modal.svelte` import should occur before type import of `$lib/types/modal`
import { i18n } from '$lib/stores/i18n.store';

Check failure on line 4 in src/frontend/src/lib/components/modals/UserDetailsModal.svelte

View workflow job for this annotation

GitHub Actions / lint

`$lib/stores/i18n.store` import should occur before type import of `$lib/types/modal`
import Canister from '$lib/components/canister/Canister.svelte';

Check warning on line 5 in src/frontend/src/lib/components/modals/UserDetailsModal.svelte

View workflow job for this annotation

GitHub Actions / lint

'Canister' is defined but never used. Allowed unused vars must match /^_/u
import Value from '$lib/components/ui/Value.svelte';
import Identifier from '$lib/components/ui/Identifier.svelte';
import UserProvider from '$lib/components/auth/UserProvider.svelte';
import { formatToDate } from '$lib/utils/date.utils';
interface Props {
detail: JunoModalDetail;
onclose: () => void;
}
let { detail, onclose }: Props = $props();
let { user, usages } = $derived(detail as JunoModalShowUserDetail);
let { owner, created_at, updated_at } = $derived(user);
</script>

<Modal on:junoClose={onclose}>
<h2>{$i18n.users.user_details}</h2>

<div class="card-container columns-3 no-border">
<div>
<Value>
{#snippet label()}
{$i18n.users.identifier}
{/snippet}
<Identifier small={false} identifier={owner.toText()} />
</Value>

<Value>
{#snippet label()}
{$i18n.users.provider}
{/snippet}
<p class="provider"><UserProvider {user} withText /></p>
</Value>
</div>

<div class="timestamps">
<Value>
{#snippet label()}
{$i18n.users.created}
{/snippet}
<p>{formatToDate(created_at)}</p>
</Value>

<Value>
{#snippet label()}
{$i18n.users.updated}
{/snippet}
<p>{formatToDate(updated_at)}</p>
</Value>
</div>

{#if usages.length > 0}
<div class="chart">
<Value>
{#snippet label()}
{$i18n.core.usage}
{/snippet}

<div class="table-container">
<table>
<thead>
<tr>
<th> {$i18n.collections.title} </th>
<th> {$i18n.users.persistence} </th>
<th> {$i18n.users.changes} </th>
</tr>
</thead>

<tbody>
{#each usages as usage}
<tr>
<td>{usage.collection}</td>
<td
>{'Db' in usage.collectionType
? $i18n.datastore.title
: $i18n.storage.title}</td
>
<td>{usage.usage?.changes_count ?? 0}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</Value>
</div>
{/if}
</div>
</Modal>

<style lang="scss">
@use '../../styles/mixins/media';
.card-container {
padding: var(--padding-2x) var(--padding-2x) 0 0;
}
.provider {
display: inline-flex;
gap: var(--padding);
}
.timestamps {
grid-column: 2 / 4;
}
.chart {
grid-column: 1 / 4;
margin: var(--padding-2x) 0;
}
.table-container {
margin: var(--padding) 0 0;
}
</style>
7 changes: 5 additions & 2 deletions src/frontend/src/lib/components/ui/ButtonTableAction.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
import IconDelete from '$lib/components/icons/IconDelete.svelte';
import IconEdit from '$lib/components/icons/IconEdit.svelte';
import IconInfo from '$lib/components/icons/IconInfo.svelte';
import IconVisibility from '$lib/components/icons/IconVisibility.svelte';
interface Props {
ariaLabel: string;
icon: 'delete' | 'edit' | 'info';
onaction: () => void;
icon: 'delete' | 'edit' | 'info' | 'visibility';
onaction: (() => void) | (() => Promise<void>);
}
let { ariaLabel, icon, onaction }: Props = $props();
Expand All @@ -23,6 +24,8 @@
<IconDelete size="20px" />
{:else if icon === 'info'}
<IconInfo size="20px" />
{:else if icon === 'visibility'}
<IconVisibility size="20px" />
{:else}
<IconEdit size="20px" />
{/if}</button
Expand Down
13 changes: 9 additions & 4 deletions src/frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@
"amount": "Amount",
"fee": "Fee",
"preparing": "Gearing up...",
"refreshing_interface": "Refreshing the interface..."
"refreshing_interface": "Refreshing the interface...",
"usage": "Usage"
},
"canisters": {
"top_up": "Top-up",
Expand Down Expand Up @@ -551,7 +552,8 @@
"monitoring_email_missing": "An email address must be provided.",
"monitoring_email_update": "Unexpected error(s) while trying to update your email address.",
"monitoring_notifications_update": "Unexpected error(s) while trying to update the notifications.",
"monitoring_upgrade": "Please upgrade your Mission Control to enable the monitoring feature."
"monitoring_upgrade": "Please upgrade your Mission Control to enable the monitoring feature.",
"user_usage_not_loaded": "Unexpected error(s) while loading the user's usage."
},
"document": {
"owner": "Owner",
Expand Down Expand Up @@ -678,11 +680,15 @@
"placeholder_owners": "Owner ID"
},
"users": {
"user_details": "User's details",
"identifier": "Identifier",
"provider": "Provider",
"created": "Created",
"updated": "Updated",
"empty": "No registered users at the moment."
"empty": "No registered users at the moment.",
"view_details": "View user details",
"persistence": "Persistence",
"changes": "Changes"
},
"monitoring": {
"title": "Monitoring",
Expand Down Expand Up @@ -728,7 +734,6 @@
"stop_monitoring_note": "<strong>Important Note</strong>: it can only be stopped if all your modules are about to stop being monitored or if none are still monitored.",
"last_status_check": "Last status check",
"last_top_up": "Last top-up",
"usage": "Usage",
"weekly_cycles_deposit": "Weekly cycles deposit",
"default_strategy": "Default Strategy",
"strategy_for_modules": "This auto-refill strategy will be suggested as the default for monitoring newly created modules.",
Expand Down
Loading

0 comments on commit 653e639

Please sign in to comment.