Merge branch 'main' into feat-project-labels

This commit is contained in:
Matej Bačo
2026-01-07 15:38:04 +01:00
8 changed files with 193 additions and 84 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ export const oAuthProviders: Record<string, Provider> = {
apple: {
name: 'Apple',
icon: 'apple',
docs: 'https://developer.apple.com/',
docs: 'https://developer.apple.com/sign-in-with-apple/',
component: Apple
},
auth0: {
+64 -2
View File
@@ -1,4 +1,4 @@
import { Client, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { Client, Functions, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import { getApiEndpoint } from '$lib/stores/sdk';
import { page } from '$app/state';
@@ -32,6 +32,13 @@ const temporarySites = (region: string, projectId: string) => {
return new Sites(clientProject);
};
const temporaryFunctions = (region: string, projectId: string) => {
const clientProject = new Client().setMode('admin');
const endpoint = getApiEndpoint(region);
clientProject.setEndpoint(endpoint).setProject(projectId);
return new Functions(clientProject);
};
const createUploader = () => {
const { subscribe, set, update } = writable<Uploader>({
isOpen: false,
@@ -110,7 +117,19 @@ const createUploader = () => {
newFile.status = 'success';
updateFile(newFile.$id, newFile);
},
uploadSiteDeployment: async (siteId: string, code: File) => {
uploadSiteDeployment: async ({
siteId,
code,
buildCommand,
installCommand,
outputDirectory
}: {
siteId: string;
code: File;
buildCommand?: string;
installCommand?: string;
outputDirectory?: string;
}) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: siteId,
@@ -132,6 +151,49 @@ const createUploader = () => {
siteId,
code,
activate: true,
buildCommand,
installCommand,
outputDirectory,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
newDeployment.status = progress.progress === 100 ? 'success' : 'pending';
updateFile(progress.$id, newDeployment);
}
});
newDeployment.$id = uploadedFile.$id;
newDeployment.progress = 100;
newDeployment.status = 'success';
updateFile(newDeployment.$id, newDeployment);
},
uploadFunctionDeployment: async ({
functionId,
code
}: {
functionId: string;
code: File;
}) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: functionId,
name: code.name,
size: code.size,
progress: 0,
status: 'pending'
};
update((n) => {
n.isOpen = true;
n.isCollapsed = false;
n.files.unshift(newDeployment);
return n;
});
const uploadedFile = await temporaryFunctions(
page.params.region,
page.params.project
).createDeployment({
functionId,
code,
activate: true,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
@@ -1,4 +1,5 @@
import { Query } from '@appwrite.io/console';
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
import { CARD_LIMIT, Dependencies } from '$lib/constants';
@@ -23,54 +24,56 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const archivedPage =
Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
const archivedOffset = pageToOffset(archivedPage, limit);
const searchQueries = search
? [Query.or([Query.search('search', search), Query.contains('labels', search)])]
? [Query.or([Query.search('search', search), Query.contains('labels', search)])];
const commonQueries = [Query.equal('teamId', params.organization)];
const activeQueries = isCloud
? [Query.or([Query.equal('status', 'active'), Query.isNull('status')])]
: [];
const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
sdk.forConsole.projects.list({
queries: [
Query.offset(offset),
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
Query.limit(limit),
Query.orderDesc(''),
...searchQueries
...commonQueries,
...searchQueries,
...activeQueries
]
}),
isCloud
? sdk.forConsole.projects.list({
queries: [
Query.offset(archivedOffset),
Query.limit(limit),
Query.orderDesc(''),
...commonQueries,
...searchQueries,
Query.equal('status', 'archived')
]
})
: Promise.resolve({ projects: [], total: 0 }),
sdk.forConsole.projects.list({
queries: [
Query.offset(archivedOffset),
Query.equal('teamId', params.organization),
Query.equal('status', 'archived'),
Query.limit(limit),
Query.orderDesc(''),
...searchQueries
]
queries: [...commonQueries, ...activeQueries, ...searchQueries]
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
Query.or([Query.equal('status', 'active'), Query.isNull('status')]),
...searchQueries
]
}),
sdk.forConsole.projects.list({
queries: [
Query.equal('teamId', params.organization),
Query.equal('status', 'archived'),
...searchQueries
]
})
isCloud
? sdk.forConsole.projects.list({
queries: [...commonQueries, ...searchQueries, Query.equal('status', 'archived')],
search: search || undefined
})
: Promise.resolve({ projects: [], total: 0 })
]);
// set `default` if no region!
for (const project of activeProjects.projects) {
project.region ??= 'default';
}
for (const project of archivedProjects.projects) {
project.region ??= 'default';
if (isCloud) {
for (const project of archivedProjects.projects) {
project.region ??= 'default';
}
}
return {
@@ -124,7 +124,7 @@
<style>
.layout-level-progress-bars {
gap: 1rem;
z-index: 2;
z-index: 100;
display: flex;
flex-direction: column;
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto, invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { base, resolve } from '$app/paths';
import { page } from '$app/state';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Button, Form } from '$lib/elements/forms';
@@ -23,6 +23,7 @@
import { isCloud } from '$lib/system';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { currentPlan } from '$lib/stores/organization';
import { uploader } from '$lib/stores/uploader';
export let data;
@@ -61,19 +62,19 @@
let specification = specificationOptions[0]?.value || '';
async function create() {
let func: Models.Function | null = null;
try {
const func = await sdk
.forProject(page.params.region, page.params.project)
.functions.create({
functionId: id || ID.unique(),
name,
runtime,
execute: roles?.length ? roles : undefined,
enabled: true,
entrypoint,
commands: buildCommand,
specification: specification || undefined
});
func = await sdk.forProject(page.params.region, page.params.project).functions.create({
functionId: id || ID.unique(),
name,
runtime,
execute: roles?.length ? roles : undefined,
enabled: true,
entrypoint,
commands: buildCommand,
specification: specification || undefined
});
// Add domain
await sdk.forProject(page.params.region, page.params.project).proxy.createFunctionRule({
@@ -92,25 +93,43 @@
);
await Promise.all(promises);
await sdk
.forProject(page.params.region, page.params.project)
.functions.createDeployment({
functionId: func.$id,
code: files[0],
activate: true
});
const promise = uploader.uploadFunctionDeployment({
functionId: func.$id,
code: files[0]
});
addNotification({
message: 'Deployment upload started',
type: 'success'
});
trackEvent(Submit.FunctionCreate, {
source: 'repository',
runtime: runtime
});
await goto(
`${base}/project-${page.params.region}-${page.params.project}/functions/function-${func.$id}`
);
await promise;
const upload = $uploader.files.find((f) => f.resourceId === func.$id);
if (upload?.status === 'success') {
const deploymentId = upload.$id;
const resolvedPath = resolve(
`/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]`,
{
region: page.params.region,
project: page.params.project,
function: func.$id,
deployment: deploymentId
}
);
await goto(resolvedPath);
}
invalidate(Dependencies.FUNCTION);
} catch (e) {
const upload = $uploader.files.find((f) => f.resourceId === func?.$id);
if (upload) {
uploader.removeFromQueue(upload.$id);
}
addNotification({
type: 'error',
message: e.message
@@ -6,15 +6,14 @@
import { Button } from '$lib/elements/forms';
import { InvalidFileType, removeFile } from '$lib/helpers/files';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte';
import { func } from '../store';
import { page } from '$app/state';
import { consoleVariables } from '$routes/(console)/store';
import { currentPlan } from '$lib/stores/organization';
import { isCloud } from '$lib/system';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { uploader } from '$lib/stores/uploader';
export let show = false;
@@ -30,17 +29,16 @@
async function create() {
try {
await sdk
.forProject(page.params.region, page.params.project)
.functions.createDeployment({
functionId: $func.$id,
code: files[0],
activate: true
});
await invalidate(Dependencies.DEPLOYMENTS);
files = undefined;
uploader.uploadFunctionDeployment({
functionId: $func.$id,
code: files[0]
});
show = false;
trackEvent(Submit.DeploymentCreate);
files = undefined;
await invalidate(Dependencies.DEPLOYMENTS);
trackEvent(Submit.DeploymentCreate, {
type: 'function'
});
addNotification({
type: 'success',
message: 'Deployment created successfully'
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { base, resolve } from '$app/paths';
import { page } from '$app/state';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Button, Form } from '$lib/elements/forms';
@@ -21,6 +21,7 @@
import { isCloud } from '$lib/system';
import { currentPlan } from '$lib/stores/organization';
import Domain from '../domain.svelte';
import { uploader } from '$lib/stores/uploader';
export let data;
let showExitModal = false;
@@ -50,6 +51,8 @@
$: readableMaxSize = humanFileSize(maxSize);
async function create() {
let site: Models.Site | null = null;
try {
if (!domainIsValid) {
addNotification({
@@ -63,7 +66,7 @@
const buildRuntime = Object.values(BuildRuntime).find(
(f) => f === framework.buildRuntime
);
let site = await sdk.forProject(page.params.region, page.params.project).sites.create({
site = await sdk.forProject(page.params.region, page.params.project).sites.create({
siteId: id || ID.unique(),
name,
framework: fr,
@@ -90,26 +93,42 @@
);
await Promise.all(promises);
const deployment = await sdk
.forProject(page.params.region, page.params.project)
.sites.createDeployment({
siteId: site.$id,
code: files[0],
activate: true,
installCommand: installCommand || undefined,
buildCommand: buildCommand || undefined,
outputDirectory: outputDirectory || undefined
});
const promise = uploader.uploadSiteDeployment({
siteId: site.$id,
code: files[0],
buildCommand: buildCommand || undefined,
installCommand: installCommand || undefined,
outputDirectory: outputDirectory || undefined
});
addNotification({
message: 'Deployment upload started',
type: 'success'
});
trackEvent(Submit.SiteCreate, {
source: 'manual',
framework: framework.key
});
await goto(
`${base}/project-${page.params.region}-${page.params.project}/sites/create-site/deploying?site=${site.$id}&deployment=${deployment.$id}`
);
await promise;
const upload = $uploader.files.find((f) => f.resourceId === site.$id);
if (upload?.status === 'success') {
const deploymentId = upload.$id;
const resolvedPath = resolve(
`/(console)/project-[region]-[project]/sites/create-site/deploying`,
{
region: page.params.region,
project: page.params.project
}
);
await goto(`${resolvedPath}?site=${site.$id}&deployment=${deploymentId}`);
}
} catch (e) {
const upload = $uploader.files.find((f) => f.resourceId === site?.$id);
if (upload) {
uploader.removeFromQueue(upload.$id);
}
addNotification({
type: 'error',
message: e.message
@@ -13,6 +13,7 @@
import { currentPlan } from '$lib/stores/organization';
import { consoleVariables } from '$routes/(console)/store';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
export let show = false;
export let site: Models.Site;
@@ -29,15 +30,22 @@
async function createDeployment() {
try {
uploader.uploadSiteDeployment(site.$id, files[0]);
uploader.uploadSiteDeployment({
siteId: site.$id,
code: files[0]
});
show = false;
invalidate(Dependencies.DEPLOYMENTS);
trackEvent(Submit.DeploymentCreate, {
type: 'site'
});
addNotification({
message: 'Deployment upload started',
type: 'success'
});
} catch (e) {
error = e.message;
trackError(e, Submit.DeploymentCreate);
}
}