Skip to content

DevKeys on Pink2 #1762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1409722
add: analytics, dependencies.
ItzNotABug Mar 23, 2025
49a2017
feat: dev-keys on pink2 [wip].
ItzNotABug Mar 23, 2025
ed4b4a9
update: simplify UI components.
ItzNotABug Mar 23, 2025
eb18860
fix: duplicate shortcut.
ItzNotABug Mar 23, 2025
0d4f71e
update: cleanups, expiry fixes.
ItzNotABug Mar 24, 2025
9d62307
fixes: as per designs.
ItzNotABug Mar 24, 2025
6f4a45f
update: streamline components.
ItzNotABug Mar 24, 2025
1b860d3
Merge branch 'feat-pink-v2' into devkeys-pink2
ItzNotABug Mar 24, 2025
4c99007
update: component dir path.
ItzNotABug Mar 26, 2025
1c4c8ba
Merge branch 'feat-pink-v2' into devkeys-pink2
ItzNotABug Mar 26, 2025
62e42bc
Merge branch 'feat-pink-v2' into 'devkeys-pink2'.
ItzNotABug Mar 26, 2025
501b97b
fix: accordion divider; update: wizard column size.
ItzNotABug Mar 26, 2025
e042c12
Merge branch 'feat-pink-v2' into 'devkeys-pink2'.
ItzNotABug Apr 23, 2025
7abc5b5
misc: fixes.
ItzNotABug Apr 23, 2025
140a215
fix: table missing dev key data.
ItzNotABug Apr 23, 2025
e420bab
address comments: misc changes to logic, copy.
ItzNotABug Apr 24, 2025
b8da104
update: batch delete to keys.
ItzNotABug Apr 24, 2025
305ea3c
Merge branch 'feat-pink-v2' into 'devkeys-pink2'.
ItzNotABug Apr 24, 2025
d92ae47
Merge branch 'feat-pink-v2' into 'devkeys-pink2'.
ItzNotABug May 8, 2025
aafa091
update: simplify tab selection logic, run lint.
ItzNotABug May 8, 2025
aff1f05
address comments.
ItzNotABug May 9, 2025
8eb71e1
Merge branch 'feat-pink-v2' into 'devkeys-pink2'.
ItzNotABug May 9, 2025
0692f28
remove: conflict comments.
ItzNotABug May 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/lib/actions/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ export enum Click {
FunctionsDeploymentDeleteClick = 'click_deployment_delete',
FunctionsDeploymentCancelClick = 'click_deployment_cancel',
KeyCreateClick = 'click_key_create',
DevKeyCreateClick = 'click_dev_key_create',
MenuDropDownClick = 'click_menu_dropdown',
MenuOverviewClick = 'click_menu_overview',
ModalCloseClick = 'click_close_modal',
Expand Down Expand Up @@ -311,12 +312,18 @@ export enum Submit {
VariableDelete = 'submit_variable_delete',
VariableUpdate = 'submit_variable_update',
VariableEditor = 'submit_variable_editor',

KeyCreate = 'submit_key_create',
KeyDelete = 'submit_key_delete',
KeyUpdateName = 'submit_key_update_name',
KeyUpdateScopes = 'submit_key_update_scopes',
KeyUpdateExpire = 'submit_key_update_expire',

DevKeyCreate = 'submit_dev_key_create',
DevKeyDelete = 'submit_dev_key_delete',
DevKeyUpdateName = 'submit_dev_key_update_name',
DevKeyUpdateExpire = 'submit_dev_key_update_expire',

PlatformCreate = 'submit_platform_create',
PlatformDelete = 'submit_platform_delete',
PlatformUpdate = 'submit_platform_update',
Expand Down
87 changes: 0 additions & 87 deletions src/lib/components/expirationInput.svelte
Original file line number Diff line number Diff line change
@@ -1,87 +0,0 @@
<script lang="ts">
import { InputDateTime, InputSelect } from '$lib/elements/forms';
import { isSameDay, isValidDate, toLocaleDate } from '$lib/helpers/date';

function incrementToday(value: number, type: 'day' | 'month' | 'year'): string {
const date = new Date();
switch (type) {
case 'day':
date.setDate(date.getDate() + value);
break;
case 'month':
date.setMonth(date.getMonth() + value);
break;
case 'year':
date.setMonth(date.getMonth() + value * 12);
break;
}

return date.toISOString();
}

const options = [
{
label: 'Never',
value: null
},
{
label: '7 Days',
value: incrementToday(7, 'day')
},
{
label: '30 days',
value: incrementToday(30, 'day')
},
{
label: '90 days',
value: incrementToday(90, 'day')
},
{
label: '1 Year',
value: incrementToday(1, 'year')
},
{
label: 'Custom Date',
value: 'custom'
}
];

export let value: string | null = null;
export let dateSelectorLabel: string | undefined = undefined;
export let selectorLabel: string | undefined = 'Expiration Date';
export let resourceType: string | 'key' | 'token' | undefined = 'key';

function initExpirationSelect() {
if (value === null || !isValidDate(value)) return null;

let result = 'custom';
for (const option of options) {
if (!isValidDate(option.value)) continue;

if (isSameDay(new Date(option.value), new Date(value))) {
result = option.value;
break;
}
}

return result;
}
let expirationSelect = initExpirationSelect();
let expirationCustom: string | null = value ?? null;
$: helper =
expirationSelect !== 'custom' && expirationSelect !== null
? `Your ${resourceType} will expire in ${toLocaleDate(value)}`
: null;

$: {
if (!isSameDay(new Date(expirationSelect), new Date(value))) {
value = expirationSelect === 'custom' ? expirationCustom : expirationSelect;
}
}
</script>

<InputSelect bind:value={expirationSelect} {options} id="preset" label={selectorLabel} {helper}>
</InputSelect>
{#if expirationSelect === 'custom'}
<InputDateTime required id="expire" label={dateSelectorLabel} bind:value={expirationCustom} />
{/if}
2 changes: 2 additions & 0 deletions src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export enum Dependencies {
PLATFORMS = 'dependency:platforms',
KEY = 'dependency:key',
KEYS = 'dependency:keys',
DEV_KEY = 'dependency:dev_key',
DEV_KEYS = 'dependency:dev_keys',
DOMAINS = 'dependency:domains',
DOMAIN = 'dependency:domains',
WEBHOOK = 'dependency:webhook',
Expand Down
11 changes: 6 additions & 5 deletions src/lib/layout/usage.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script context="module" lang="ts">
import { ProjectUsageRange, type Models } from '@appwrite.io/console';
export type UsagePeriods = '24h' | '30d' | '90d';

export function periodToDates(period: UsagePeriods): {
Expand Down Expand Up @@ -68,14 +69,14 @@
</script>

<script lang="ts">
import { BarChart } from '$lib/charts';
import { formatNumberWithCommas } from '$lib/helpers/numbers';
import { Card } from '$lib/components';
import { ProjectUsageRange, type Models } from '@appwrite.io/console';
import { page } from '$app/state';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
import { goto } from '$app/navigation';

import { BarChart } from '$lib/charts';
import { Card } from '$lib/components';
import { InputSelect } from '$lib/elements/forms';
import { formatNumberWithCommas } from '$lib/helpers/numbers';
import { Layout, Typography } from '@appwrite.io/pink-svelte';

type MetricMetadata = {
title: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { InputText } from '$lib/elements/forms/index.js';
import { Wizard } from '$lib/layout';
import { Fieldset, Layout, Typography } from '@appwrite.io/pink-svelte';
import ExpirationInput from './expirationInput.svelte';
import Button from '$lib/elements/forms/button.svelte';
import Form from '$lib/elements/forms/form.svelte';
import { sdk } from '$lib/stores/sdk';
import { onboarding } from '../../store';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import { writable } from 'svelte/store';
import Scopes from '../keys/scopes.svelte';

const projectId = $page.params.project;

let showExitModal = false;
let formComponent: Form;
let isSubmitting = writable(false);

let scopes = [];
let name = '',
expire = '';

async function create() {
try {
const { $id } = await sdk.forConsole.projects.createKey(
projectId,
name,
scopes,
expire || undefined
);

if ($onboarding) {
await invalidate(Dependencies.PROJECT);
}

trackEvent(Submit.KeyCreate);
await goto(`${base}/project-${projectId}/overview/keys/${$id}`);
addNotification({
message: `API key has been created`,
type: 'success'
});
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.KeyCreate);
}
}
</script>

<Wizard
title="Create API key"
href={`${base}/project-${projectId}/overview/keys/`}
bind:showExitModal
column
columnSize="s"
confirmExit>
<Form bind:this={formComponent} onSubmit={create} bind:isSubmitting>
<Layout.Stack gap="xxl">
<Fieldset legend="Configuration">
<Layout.Stack>
<InputText
id="name"
label="Name"
placeholder="Enter key name"
required
bind:value={name} />

<ExpirationInput bind:value={expire} keyType="api" />
</Layout.Stack>
</Fieldset>

<Fieldset legend="Scopes">
<Layout.Stack gap="xl">
<Typography.Text>
Choose which permission scopes to grant your application. It is best
practice to allow only the permissions you need to meet your project goals.
</Typography.Text>
<Scopes bind:scopes />
</Layout.Stack>
</Fieldset>
</Layout.Stack>
</Form>

<svelte:fragment slot="footer">
<Button fullWidthMobile secondary on:click={() => (showExitModal = true)}>Cancel</Button>
<Button
fullWidthMobile
on:click={() => formComponent.triggerSubmit()}
disabled={$isSubmitting}>
Create
</Button>
</svelte:fragment>
</Wizard>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<script lang="ts">
import { page } from '$app/state';
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk';
import { Dependencies } from '$lib/constants';
import { type Models } from '@appwrite.io/console';
import { goto, invalidate } from '$app/navigation';
import Confirm from '$lib/components/confirm.svelte';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';

export let showDelete = false;
export let keyType: 'api' | 'dev' = 'api';
export let key: Models.DevKey | Models.Key;

const projectId = page.params.project;

const isApiKey = keyType === 'api';
const label = isApiKey ? 'API' : 'Dev';
const slug = isApiKey ? 'keys' : 'dev-keys';
const event = isApiKey ? Submit.KeyDelete : Submit.DevKeyDelete;
const dependency = isApiKey ? Dependencies.KEYS : Dependencies.DEV_KEYS;

let error: string;

async function handleDelete() {
try {
isApiKey
? await sdk.forConsole.projects.deleteKey(projectId, key.$id)
: await sdk.forConsole.projects.deleteDevKey(projectId, key.$id);

await invalidate(dependency);
showDelete = false;
addNotification({
type: 'success',
message: `${key.name} has been deleted`
});
trackEvent(event);
await goto(`${base}/project-${projectId}/overview/${slug}`);
} catch (e) {
error = e.message;
trackError(e, event);
}
}
</script>

<Confirm onSubmit={handleDelete} title="Delete {label} key" bind:open={showDelete} bind:error>
Are you sure you want to delete this key?
</Confirm>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<script lang="ts">
import { page } from '$app/state';
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk';
import { Dependencies } from '$lib/constants';
import { goto, invalidate } from '$app/navigation';
import Confirm from '$lib/components/confirm.svelte';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';

export let showDelete = false;
export let keyIds: string[] = [];
export let keyType: 'api' | 'dev' = 'api';

let error: string;

const isApiKey = keyType === 'api';
const label = isApiKey ? 'API' : 'dev';
const projectId = page.params.project;

async function handleDelete() {
const slug = isApiKey ? 'keys' : 'dev-keys';
const event = isApiKey ? Submit.KeyDelete : Submit.DevKeyDelete;
const dependency = isApiKey ? Dependencies.KEYS : Dependencies.DEV_KEYS;

try {
await Promise.all(
keyIds.map((key) =>
isApiKey
? sdk.forConsole.projects.deleteKey(projectId, key)
: sdk.forConsole.projects.deleteDevKey(projectId, key)
)
);

await invalidate(dependency);
showDelete = false;

addNotification({
type: 'success',
message: `${keyIds.length} ${label} key${keyIds.length > 1 ? 's' : ''} deleted`
});

trackEvent(event);
await goto(`${base}/project-${projectId}/overview/${slug}`);
} catch (e) {
error = e.message;
trackError(e, event);
} finally {
keyIds = [];
}
}
</script>

<Confirm onSubmit={handleDelete} title={`Delete ${label} key`} bind:open={showDelete} bind:error>
<p>
Are you sure you want to delete <b>{keyIds.length}</b>
{label} key{keyIds.length > 1 ? 's' : ''}?
</p>
</Confirm>
Loading
Loading