mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: deployments
This commit is contained in:
@@ -1,26 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { Card, Empty, Layout } from '@appwrite.io/pink-svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { EmptySearch } from '.';
|
||||
import { queries } from './filters';
|
||||
|
||||
export let resource: string;
|
||||
</script>
|
||||
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold">Sorry, we couldn't find any {resource}.</b>
|
||||
<p>There are no {resource} that match your filters.</p>
|
||||
</div>
|
||||
<div class="u-flex common-section u-main-center">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => {
|
||||
queries.clearAll();
|
||||
queries.apply();
|
||||
}}>
|
||||
Clear filters
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
<Card.Base padding="none">
|
||||
<Empty
|
||||
title={`Sorry, we couldn't find any ${resource}`}
|
||||
description={`There are no ${resource} that match your filters.`}
|
||||
type="secondary">
|
||||
<svelte:fragment slot="actions">
|
||||
<Layout.Stack direction="row" justifyContent="center">
|
||||
<Button
|
||||
secondary
|
||||
on:click={() => {
|
||||
queries.clearAll();
|
||||
queries.apply();
|
||||
}}>
|
||||
Clear filters
|
||||
</Button>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</Empty>
|
||||
</Card.Base>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<Card.Base padding="none">
|
||||
<Empty
|
||||
title={`Sorry, we couldn't find ‘${search}’`}
|
||||
title={`Sorry, we couldn't find ${search ? `‘${search}’` : `any ${target}`}`}
|
||||
description={`There are no ${target} that match your search.`}
|
||||
type="secondary">
|
||||
<svelte:fragment slot="actions">
|
||||
|
||||
@@ -158,13 +158,13 @@
|
||||
{/if}
|
||||
<div class="u-flex u-gap-8">
|
||||
{#if singleCondition}
|
||||
<Button text on:click={toggleDropdown}>Cancel</Button>
|
||||
<Button size="s" text on:click={toggleDropdown}>Cancel</Button>
|
||||
{:else}
|
||||
<Button disabled={applied === 0} text on:click={clearAll}>
|
||||
<Button size="s" disabled={applied === 0} text on:click={clearAll}>
|
||||
Clear all
|
||||
</Button>
|
||||
{/if}
|
||||
<Button on:click={apply} disabled={isButtonDisabled}>Apply</Button>
|
||||
<Button size="s" on:click={apply} disabled={isButtonDisabled}>Apply</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -174,7 +174,7 @@
|
||||
|
||||
<div class="is-only-mobile">
|
||||
<slot name="mobile" {disabled} toggle={toggleMobileModal}>
|
||||
<Button secondary on:click={toggleMobileModal} {fullWidthMobile}>
|
||||
<Button size="s" secondary on:click={toggleMobileModal} {fullWidthMobile}>
|
||||
<i class="icon-filter u-opacity-50" />
|
||||
Filters
|
||||
{#if applied > 0}
|
||||
@@ -219,11 +219,12 @@
|
||||
{/if}
|
||||
<div class="u-flex u-gap-8">
|
||||
{#if singleCondition}
|
||||
<Button text on:click={() => (showFiltersMobile = false)}>Cancel</Button>
|
||||
<Button size="s" text on:click={() => (showFiltersMobile = false)}
|
||||
>Cancel</Button>
|
||||
{:else}
|
||||
<Button text on:click={clearAll}>Clear all</Button>
|
||||
<Button size="s" text on:click={clearAll}>Clear all</Button>
|
||||
{/if}
|
||||
<Button on:click={apply} disabled={isButtonDisabled}>Apply</Button>
|
||||
<Button size="s" on:click={apply} disabled={isButtonDisabled}>Apply</Button>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
{#if $columns?.length}
|
||||
<DropList bind:show={showSelectColumns} scrollable wrapperFullWidth={fullWidthMobile}>
|
||||
<Button
|
||||
size="s"
|
||||
secondary
|
||||
on:click={() => (showSelectColumns = !showSelectColumns)}
|
||||
{fullWidthMobile}>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
export let danger = false;
|
||||
export let icon = false;
|
||||
export let link = false;
|
||||
export let size: Props['size'] = 'medium';
|
||||
export let size: Props['size'] = 'm';
|
||||
export let disabled = false;
|
||||
export let external = false;
|
||||
export let href: string = null;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { Breadcrumbs } from '$lib/layout';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { project } from '../../store';
|
||||
@@ -16,6 +17,10 @@
|
||||
{
|
||||
href: `${base}/project-${$project?.$id}/sites`,
|
||||
title: 'Sites'
|
||||
},
|
||||
{
|
||||
href: `${base}/project-${$project?.$id}/sites/site-${$page.params.site}`,
|
||||
title: $page.params.site
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { invalidate, goto } from '$app/navigation';
|
||||
import { registerCommands } from '$lib/commandCenter';
|
||||
import { project } from '../../../store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { base } from '$app/paths';
|
||||
import { canWriteSites } from '$lib/stores/roles';
|
||||
|
||||
onMount(() => {
|
||||
let previousStatus = null;
|
||||
return sdk.forConsole.client.subscribe<Models.Deployment>('console', (message) => {
|
||||
if (previousStatus === message.payload.status) {
|
||||
return;
|
||||
}
|
||||
previousStatus = message.payload.status;
|
||||
if (message.events.includes('sites.*.deployments.*.create')) {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
|
||||
return;
|
||||
}
|
||||
if (message.events.includes('sites.*.deployments.*.update')) {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
|
||||
return;
|
||||
}
|
||||
if (message.events.includes('sites.*.deployments.*.delete')) {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Create deployment',
|
||||
async callback() {
|
||||
if (!$page.url.pathname.endsWith($page.params.site)) {
|
||||
await goto(`${base}/project-${$project.$id}/sites/site-${$page.params.site}`);
|
||||
}
|
||||
},
|
||||
keys: $page.url.pathname.endsWith($page.params.site) ? ['c'] : ['c', 'd'],
|
||||
group: 'functions',
|
||||
icon: 'plus',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Permissions',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings#permissions`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
icon: 'search',
|
||||
group: 'functions',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Events',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings#events`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
icon: 'calendar',
|
||||
group: 'functions',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Variables',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings#variables`
|
||||
);
|
||||
},
|
||||
icon: 'list',
|
||||
group: 'functions',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Timeout',
|
||||
callback() {
|
||||
goto(
|
||||
`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings#timeout`
|
||||
);
|
||||
},
|
||||
icon: 'x-circle',
|
||||
group: 'functions',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Schedule',
|
||||
async callback() {
|
||||
await goto(
|
||||
`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings#schedule`
|
||||
);
|
||||
scrollBy({ top: -100 });
|
||||
},
|
||||
icon: 'clock',
|
||||
group: 'functions',
|
||||
disabled: !$canWriteSites
|
||||
},
|
||||
{
|
||||
label: 'Go to deployments',
|
||||
callback() {
|
||||
goto(`${base}/project-${$project.$id}/sites/site-${$page.params.site}`);
|
||||
},
|
||||
keys: ['g', 'd'],
|
||||
group: 'navigation',
|
||||
rank: 10,
|
||||
disabled: $page.url.pathname.endsWith($page.params.site)
|
||||
},
|
||||
{
|
||||
label: 'Go to usage',
|
||||
callback() {
|
||||
goto(`${base}/project-${$project.$id}/sites/site-${$page.params.site}/usage`);
|
||||
},
|
||||
keys: ['g', 'u'],
|
||||
group: 'navigation',
|
||||
rank: 10,
|
||||
disabled: $page.url.pathname.endsWith('usage')
|
||||
},
|
||||
{
|
||||
label: 'Go to executions',
|
||||
callback() {
|
||||
goto(`${base}/project-${$project.$id}/sites/site-${$page.params.site}/executions`);
|
||||
},
|
||||
keys: ['g', 'e'],
|
||||
group: 'navigation',
|
||||
rank: 10,
|
||||
disabled: $page.url.pathname.endsWith('executions')
|
||||
},
|
||||
{
|
||||
label: 'Go to settings',
|
||||
callback() {
|
||||
goto(`${base}/project-${$project.$id}/sites/site-${$page.params.site}/settings`);
|
||||
},
|
||||
keys: ['g', 's'],
|
||||
group: 'navigation',
|
||||
rank: 10,
|
||||
disabled: $page.url.pathname.endsWith('settings') || !$canWriteSites
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@@ -0,0 +1,28 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.SITE);
|
||||
depends(Dependencies.DEPLOYMENTS);
|
||||
|
||||
try {
|
||||
const [site, proxyRuleList] = await Promise.all([
|
||||
sdk.forProject.sites.get(params.site),
|
||||
sdk.forProject.proxy.listRules([
|
||||
Query.equal('resourceType', 'sites'),
|
||||
Query.equal('resourceId', params.site),
|
||||
Query.limit(1)
|
||||
])
|
||||
]);
|
||||
|
||||
return {
|
||||
site,
|
||||
proxyRuleList
|
||||
};
|
||||
} catch (e) {
|
||||
error(e.code, e.message);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
<script lang="ts">
|
||||
import { PaginationWithLimit, ViewSelector, EmptyFilter } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { deploymentList } from './store';
|
||||
import { Container, ContainerHeader } from '$lib/layout';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { Filters } from '$lib/components/filters';
|
||||
import { queries, tags } from '$lib/components/filters/store';
|
||||
import { View } from '$lib/helpers/load';
|
||||
import RedeployModal from './redeployModal.svelte';
|
||||
import QuickFilters from './quickFilters.svelte';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { onMount } from 'svelte';
|
||||
import Table from './table.svelte';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
let showRedeploy = false;
|
||||
|
||||
let selectedDeployment: Models.Deployment = null;
|
||||
|
||||
const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Deployment ID', type: 'string', show: true, width: 150 },
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
type: 'enum',
|
||||
show: true,
|
||||
width: 110,
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: ['ready', 'processing', 'building', 'waiting', 'cancelled', 'failed'],
|
||||
filter: false
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
title: 'Source',
|
||||
type: 'string',
|
||||
show: true,
|
||||
width: 90,
|
||||
array: true,
|
||||
format: 'enum',
|
||||
elements: [
|
||||
{ value: 'manual', label: 'Manual' },
|
||||
{ value: 'cli', label: 'CLI' },
|
||||
{ value: 'vcs', label: 'Git' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: '$updatedAt',
|
||||
title: 'Updated',
|
||||
type: 'datetime',
|
||||
show: true,
|
||||
width: 150,
|
||||
format: 'datetime'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'buildTime',
|
||||
title: 'Build time',
|
||||
type: 'integer',
|
||||
show: true,
|
||||
width: 90,
|
||||
elements: [
|
||||
{
|
||||
value: 15,
|
||||
label: 'more than 15 seconds'
|
||||
},
|
||||
{
|
||||
value: 60,
|
||||
label: 'more than 1 minute'
|
||||
},
|
||||
{
|
||||
value: 5 * 60,
|
||||
label: 'more than 5 minutes'
|
||||
}
|
||||
],
|
||||
filter: false
|
||||
},
|
||||
{
|
||||
id: 'size',
|
||||
title: 'Size',
|
||||
type: 'integer',
|
||||
show: true,
|
||||
width: 140,
|
||||
elements: [
|
||||
{
|
||||
value: 2 * 1000 * 1000,
|
||||
label: 'more than 2MB'
|
||||
},
|
||||
{
|
||||
value: 10 * 1000 * 1000,
|
||||
label: 'more than 10MB'
|
||||
},
|
||||
{
|
||||
value: 50 * 1000 * 1000,
|
||||
label: 'more than 50MB'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'buildSize',
|
||||
title: 'Build size',
|
||||
type: 'integer',
|
||||
show: false,
|
||||
filter: false,
|
||||
width: 80
|
||||
}
|
||||
]);
|
||||
|
||||
let showMobileFilters = false;
|
||||
|
||||
onMount(() => {
|
||||
data?.query ? (showMobileFilters = true) : (showMobileFilters = false);
|
||||
});
|
||||
|
||||
function clearAll() {
|
||||
queries.clearAll();
|
||||
queries.apply();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<ContainerHeader title="Deployments"></ContainerHeader>
|
||||
<Layout.Stack justifyContent="space-between" direction="row">
|
||||
<Layout.Stack alignItems="center" direction="row" gap="s">
|
||||
<QuickFilters {columns} />
|
||||
<Filters query={data.query} {columns} let:disabled let:toggle singleCondition>
|
||||
<Layout.Stack alignItems="center" direction="row" gap="xs">
|
||||
<Button text on:click={toggle} {disabled} size="s" ariaLabel="open filter">
|
||||
<span class="icon-filter-line" />
|
||||
<span class="text">More filters</span>
|
||||
</Button>
|
||||
{#if $tags?.length}
|
||||
<!-- TODO: add vertical divider to pink 2 -->
|
||||
<div
|
||||
style="flex-basis:1px; background-color:hsl(var(--color-border)); width: 1px">
|
||||
</div>
|
||||
<Button text on:click={clearAll} size="s">Clear all</Button>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Filters>
|
||||
</Layout.Stack>
|
||||
<ViewSelector view={View.Table} {columns} hideView allowNoColumns hideText />
|
||||
</Layout.Stack>
|
||||
<div class="is-only-mobile">
|
||||
<Layout.Stack justifyContent="space-between" direction="row">
|
||||
<Button
|
||||
text
|
||||
size="s"
|
||||
on:click={() => (showMobileFilters = !showMobileFilters)}
|
||||
ariaLabel="toggle filters">
|
||||
<span class="icon-filter-line" />
|
||||
<span class="text">Filters</span>
|
||||
</Button>
|
||||
|
||||
<ViewSelector view={View.Table} {columns} hideView allowNoColumns />
|
||||
</Layout.Stack>
|
||||
<div
|
||||
class:u-hide={!showMobileFilters}
|
||||
class:u-flex={showMobileFilters}
|
||||
class=" u-gap-8 u-flex-wrap u-margin-block-start-16">
|
||||
<QuickFilters {columns} />
|
||||
|
||||
<Filters query={data.query} {columns} clearOnClick>
|
||||
<svelte:fragment slot="mobile" let:disabled let:toggle>
|
||||
<Pill
|
||||
button
|
||||
on:click={toggle}
|
||||
{disabled}
|
||||
class="u-flex u-gap-4 u-cross-center"
|
||||
style="--p-tag-content-height: auto">
|
||||
<span class="text">More filters</span>
|
||||
<span class="icon-cheveron-down" />
|
||||
</Pill>
|
||||
</svelte:fragment>
|
||||
</Filters>
|
||||
</div>
|
||||
</div>
|
||||
{#if $deploymentList.total}
|
||||
<Table columns={$columns} {data} />
|
||||
{:else if data?.query}
|
||||
<EmptyFilter resource="deployments" />
|
||||
{/if}
|
||||
|
||||
<PaginationWithLimit
|
||||
name="Deployments"
|
||||
limit={data.limit}
|
||||
offset={data.offset}
|
||||
total={$deploymentList?.total} />
|
||||
</Container>
|
||||
|
||||
{#if selectedDeployment}
|
||||
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
|
||||
{/if}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { getLimit, getPage, getQuery, pageToOffset } from '$lib/helpers/load';
|
||||
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
import { queries, queryParamToMap } from '$lib/components/filters';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
|
||||
const data = await parent();
|
||||
depends(Dependencies.DEPLOYMENTS);
|
||||
const page = getPage(url);
|
||||
const limit = getLimit(url, route, PAGE_LIMIT);
|
||||
const offset = pageToOffset(page, limit);
|
||||
const query = getQuery(url);
|
||||
|
||||
const parsedQueries = queryParamToMap(query || '[]');
|
||||
queries.set(parsedQueries);
|
||||
return {
|
||||
offset,
|
||||
limit,
|
||||
query,
|
||||
activeDeployment: data.site.deploymentId
|
||||
? await sdk.forProject.sites.getDeployment(params.site, data.site.deploymentId)
|
||||
: null,
|
||||
deploymentList: await sdk.forProject.sites.listDeployments(params.site, [
|
||||
Query.limit(limit),
|
||||
Query.offset(offset),
|
||||
Query.orderDesc(''),
|
||||
...parsedQueries.values()
|
||||
])
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let showCancel = false;
|
||||
export let selectedDeployment: Models.Deployment;
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await sdk.forProject.functions.updateDeploymentBuild(
|
||||
selectedDeployment.resourceId,
|
||||
selectedDeployment.$id
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
showCancel = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Deployment has been deleted`
|
||||
});
|
||||
trackEvent(Submit.DeploymentCancel);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.DeploymentCancel);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Cancel deployment"
|
||||
bind:show={showCancel}
|
||||
onSubmit={handleSubmit}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
headerDivider={false}>
|
||||
<p data-private>Are you sure you want to cancel this deployment?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showCancel = false)}>Cancel</Button>
|
||||
<Button secondary submit>Continue</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let showDelete = false;
|
||||
export let selectedDeployment: Models.Deployment = null;
|
||||
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await sdk.forProject.functions.deleteDeployment(
|
||||
selectedDeployment.resourceId,
|
||||
selectedDeployment.$id
|
||||
);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Deployment has been deleted`
|
||||
});
|
||||
trackEvent(Submit.DeploymentDelete);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.DeploymentDelete);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Delete deployment"
|
||||
bind:show={showDelete}
|
||||
onSubmit={handleSubmit}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
headerDivider={false}>
|
||||
<p data-private>Are you sure you want to delete this deployment?</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid } from '$lib/components';
|
||||
import { Container } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { page } from '$app/stores';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { deployment } from './store';
|
||||
|
||||
export let data;
|
||||
|
||||
let logs = '';
|
||||
|
||||
onMount(() => {
|
||||
logs = $deployment.buildLogs;
|
||||
if ($deployment.status === 'ready') {
|
||||
return;
|
||||
}
|
||||
return sdk.forConsole.client.subscribe<Models.Deployment>('console', (message) => {
|
||||
if (
|
||||
message.events.includes(
|
||||
`functions.${$page.params.site}.deployments.${$page.params.deployment}.update`
|
||||
)
|
||||
) {
|
||||
logs = message.payload['logs'];
|
||||
if (message.payload.status === 'ready') {
|
||||
invalidate(Dependencies.DEPLOYMENT);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<Container></Container>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load: PageLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.DEPLOYMENT);
|
||||
|
||||
const deployment = await sdk.forProject.sites.getDeployment(params.site, params.deployment);
|
||||
|
||||
return {
|
||||
deployment
|
||||
};
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export const deployment = derived(page, ($page) => $page.data.deployment as Models.Deployment);
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<script lang="ts">
|
||||
import { CardGrid, Id, SvgIcon } from '$lib/components';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { func, proxyRuleList } from './store';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { humanFileSize } from '$lib/helpers/sizeConvertion';
|
||||
import { calculateTime } from '$lib/helpers/timeConversion';
|
||||
import DeploymentCreatedBy from './deploymentCreatedBy.svelte';
|
||||
import DeploymentSource from './deploymentSource.svelte';
|
||||
|
||||
import DeploymentDomains from './deploymentDomains.svelte';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
|
||||
export let deployment: Models.Deployment;
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<div class="u-flex u-cross-start u-gap-16">
|
||||
<div class="avatar" style={`--p-image-size: ${32 / 16}rem`} aria-hidden="true">
|
||||
<SvgIcon size={64} iconSize="large" name={$func.runtime.split('-')[0]}></SvgIcon>
|
||||
</div>
|
||||
<div class="u-grid-equal-row-size u-gap-4 u-line-height-1">
|
||||
<p><b>Deployment ID</b></p>
|
||||
|
||||
<Id value={deployment.$id}>
|
||||
{deployment.$id}
|
||||
</Id>
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
{@const status = deployment.status}
|
||||
{@const deploymentSize = humanFileSize(deployment.size)}
|
||||
{@const buildSize = humanFileSize(deployment.buildSize)}
|
||||
{@const totalSize = humanFileSize(deployment.buildSize + deployment.size)}
|
||||
<ul class="stats-grid-box u-gap-16">
|
||||
<li class="u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Status</p>
|
||||
<span>
|
||||
<Pill
|
||||
danger={status === 'failed'}
|
||||
warning={status === 'building'}
|
||||
success={status === 'ready'}>
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text u-trim">
|
||||
{status === 'ready' ? 'active' : status}
|
||||
</span>
|
||||
</Pill>
|
||||
</span>
|
||||
</li>
|
||||
<li class="u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Build time</p>
|
||||
<p class="u-line-height-2">
|
||||
{calculateTime(deployment.buildTime)}
|
||||
</p>
|
||||
</li>
|
||||
<li class="u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Total size</p>
|
||||
<p class="u-line-height-2">
|
||||
{totalSize.value + totalSize.unit}
|
||||
<button
|
||||
type="button"
|
||||
on:click|preventDefault
|
||||
class="tooltip"
|
||||
aria-label="input tooltip"
|
||||
use:tooltip={{
|
||||
content: `
|
||||
<p><b>Deployment size:</b> ${deploymentSize.value + deploymentSize.unit}</p>
|
||||
<p><b>Build size:</b> ${buildSize.value + buildSize.unit}</p>
|
||||
`,
|
||||
allowHTML: true,
|
||||
appendTo: 'parent'
|
||||
}}>
|
||||
<span
|
||||
class="icon-info"
|
||||
aria-hidden="true"
|
||||
style="font-size: var(--icon-size-small)" />
|
||||
</button>
|
||||
</p>
|
||||
</li>
|
||||
<li class="u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Updated</p>
|
||||
<p class="u-line-height-2">
|
||||
<DeploymentCreatedBy {deployment} />
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Source</p>
|
||||
<div>
|
||||
<DeploymentSource {deployment} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if $proxyRuleList?.rules?.length}
|
||||
<div class="u-flex u-flex-vertical u-gap-4">
|
||||
<p class="u-color-text-offline">Domains</p>
|
||||
<DeploymentDomains domain={$proxyRuleList} />
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<slot name="actions" />
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@appwrite.io/pink/src/abstract/variables/_devices.scss';
|
||||
|
||||
.stats-grid-box {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media #{$break3open} {
|
||||
.stats-grid-box {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { timeFromNow } from '$lib/helpers/date';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let deployment: Models.Deployment;
|
||||
</script>
|
||||
|
||||
{timeFromNow(deployment.$updatedAt)}
|
||||
{#if deployment.providerCommitAuthor}
|
||||
by <a
|
||||
class="u-underline u-cursor-pointer"
|
||||
target="_blank"
|
||||
href={deployment.providerCommitAuthorUrl}>{deployment.providerCommitAuthor}</a>
|
||||
{/if}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { DropList, DropListLink, Trim } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let domain: Models.ProxyRuleList;
|
||||
let showDropdown = false;
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-gap-4 u-cross-center">
|
||||
<a
|
||||
href={`http://${domain.rules[0].domain}`}
|
||||
target="_blank"
|
||||
class="u-flex u-gap-4 u-cross-center">
|
||||
<Trim alternativeTrim>
|
||||
<span class="link">
|
||||
{domain.rules[0].domain}
|
||||
</span>
|
||||
</Trim>
|
||||
<span class="icon-external-link" aria-hidden="true" />
|
||||
</a>
|
||||
{#if domain.rules.length > 1}
|
||||
<DropList bind:show={showDropdown} scrollable>
|
||||
<Pill button on:click={() => (showDropdown = !showDropdown)}>
|
||||
+{domain.rules.length - 1}
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
{#each domain.rules as rule, i}
|
||||
{#if i !== 0}
|
||||
<DropListLink href={`http://${rule.domain}`} external icon="external-link">
|
||||
{rule.domain}
|
||||
</DropListLink>
|
||||
{/if}
|
||||
{/each}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/if}
|
||||
</div>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { DropList, DropListLink, Trim } from '$lib/components';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let deployment: Models.Deployment;
|
||||
let showDropdown = false;
|
||||
</script>
|
||||
|
||||
{#if deployment.type === 'vcs'}
|
||||
<DropList bind:show={showDropdown}>
|
||||
<button
|
||||
on:click={() => (showDropdown = !showDropdown)}
|
||||
type="button"
|
||||
class="u-flex u-gap-4 u-cross-center">
|
||||
<span class="icon-github" aria-hidden="true" /> <span class="link">GitHub</span>
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListLink href={deployment.providerRepositoryUrl} external icon="github">
|
||||
{deployment.providerRepositoryOwner}/{deployment.providerRepositoryName}
|
||||
</DropListLink>
|
||||
<DropListLink href={deployment.providerBranchUrl} external icon="git-branch">
|
||||
{deployment.providerBranch}
|
||||
</DropListLink>
|
||||
{#if deployment?.providerCommitMessage && deployment?.providerCommitHash && deployment?.providerCommitUrl}
|
||||
<DropListLink href={deployment.providerCommitUrl} external icon="git-commit">
|
||||
<Trim alternativeTrim>
|
||||
{deployment?.providerCommitHash?.substring(0, 7)}
|
||||
{deployment.providerCommitMessage}
|
||||
</Trim>
|
||||
</DropListLink>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else if deployment.type === 'manual'}
|
||||
<span class="icon-code" aria-hidden="true" /> <span>Manual</span>
|
||||
{:else if deployment.type === 'cli'}
|
||||
<span class="icon-terminal" aria-hidden="true" /> <span>CLI</span>
|
||||
{/if}
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { DropList, DropListItem } from '$lib/components';
|
||||
import {
|
||||
addFilter,
|
||||
queries,
|
||||
queryParamToMap,
|
||||
tagFormat,
|
||||
tags,
|
||||
ValidOperators,
|
||||
type TagValue
|
||||
} from '$lib/components/filters/store';
|
||||
import { Pill, SelectSearchCheckbox } from '$lib/elements';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { type Writable } from 'svelte/store';
|
||||
|
||||
export let columns: Writable<Column[]>;
|
||||
|
||||
type FilterData = {
|
||||
title: string;
|
||||
id: string;
|
||||
array: boolean;
|
||||
show: boolean;
|
||||
tag: string;
|
||||
operator: ValidOperators;
|
||||
options: { value: string; label: string; checked: boolean }[];
|
||||
};
|
||||
|
||||
function buildFilterCol(col: Column, customOperator = null): FilterData {
|
||||
return {
|
||||
title: col.title,
|
||||
id: col.id,
|
||||
show: false,
|
||||
array: col?.array,
|
||||
tag: null,
|
||||
operator: customOperator ?? ValidOperators.Equal,
|
||||
options: col?.elements?.map((element) => {
|
||||
return {
|
||||
value: (element?.value ?? element) as string,
|
||||
label: (element?.label ?? element) as string,
|
||||
checked: false
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
const statusCol = $columns.find((col) => col.id === 'status');
|
||||
let statusFilter = buildFilterCol(statusCol);
|
||||
const typeCol = $columns.find((col) => col.id === 'type');
|
||||
let typeFilter = buildFilterCol(typeCol);
|
||||
const sizeCol = $columns.find((col) => col.id === 'size');
|
||||
let sizeFilter = buildFilterCol(sizeCol);
|
||||
const buildTimeCol = $columns.find((col) => col.id === 'buildTime');
|
||||
let buildTimeFilter = buildFilterCol(buildTimeCol);
|
||||
|
||||
let localQueries = new Map<TagValue, string>();
|
||||
|
||||
afterNavigate((p) => {
|
||||
const paramQueries = p.to.url.searchParams.get('query');
|
||||
localQueries = queryParamToMap(paramQueries || '[]');
|
||||
const localTags = Array.from(localQueries.keys());
|
||||
|
||||
if (!localTags?.length) {
|
||||
statusFilter.tag = null;
|
||||
typeFilter.tag = null;
|
||||
buildTimeFilter.tag = null;
|
||||
[statusFilter, typeFilter].forEach((filter) => {
|
||||
resetOptions(filter);
|
||||
});
|
||||
} else {
|
||||
[statusFilter, typeFilter].forEach((filter) => {
|
||||
setFilterData(filter, localTags);
|
||||
});
|
||||
|
||||
const buildTimeTag = localTags.find((tag) =>
|
||||
tag.tag.includes(`**${buildTimeFilter.title}**`)
|
||||
);
|
||||
if (buildTimeTag) {
|
||||
const now = new Date();
|
||||
|
||||
const diff = now.getTime() - new Date(buildTimeTag.value as string).getTime();
|
||||
const ranges = buildTimeCol.elements as { value: string; label: string }[];
|
||||
const dateRange = ranges.reduce((prev, curr) => {
|
||||
if (parseInt(curr.value) < diff && curr.value > prev.value) {
|
||||
return curr;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
if (dateRange) {
|
||||
buildTimeFilter.tag = `**${buildTimeFilter.title}** is **${dateRange.label}**`;
|
||||
buildTimeFilter = buildTimeFilter;
|
||||
}
|
||||
} else {
|
||||
buildTimeFilter.tag = null;
|
||||
}
|
||||
|
||||
const sizeTag = localTags.find((tag) => tag.tag.includes(`**${sizeFilter.title}**`));
|
||||
if (sizeTag) {
|
||||
const size = sizeTag.value as string;
|
||||
const ranges = sizeCol.elements as { value: string; label: string }[];
|
||||
// find smallest range that is bigger than size
|
||||
const sizeRange = ranges.reduce((prev, curr) => {
|
||||
if (parseInt(size) >= parseInt(curr.value)) {
|
||||
return curr;
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
if (sizeRange) {
|
||||
sizeFilter.tag = `**${sizeFilter.title}** is **${sizeRange.label}**`;
|
||||
sizeFilter = sizeFilter;
|
||||
}
|
||||
} else {
|
||||
sizeFilter.tag = null;
|
||||
}
|
||||
|
||||
// Reasinging the filters to trigger reactivity
|
||||
statusFilter = statusFilter;
|
||||
typeFilter = typeFilter;
|
||||
sizeFilter = sizeFilter;
|
||||
buildTimeFilter = buildTimeFilter;
|
||||
}
|
||||
});
|
||||
|
||||
function setFilterData(filter: FilterData, list: TagValue[]) {
|
||||
const tagData = list.find((tag) => tag.tag.includes(`**${filter.title}**`));
|
||||
if (tagData) {
|
||||
filter.tag = tagData.tag;
|
||||
if (Array.isArray(tagData.value) && tagData.value?.length) {
|
||||
const values = tagData.value as string[];
|
||||
filter.options.forEach((option) => {
|
||||
option.checked = values.includes(option.value);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
filter.tag = null;
|
||||
resetOptions(filter);
|
||||
}
|
||||
}
|
||||
|
||||
function resetOptions(filter: FilterData) {
|
||||
filter.options.forEach((option) => {
|
||||
option.checked = false;
|
||||
});
|
||||
}
|
||||
|
||||
function addFilterAndApply(
|
||||
colId: string,
|
||||
colTitle: string,
|
||||
operator: ValidOperators,
|
||||
value: string,
|
||||
arrayValues: string[] = []
|
||||
) {
|
||||
const tagList = $tags.filter((tag) => tag.tag.includes(colTitle));
|
||||
tagList.forEach((tag) => queries.removeFilter(tag));
|
||||
if (value || arrayValues?.length) {
|
||||
if (colId === buildTimeFilter.id) {
|
||||
addBuildTimeFilter(value, colId);
|
||||
} else if (colId === sizeFilter.id) {
|
||||
addSizeFilter(value, colId);
|
||||
} else {
|
||||
addFilter($columns, colId, operator, value, arrayValues);
|
||||
}
|
||||
}
|
||||
queries.apply();
|
||||
trackEvent(Submit.ApplyQuickFilter, {
|
||||
source: 'function_deployments',
|
||||
column: colId,
|
||||
value: value || arrayValues.join(', ')
|
||||
});
|
||||
}
|
||||
|
||||
function addSizeFilter(value: string, colId: string) {
|
||||
addFilter($columns, colId, ValidOperators.GreaterThanOrEqual, value);
|
||||
}
|
||||
function addBuildTimeFilter(value: string, colId: string) {
|
||||
const now = new Date();
|
||||
const isoValue = new Date(now.getTime() - parseInt(value));
|
||||
addFilter($columns, colId, ValidOperators.GreaterThanOrEqual, isoValue.toISOString());
|
||||
addFilter($columns, colId, ValidOperators.LessThanOrEqual, now.toISOString());
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each [typeFilter] as filter}
|
||||
<DropList bind:show={filter.show} noArrow class="u-margin-block-start-16">
|
||||
<Pill
|
||||
button
|
||||
on:click={() => (filter.show = !filter.show)}
|
||||
event="apply_quick_filter"
|
||||
eventData={{ source: 'function_deployments', column: filter.title }}>
|
||||
{#key filter.tag}
|
||||
<span use:tagFormat>
|
||||
{filter?.tag ?? filter.title}
|
||||
</span>
|
||||
{/key}
|
||||
<span class:icon-cheveron-down={!filter.show} class:icon-cheveron-up={filter.show}>
|
||||
</span>
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
{#each filter.options as option (option.value + option.checked)}
|
||||
<SelectSearchCheckbox
|
||||
padding={8}
|
||||
bind:value={option.checked}
|
||||
on:click={() => {
|
||||
option.checked = !option.checked;
|
||||
addFilterAndApply(
|
||||
filter.id,
|
||||
filter.title,
|
||||
filter.operator,
|
||||
filter?.array ? null : option.checked ? option.value : null,
|
||||
filter?.array
|
||||
? (filter.options
|
||||
.filter((opt) => opt.checked)
|
||||
.map((opt) => opt.value) ?? [])
|
||||
: []
|
||||
);
|
||||
}}>
|
||||
{option.label}
|
||||
</SelectSearchCheckbox>
|
||||
{/each}
|
||||
{#if filter.options.some((option) => option.checked)}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
filter.tag = null;
|
||||
addFilterAndApply(filter.id, filter.title, filter.operator, null, []);
|
||||
}}>
|
||||
Clear selection
|
||||
</DropListItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/each}
|
||||
{#each [sizeFilter] as filter}
|
||||
<DropList bind:show={filter.show} noArrow class="u-margin-block-start-16">
|
||||
<Pill
|
||||
button
|
||||
on:click={() => (filter.show = !filter.show)}
|
||||
event="apply_quick_filter"
|
||||
eventData={{ source: 'function_deployments', column: filter.title }}>
|
||||
{#key filter.tag}
|
||||
<span use:tagFormat>
|
||||
{filter?.tag ?? filter.title}
|
||||
</span>
|
||||
{/key}
|
||||
<span class:icon-cheveron-down={!filter.show} class:icon-cheveron-up={filter.show}>
|
||||
</span>
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
{#each filter.options as option (option.value + option.checked)}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
|
||||
addFilterAndApply(
|
||||
filter.id,
|
||||
filter.title,
|
||||
filter.operator,
|
||||
filter?.array ? null : option.value,
|
||||
[]
|
||||
);
|
||||
}}>
|
||||
{option.label}
|
||||
</DropListItem>
|
||||
{/each}
|
||||
{#if filter?.tag}
|
||||
<DropListItem
|
||||
padding={8}
|
||||
on:click={() => {
|
||||
filter.show = false;
|
||||
filter.tag = null;
|
||||
addFilterAndApply(filter.id, filter.title, filter.operator, null, []);
|
||||
}}>
|
||||
Clear selection
|
||||
</DropListItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/each}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { Modal } from '$lib/components';
|
||||
import { func } from './store';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let show = false;
|
||||
export let selectedDeployment: Models.Deployment = null;
|
||||
let error: string;
|
||||
|
||||
async function redeploy() {
|
||||
try {
|
||||
await sdk.forProject.functions.createBuild(
|
||||
$func.$id,
|
||||
selectedDeployment.$id,
|
||||
selectedDeployment.buildId
|
||||
);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `Redeploying ${$func.name}`
|
||||
});
|
||||
trackEvent(Submit.FunctionRedeploy);
|
||||
|
||||
invalidate(Dependencies.FUNCTION);
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
show = false;
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
|
||||
trackError(e, Submit.FunctionRedeploy);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Redeploy function"
|
||||
size="big"
|
||||
bind:show
|
||||
bind:error
|
||||
onSubmit={redeploy}
|
||||
headerDivider={false}>
|
||||
<p class="text">
|
||||
Are you sure you want to redeploy <b>{$func.name}</b>? Redeploying may affect your
|
||||
production code.
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (show = false)}>Cancel</Button>
|
||||
<Button submit>Redeploy</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { page } from '$app/stores';
|
||||
import { derived, writable, type Writable } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export const func = derived(page, ($page) => $page.data.function as Models.Function);
|
||||
export const deploymentList = derived(
|
||||
page,
|
||||
($page) => $page.data.deploymentList as Models.DeploymentList
|
||||
);
|
||||
export const proxyRuleList = derived(
|
||||
page,
|
||||
($page) => $page.data.proxyRuleList as Models.ProxyRuleList
|
||||
);
|
||||
|
||||
export const repositories: Writable<{
|
||||
search: string;
|
||||
installationId: string;
|
||||
repositories: Models.ProviderRepository[];
|
||||
}> = writable({
|
||||
search: '',
|
||||
installationId: '',
|
||||
repositories: []
|
||||
});
|
||||
|
||||
export const showCreateDeployment: Writable<boolean> = writable(false);
|
||||
@@ -0,0 +1,196 @@
|
||||
<script lang="ts">
|
||||
import { DropList, DropListItem, DropListLink, Id } from '$lib/components';
|
||||
import {
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellHead,
|
||||
TableCellText,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
TableScroll
|
||||
} from '$lib/elements/table';
|
||||
import type { PageData } from './$types';
|
||||
import { type Models } from '@appwrite.io/console';
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { calculateTime } from '$lib/helpers/timeConversion';
|
||||
import DeploymentSource from './deploymentSource.svelte';
|
||||
import DeploymentCreatedBy from './deploymentCreatedBy.svelte';
|
||||
import { timer } from '$lib/actions/timer';
|
||||
import { calculateSize } from '$lib/helpers/sizeConvertion';
|
||||
import { func } from './store';
|
||||
import { page } from '$app/stores';
|
||||
import Delete from './delete.svelte';
|
||||
import RedeployModal from './redeployModal.svelte';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Cancel from './cancel.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
export let columns: Column[];
|
||||
export let data: PageData;
|
||||
|
||||
let showDropdown = [];
|
||||
let showDelete = false;
|
||||
let showActivate = false;
|
||||
let showRedeploy = false;
|
||||
let showCancel = false;
|
||||
|
||||
let selectedDeployment: Models.Deployment = null;
|
||||
|
||||
function handleActivate() {
|
||||
invalidate(Dependencies.DEPLOYMENTS);
|
||||
}
|
||||
|
||||
function getDownload(deploymentId: string) {
|
||||
return (
|
||||
sdk.forProject.functions.getDeploymentDownload($func.$id, deploymentId).toString() +
|
||||
'&mode=admin'
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<TableScroll noMargin>
|
||||
<TableHeader>
|
||||
{#each columns as column}
|
||||
{#if column.show}
|
||||
<TableCellHead width={column.width}>{column.title}</TableCellHead>
|
||||
{/if}
|
||||
{/each}
|
||||
<TableCellHead width={40} />
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#each data.deploymentList.deployments as deployment, index (deployment.$id)}
|
||||
<TableRow>
|
||||
{#each columns as column}
|
||||
{#if column.show}
|
||||
{#if column.id === '$id'}
|
||||
{#key column.id}
|
||||
<TableCell width={column.width} title="Deployment ID">
|
||||
<Id value={deployment.$id}>{deployment.$id}</Id>
|
||||
</TableCell>
|
||||
{/key}
|
||||
{:else if column.id === 'status'}
|
||||
<TableCell width={column.width} title={column.title}>
|
||||
{@const status = deployment.status}
|
||||
{#if data?.activeDeployment?.$id === deployment?.$id}
|
||||
<Pill success>
|
||||
<span class="icon-lightning-bolt" aria-hidden="true" />
|
||||
<span class="text u-trim">active</span>
|
||||
</Pill>
|
||||
{:else}
|
||||
<Pill
|
||||
danger={status === 'failed'}
|
||||
warning={status === 'building'}
|
||||
info={status === 'ready'}>
|
||||
{status}
|
||||
</Pill>
|
||||
{/if}
|
||||
</TableCell>
|
||||
{:else if column.id === 'type'}
|
||||
<TableCell width={column.width} title={column.title}>
|
||||
<DeploymentSource {deployment} />
|
||||
</TableCell>
|
||||
{:else if column.id === '$updatedAt'}
|
||||
<TableCellText width={column.width} title={column.title}>
|
||||
<DeploymentCreatedBy {deployment} />
|
||||
</TableCellText>
|
||||
{:else if column.id === 'buildTime'}
|
||||
<TableCellText width={column.width} title={column.title}>
|
||||
{#if ['processing', 'building'].includes(deployment.status)}
|
||||
<span use:timer={{ start: deployment.$createdAt }} />
|
||||
{:else}
|
||||
{calculateTime(deployment.buildTime)}
|
||||
{/if}
|
||||
</TableCellText>
|
||||
{:else if column.id === 'size'}
|
||||
<TableCellText width={column.width} title={column.title}>
|
||||
{calculateSize(deployment.size)}
|
||||
</TableCellText>
|
||||
{:else if column.id === 'buildSize'}
|
||||
<TableCellText width={column.width} title={column.title}>
|
||||
{calculateSize(deployment.buildSize)}
|
||||
</TableCellText>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
<TableCell width={40} showOverflow>
|
||||
<DropList bind:show={showDropdown[index]} placement="bottom-start" noArrow>
|
||||
<button
|
||||
class="button is-only-icon is-text"
|
||||
aria-label="More options"
|
||||
on:click|preventDefault={() => {
|
||||
showDropdown[index] = !showDropdown[index];
|
||||
}}>
|
||||
<span class="icon-dots-horizontal" aria-hidden="true" />
|
||||
</button>
|
||||
<svelte:fragment slot="list">
|
||||
<DropListItem
|
||||
icon="refresh"
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showRedeploy = true;
|
||||
showDropdown = [];
|
||||
}}>
|
||||
Redeploy
|
||||
</DropListItem>
|
||||
{#if deployment.status === 'ready' && deployment.$id !== $func.deployment}
|
||||
<DropListItem
|
||||
icon="lightning-bolt"
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showActivate = true;
|
||||
showDropdown = [];
|
||||
}}>
|
||||
Activate
|
||||
</DropListItem>
|
||||
{/if}
|
||||
<DropListLink
|
||||
icon="terminal"
|
||||
href={`${base}/project-${$page.params.project}/functions/function-${$page.params.function}/deployment-${deployment.$id}`}>
|
||||
Logs
|
||||
</DropListLink>
|
||||
<DropListLink
|
||||
icon="download"
|
||||
href={getDownload(deployment.$id)}
|
||||
on:click={() => (showDropdown[index] = false)}>
|
||||
Download
|
||||
</DropListLink>
|
||||
{#if deployment.status === 'processing' || deployment.status === 'building' || deployment.status === 'waiting'}
|
||||
<DropListItem
|
||||
icon="x-circle"
|
||||
event="deployment_cancel"
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showDropdown = [];
|
||||
showCancel = true;
|
||||
}}>
|
||||
Cancel
|
||||
</DropListItem>
|
||||
{/if}
|
||||
{#if deployment.status !== 'building' && deployment.status !== 'processing' && deployment.status !== 'waiting'}
|
||||
<DropListItem
|
||||
icon="trash"
|
||||
on:click={() => {
|
||||
selectedDeployment = deployment;
|
||||
showDropdown = [];
|
||||
showDelete = true;
|
||||
}}>
|
||||
Delete
|
||||
</DropListItem>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</TableScroll>
|
||||
|
||||
{#if selectedDeployment}
|
||||
<Delete {selectedDeployment} bind:showDelete />
|
||||
<!-- <Activate {selectedDeployment} bind:showActivate on:activated={handleActivate} /> -->
|
||||
<Cancel {selectedDeployment} bind:showCancel />
|
||||
<RedeployModal {selectedDeployment} bind:show={showRedeploy} />
|
||||
{/if}
|
||||
Reference in New Issue
Block a user