Detailed function support, create tag still broken

This commit is contained in:
alexweininger 2021-05-29 09:32:57 -05:00
parent d57c1e9696
commit 83091e74bb
44 changed files with 1101 additions and 80 deletions

80
package-lock.json generated
View file

@ -88,6 +88,14 @@
} }
} }
}, },
"@babel/runtime": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz",
"integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
},
"@discoveryjs/json-ext": { "@discoveryjs/json-ext": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz",
@ -211,6 +219,11 @@
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
"dev": true "dev": true
}, },
"@types/lodash": {
"version": "4.14.170",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.170.tgz",
"integrity": "sha512-bpcvu/MKHHeYX+qeEN8GE7DIravODWdACVA1ctevD8CN24RhPZIKMn9ntfAsrvLfSX3cR5RrBKAbYm9bGs0A+Q=="
},
"@types/minimatch": { "@types/minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz",
@ -928,6 +941,19 @@
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"dev": true "dev": true
}, },
"cron-validate": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/cron-validate/-/cron-validate-1.4.3.tgz",
"integrity": "sha512-N+qKw019oQBEPIP5Qwi8Z5XelQ00ThN6Maahwv+9UGu2u/b/MPb35zngMQI0T8pBoNiBrIXGlhvsmspNSYae/w==",
"requires": {
"yup": "0.32.9"
}
},
"cronstrue": {
"version": "1.113.0",
"resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-1.113.0.tgz",
"integrity": "sha512-j0+CQsQx0g0Iv6nQs0bHkLcpeCzYShWUdQ3QwSHV+dUyTLqI/3NPrHceeDfTXmC3Re4osMli5+wAYpffNO+e9w=="
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -1421,9 +1447,9 @@
"dev": true "dev": true
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.13.3", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg=="
}, },
"form-data": { "form-data": {
"version": "4.0.0", "version": "4.0.0",
@ -1950,8 +1976,12 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
"dev": true },
"lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
}, },
"lodash.clonedeep": { "lodash.clonedeep": {
"version": "4.5.0", "version": "4.5.0",
@ -2191,6 +2221,11 @@
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
"dev": true "dev": true
}, },
"nanoclone": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz",
"integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA=="
},
"nanoid": { "nanoid": {
"version": "3.1.20", "version": "3.1.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz",
@ -2210,9 +2245,9 @@
"dev": true "dev": true
}, },
"node-appwrite": { "node-appwrite": {
"version": "2.2.1", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-2.2.1.tgz", "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-2.2.3.tgz",
"integrity": "sha512-YbdcJJo4GD3v2rwChUz7uEMJQyzV6fbGdjLj2eshsK0ynqK76GB5M513Qs5E8cid50i4KFbFL9B1uV8oaQ/PAQ==", "integrity": "sha512-2j7AIKUxbjN25QrqZfMBRuWVRYlB5fixmW0HF/XP5QnrttCfozjPa5wWrgVRrJLYCoqwe2wwgWc9S3fyZeP/0g==",
"requires": { "requires": {
"axios": "^0.21.1", "axios": "^0.21.1",
"form-data": "^4.0.0" "form-data": "^4.0.0"
@ -2481,6 +2516,11 @@
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"dev": true "dev": true
}, },
"property-expr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
"integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
},
"prr": { "prr": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
@ -2567,6 +2607,11 @@
"resolve": "^1.9.0" "resolve": "^1.9.0"
} }
}, },
"regenerator-runtime": {
"version": "0.13.7",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
"integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew=="
},
"regexpp": { "regexpp": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
@ -2947,6 +2992,11 @@
"is-number": "^7.0.0" "is-number": "^7.0.0"
} }
}, },
"toposort": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
"integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
},
"traverse": { "traverse": {
"version": "0.3.9", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
@ -3460,6 +3510,20 @@
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true "dev": true
},
"yup": {
"version": "0.32.9",
"resolved": "https://registry.npmjs.org/yup/-/yup-0.32.9.tgz",
"integrity": "sha512-Ci1qN+i2H0XpY7syDQ0k5zKQ/DoxO0LzPg8PAR/X4Mpj6DqaeCoIYEEjDJwhArh3Fa7GWbQQVDZKeXYlSH4JMg==",
"requires": {
"@babel/runtime": "^7.10.5",
"@types/lodash": "^4.14.165",
"lodash": "^4.17.20",
"lodash-es": "^4.17.15",
"nanoclone": "^0.2.1",
"property-expr": "^2.0.4",
"toposort": "^2.0.2"
}
} }
} }
} }

View file

@ -186,6 +186,11 @@
"title": "Refresh projects", "title": "Refresh projects",
"icon": "$(refresh)" "icon": "$(refresh)"
}, },
{
"command": "vscode-appwrite.refreshFunctions",
"title": "Refresh functions",
"icon": "$(refresh)"
},
{ {
"command": "vscode-appwrite.removeProject", "command": "vscode-appwrite.removeProject",
"title": "Remove project", "title": "Remove project",
@ -196,10 +201,84 @@
"title": "Create function tag", "title": "Create function tag",
"icon": "$(cloud-upload)" "icon": "$(cloud-upload)"
}, },
{
"command": "vscode-appwrite.deleteTag",
"title": "Delete tag",
"icon": "$(trash)"
},
{ {
"command": "vscode-appwrite.CreateExecution", "command": "vscode-appwrite.CreateExecution",
"title": "Execute function", "title": "Execute"
"icon": "$(play)" },
{
"command": "vscode-appwrite.activateTag",
"title": "Activate"
},
{
"command": "vscode-appwrite.editValue",
"title": "Edit",
"icon": "$(edit)"
},
{
"command": "vscode-appwrite.deleteFunction",
"title": "Delete",
"icon": "$(trash)"
},
{
"command": "vscode-appwrite.openFunctionsDocumentation",
"title": "Open functions documentation",
"icon": "$(book)"
},
{
"command": "vscode-appwrite.createFunction",
"title": "Create function",
"icon": "$(add)"
},
{
"command": "vscode-appwrite.createFunctionVar",
"title": "Create variable",
"icon": "$(add)"
},
{
"command": "vscode-appwrite.deleteFunctionVar",
"title": "Delete variable",
"icon": "$(trash)"
},
{
"command": "vscode-appwrite.viewExecutionOutput",
"title": "View execution stdout",
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/"
},
{
"command": "vscode-appwrite.viewExecutionErrors",
"title": "View execution stderr",
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/"
},
{
"command": "vscode-appwrite.copyExecutionOutput",
"title": "Copy execution stdout",
"enablement": "viewItem =~ /^((execution|execution_outputOnly))$/"
},
{
"command": "vscode-appwrite.copyExecutionErrors",
"title": "Copy execution stderr",
"enablement": "viewItem =~ /^((execution|execution_errorOnly))$/"
},
{
"command": "vscode-appwrite.openExecutionsInBrowser",
"title": "View executions in browser",
"enablement": "viewItem =~ /^(executions)$/",
"icon": "$(link-external)"
},
{
"command": "vscode-appwrite.openFunctionTagsInBrowser",
"title": "Open function tags in browser",
"icon": "$(link-external)"
},
{
"command": "vscode-appwrite.openFunctionSettingsInBrowser",
"title": "Open function settings in browser",
"icon": "$(link-external)"
} }
], ],
"views": { "views": {
@ -292,14 +371,24 @@
"when": "view == Storage", "when": "view == Storage",
"group": "navigation" "group": "navigation"
}, },
{
"command": "vscode-appwrite.openFunctionsDocumentation",
"when": "view == Functions",
"group": "navigation"
},
{ {
"command": "vscode-appwrite.refreshProjects", "command": "vscode-appwrite.refreshProjects",
"when": "view == Projects", "when": "view == Projects",
"group": "navigation" "group": "navigation"
}, },
{ {
"command": "vscode-appwrite.addProject", "command": "vscode-appwrite.refreshFunctions",
"when": "view == Projects", "when": "view == Functions",
"group": "navigation"
},
{
"command": "vscode-appwrite.createFunction",
"when": "view == Functions",
"group": "navigation" "group": "navigation"
} }
], ],
@ -402,7 +491,69 @@
}, },
{ {
"command": "vscode-appwrite.CreateExecution", "command": "vscode-appwrite.CreateExecution",
"when": "viewItem =~ /(function)/", "when": "viewItem =~ /^(function)$/",
"group": "inline"
},
{
"command": "vscode-appwrite.activateTag",
"when": "viewItem =~ /^(tag)$/",
"group": "inline"
},
{
"command": "vscode-appwrite.editValue",
"when": "viewItem =~ /^(editable)/",
"group": "inline"
},
{
"command": "vscode-appwrite.deleteFunction",
"when": "viewItem =~ /^(function)$/"
},
{
"command": "vscode-appwrite.deleteFunctionVar",
"when": "viewItem =~ /(var)$/"
},
{
"command": "vscode-appwrite.createFunctionVar",
"when": "viewItem =~ /^(vars)$/",
"group": "inline"
},
{
"command": "vscode-appwrite.deleteTag",
"when": "viewItem =~ /^(tag)$/"
},
{
"command": "vscode-appwrite.viewExecutionErrors",
"when": "viewItem =~ /^execution[^s]*$/",
"group": "view@1"
},
{
"command": "vscode-appwrite.viewExecutionOutput",
"when": "viewItem =~ /^execution[^s]*$/",
"group": "view@1"
},
{
"command": "vscode-appwrite.copyExecutionErrors",
"when": "viewItem =~ /^execution[^s]*$/",
"group": "copy@2"
},
{
"command": "vscode-appwrite.copyExecutionOutput",
"when": "viewItem =~ /^execution[^s]*$/",
"group": "copy@2"
},
{
"command": "vscode-appwrite.openExecutionsInBrowser",
"when": "viewItem =~ /^executions$/",
"group": "inline"
},
{
"command": "vscode-appwrite.openFunctionTagsInBrowser",
"when": "viewItem =~ /^tags$/",
"group": "inline"
},
{
"command": "vscode-appwrite.openFunctionSettingsInBrowser",
"when": "viewItem =~ /^functionSettings$/",
"group": "inline" "group": "inline"
} }
], ],
@ -542,9 +693,11 @@
"webpack-cli": "^4.4.0" "webpack-cli": "^4.4.0"
}, },
"dependencies": { "dependencies": {
"cron-validate": "^1.4.3",
"cronstrue": "^1.113.0",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"fs-extra": "^9.1.0", "fs-extra": "^9.1.0",
"node-appwrite": "^2.2.1", "node-appwrite": "^2.2.3",
"tar": "^6.1.0" "tar": "^6.1.0"
} }
} }

7
src/appwrite.d.ts vendored
View file

@ -359,7 +359,7 @@ export type AppwriteHealth = {
}; };
export type StorageClient = { export type StorageClient = {
createFile: (file: any, read: string[], write: string[]) => Promise<any>; createFile: (file: any, read?: string[], write?: string[]) => Promise<any>;
listFiles: () => Promise<any>; listFiles: () => Promise<any>;
getFile: (fileId: string) => Promise<any>; getFile: (fileId: string) => Promise<any>;
}; };
@ -431,7 +431,7 @@ export type FunctionsClient = {
create: (name: string, execute: string[], env: string, vars?: Vars, events?: string[], schedule?: string, timeout?: number) => Promise<any>; create: (name: string, execute: string[], env: string, vars?: Vars, events?: string[], schedule?: string, timeout?: number) => Promise<any>;
list: (search?: string, offset?: number, limit?: number, orderType?: 'ASC' | 'DESC') => Promise<any>; list: (search?: string, offset?: number, limit?: number, orderType?: 'ASC' | 'DESC') => Promise<any>;
get: (functionId: string) => Promise<any>; get: (functionId: string) => Promise<any>;
update: (functionId: string, name: string, execute: string, vars: Vars, events: string[], schedule?: string, timeout?: number) => Promise<any>; update: (functionId: string, name: string, execute: string[], vars?: Vars, events?: string[], schedule?: string, timeout?: number) => Promise<any>;
updateTag: (functionId: string, tagId: string) => Promise<any>; updateTag: (functionId: string, tagId: string) => Promise<any>;
delete: (functionId: string) => Promise<any>; delete: (functionId: string) => Promise<any>;
createTag: (id: string, command: string, code: ReadStream) => Promise<any>; createTag: (id: string, command: string, code: ReadStream) => Promise<any>;
@ -443,9 +443,6 @@ export type FunctionsClient = {
getExecution: (functionId: string, executionId: string) => Promise<any>; getExecution: (functionId: string, executionId: string) => Promise<any>;
} }
export type SDK = { export type SDK = {
Client: new () => Client; Client: new () => Client;

View file

@ -19,7 +19,7 @@ export class Functions {
public async get(functionId: string): Promise<any> { public async get(functionId: string): Promise<any> {
return await AppwriteCall(this.functions.get(functionId)); return await AppwriteCall(this.functions.get(functionId));
} }
public async update(functionId: string, name: string, execute: string, vars: Vars, events: string[], schedule?: string, timeout?: number): Promise<any> { public async update(functionId: string, name: string, execute: string[], vars?: Vars, events?: string[], schedule?: string, timeout?: number): Promise<any> {
return await AppwriteCall(this.functions.update(functionId, name, execute, vars, events, schedule, timeout)); return await AppwriteCall(this.functions.update(functionId, name, execute, vars, events, schedule, timeout));
} }
public async updateTag(functionId: string, tagId: string): Promise<any> { public async updateTag(functionId: string, tagId: string): Promise<any> {

View file

@ -1,3 +1,4 @@
import { ReadStream } from 'node:fs';
import { Client, FilesList, StorageClient } from "../appwrite"; import { Client, FilesList, StorageClient } from "../appwrite";
import { AppwriteSDK } from '../constants'; import { AppwriteSDK } from '../constants';
import AppwriteCall from "../utils/AppwriteCall"; import AppwriteCall from "../utils/AppwriteCall";
@ -12,4 +13,8 @@ export class Storage {
public async listFiles(): Promise<FilesList | undefined> { public async listFiles(): Promise<FilesList | undefined> {
return await AppwriteCall(this.storage.listFiles()); return await AppwriteCall(this.storage.listFiles());
} }
public async createFile(file: ReadStream): Promise<void> {
return await AppwriteCall(this.storage.createFile(file));
}
} }

View file

@ -0,0 +1,9 @@
import { EditableTreeItem } from '../../tree/common/SimpleEditableTreeItem';
export async function editValue(treeItem: EditableTreeItem): Promise<void> {
if (treeItem === undefined) {
return;
}
await treeItem.prompt();
}

View file

@ -0,0 +1,7 @@
import { functionsClient } from '../../client';
import { TagTreeItem } from '../../tree/functions/tags/TagTreeItem';
export async function activateTag(tagItem: TagTreeItem): Promise<void> {
const tag = tagItem.tag;
await functionsClient?.updateTag(tag.functionId, tag.$id);
}

View file

@ -0,0 +1,11 @@
import { env } from 'vscode';
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
export async function copyExecutionErrors(executionItem: ExecutionTreeItem): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
env.clipboard.writeText(execution.stderr);
}

View file

@ -0,0 +1,11 @@
import { env } from 'vscode';
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
export async function copyExecutionOutput(executionItem: ExecutionTreeItem): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
env.clipboard.writeText(execution.stdout);
}

View file

@ -0,0 +1,19 @@
import { window } from 'vscode';
import { functionsClient } from '../../client';
import { appwriteFunctionRuntimes } from '../../constants';
import { validateFunctionName } from '../../tree/functions/settings/NameTreeItem';
export async function createFunction(): Promise<void> {
const name = await window.showInputBox({ prompt: 'Function name', validateInput: validateFunctionName });
if (name === undefined) {
return;
}
const env: string | undefined = await window.showQuickPick(appwriteFunctionRuntimes);
if (env === undefined) {
return;
}
await functionsClient?.create(name, [], env);
}

View file

@ -0,0 +1,16 @@
import { functionsClient } from '../../client';
import { VarsTreeItem } from '../../tree/functions/settings/VarsTreeItem';
import { keyValuePrompt } from '../../tree/functions/settings/VarTreeItem';
export async function createFunctionVar(treeItem: VarsTreeItem): Promise<void> {
if (treeItem === undefined) {
return;
}
const func = treeItem.parent.func;
const keyval = await keyValuePrompt();
if (keyval) {
const newVars = {...func.vars};
newVars[keyval.key] = keyval.value;
await functionsClient?.update(func.$id, func.name, [], newVars, func.events, func.schedule, func.timeout);
}
}

View file

@ -1,14 +1,22 @@
import { Uri } from 'vscode'; import { Uri } from "vscode";
import { functionsClient } from '../../client'; import { functionsClient, storageClient } from "../../client";
import { getTarReadStream } from '../../utils/tar'; import { getTarReadStream } from "../../utils/tar";
import { ext } from '../../extensionVariables'; import { ext } from "../../extensionVariables";
import * as fs from "fs";
export async function createTag(folder: Uri): Promise<void> { export async function createTag(folder: Uri): Promise<void> {
const buffer = await getTarReadStream(folder); const tarFilePath = await getTarReadStream(folder);
if (buffer !== undefined) { if (functionsClient === undefined) {
return;
}
if (tarFilePath === undefined) {
ext.outputChannel.appendLog("Error creating tar file.");
return;
}
try { try {
await functionsClient?.createTag('60b1836a8e5d9', "python hello.py", buffer); await functionsClient.createTag("60b1836a8e5d9", "python ./hello.py", fs.createReadStream(tarFilePath));
await storageClient?.createFile(fs.createReadStream(tarFilePath));
} catch (e) { } catch (e) {
ext.outputChannel.appendLog("Creating tag error: " + e); ext.outputChannel.appendLog("Creating tag error: " + e);
} }
} }
}

View file

@ -0,0 +1,9 @@
import { functionsClient } from '../../client';
import { FunctionTreeItem } from '../../tree/functions/FunctionTreeItem';
export async function deleteFunction(treeItem: FunctionTreeItem): Promise<void> {
if (!treeItem) {
return;
}
await functionsClient?.delete(treeItem.func.$id);
}

View file

@ -0,0 +1,13 @@
import { functionsClient } from '../../client';
import { VarTreeItem } from '../../tree/functions/settings/VarTreeItem';
export async function deleteFunctionVar(treeItem: VarTreeItem): Promise<void> {
if (treeItem === undefined) {
return;
}
const func = treeItem.func;
const newVars = {...func.vars};
delete newVars[treeItem.key];
await functionsClient?.update(func.$id, func.name, [], newVars, func.events, func.schedule, func.timeout);
}

View file

@ -0,0 +1,11 @@
import { functionsClient } from "../../client";
import { TagTreeItem } from "../../tree/functions/tags/TagTreeItem";
export async function deleteTag(tagItem: TagTreeItem): Promise<void> {
if (tagItem === undefined) {
return;
}
const func = tagItem.parent.parent.func;
await functionsClient?.deleteTag(func.$id, tagItem.tag.$id);
}

View file

@ -0,0 +1,15 @@
import { clientConfig } from '../../client';
import { ExecutionsTreeItem } from '../../tree/functions/executions/ExecutionsTreeItem';
import { openUrl } from '../../utils/openUrl';
import { getConsoleUrlFromEndpoint } from '../users/openUserInConsole';
export async function openExecutionsInBrowser(treeItem: ExecutionsTreeItem): Promise<void> {
const func = treeItem.parent.func;
const consoleUrl = getConsoleUrlFromEndpoint(clientConfig.endpoint);
// https://console.streamlux.com/console/functions/function/logs?id=60b1836a8e5d9&project=605ce39a30c01
const url = `${consoleUrl}/functions/function/logs?id=${func.$id}&project=${clientConfig.projectId}`;
openUrl(url);
}

View file

@ -0,0 +1,15 @@
import { clientConfig } from '../../client';
import { ExecutionsTreeItem } from '../../tree/functions/executions/ExecutionsTreeItem';
import { openUrl } from '../../utils/openUrl';
import { getConsoleUrlFromEndpoint } from '../users/openUserInConsole';
export async function openFunctionSettingsInBrowser(treeItem: ExecutionsTreeItem): Promise<void> {
const func = treeItem.parent.func;
const consoleUrl = getConsoleUrlFromEndpoint(clientConfig.endpoint);
// https://console.streamlux.com/console/functions/function/settings?id=60b1836a8e5d9&project=605ce39a30c01
const url = `${consoleUrl}/functions/function/settings?id=${func.$id}&project=${clientConfig.projectId}`;
openUrl(url);
}

View file

@ -0,0 +1,13 @@
import { clientConfig } from '../../client';
import { ExecutionsTreeItem } from '../../tree/functions/executions/ExecutionsTreeItem';
import { openUrl } from '../../utils/openUrl';
import { getConsoleUrlFromEndpoint } from '../users/openUserInConsole';
export async function openFunctionTagsInBrowser(treeItem: ExecutionsTreeItem): Promise<void> {
const func = treeItem.parent.func;
const consoleUrl = getConsoleUrlFromEndpoint(clientConfig.endpoint);
// https://console.streamlux.com/console/functions/function?id=60b1836a8e5d9&project=605ce39a30c01
const url = `${consoleUrl}/functions/function?id=${func.$id}&project=${clientConfig.projectId}`;
openUrl(url);
}

View file

@ -0,0 +1,11 @@
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
import { openReadOnlyContent } from "../../ui/openReadonlyContent";
export async function viewExecutionErrors(executionItem: ExecutionTreeItem): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
await openReadOnlyContent({ label: `${executionItem.parent.parent.func.name} execution stderr`, fullId: `${execution.$id}-errors.txt` }, execution.stderr, '.txt');
}

View file

@ -0,0 +1,11 @@
import { ExecutionTreeItem } from "../../tree/functions/executions/ExecutionTreeItem";
import { openReadOnlyContent } from "../../ui/openReadonlyContent";
export async function viewExecutionOutput(executionItem: ExecutionTreeItem): Promise<void> {
if (executionItem === undefined) {
return;
}
const execution = executionItem.execution;
await openReadOnlyContent({ label: `${executionItem.parent.parent.func.name} execution stdout`, fullId: `${execution.$id}-output.txt` }, execution.stdout, '.txt');
}

View file

@ -5,7 +5,8 @@ const documentationLinks = {
users: 'https://appwrite.io/docs/server/users', users: 'https://appwrite.io/docs/server/users',
database: 'https://appwrite.io/docs/client/database', database: 'https://appwrite.io/docs/client/database',
health: 'https://appwrite.io/docs/server/health', health: 'https://appwrite.io/docs/server/health',
storage: 'https://appwrite.io/docs/client/storage' storage: 'https://appwrite.io/docs/client/storage',
functions: 'https://appwrite.io/docs/server/functions'
}; };
type DocsPage = keyof typeof documentationLinks; type DocsPage = keyof typeof documentationLinks;

View file

@ -27,6 +27,20 @@ import { setActiveProject } from "./project/setActiveProject";
import { removeProject } from "./project/removeProject"; import { removeProject } from "./project/removeProject";
import { createTag } from './functions/createTag'; import { createTag } from './functions/createTag';
import { createExecution } from './functions/createExecution'; import { createExecution } from './functions/createExecution';
import { activateTag } from './functions/activateTag';
import { editValue } from './common/editValue';
import { deleteFunction } from './functions/deleteFunction';
import { createFunction } from './functions/createFunction';
import { createFunctionVar } from './functions/createFunctionVar';
import { deleteFunctionVar } from './functions/deleteFunctionVar';
import { deleteTag } from './functions/deleteTag';
import { viewExecutionErrors } from './functions/viewExecutionErrors';
import { viewExecutionOutput } from './functions/viewExecutionOutput';
import { copyExecutionErrors } from './functions/copyExecutionErrors';
import { copyExecutionOutput } from './functions/copyExecutionOutput';
import { openExecutionsInBrowser } from './functions/openExecutionsInBrowser';
import { openFunctionSettingsInBrowser } from './functions/openFunctionSettingsInBrowser';
import { openFunctionTagsInBrowser } from './functions/openFunctionTagsInBrowser';
class CommandRegistrar { class CommandRegistrar {
constructor(private readonly context: ExtensionContext) {} constructor(private readonly context: ExtensionContext) {}
@ -58,6 +72,9 @@ export function registerCommands(context: ExtensionContext): void {
}); });
}; };
/** Common **/
registerCommand("editValue", editValue);
/** General **/ /** General **/
registerCommand("Connect", connectAppwrite, "all"); registerCommand("Connect", connectAppwrite, "all");
@ -102,6 +119,21 @@ export function registerCommands(context: ExtensionContext): void {
registerCommand("removeProject", removeProject, "all"); registerCommand("removeProject", removeProject, "all");
/** Functions **/ /** Functions **/
registerCommand("CreateTag", createTag, "functions"); registerCommand("refreshFunctions", undefined, "functions");
registerCommand("CreateExecution", createExecution, "functions"); registerCommand("CreateExecution", createExecution, "functions");
registerCommand("CreateTag", createTag, "functions");
registerCommand("activateTag", activateTag, "functions");
registerCommand("deleteTag", deleteTag, "functions");
registerCommand("deleteFunction", deleteFunction, "functions");
registerCommand("openFunctionsDocumentation", () => openDocumentation("functions"));
registerCommand("createFunction", createFunction, "functions");
registerCommand("createFunctionVar", createFunctionVar, "functions");
registerCommand("deleteFunctionVar", deleteFunctionVar, "functions");
registerCommand("viewExecutionErrors", viewExecutionErrors);
registerCommand("viewExecutionOutput", viewExecutionOutput);
registerCommand("copyExecutionOutput", copyExecutionOutput);
registerCommand("copyExecutionErrors", copyExecutionErrors);
registerCommand("openExecutionsInBrowser", openExecutionsInBrowser);
registerCommand("openFunctionTagsInBrowser", openFunctionTagsInBrowser);
registerCommand("openFunctionSettingsInBrowser", openFunctionSettingsInBrowser);
} }

View file

@ -3,7 +3,7 @@ import { commands, Uri } from "vscode";
import { clientConfig } from "../../client"; import { clientConfig } from "../../client";
import { UserTreeItem } from "../../tree/users/UserTreeItem"; import { UserTreeItem } from "../../tree/users/UserTreeItem";
function getConsoleUrlFromEndpoint(endpoint: string): string { export function getConsoleUrlFromEndpoint(endpoint: string): string {
const url = new URL(endpoint); const url = new URL(endpoint);
return `${url.origin}/console`; return `${url.origin}/console`;
} }

View file

@ -1,4 +1,197 @@
import type { SDK } from './appwrite'; import type { SDK } from "./appwrite";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
export const AppwriteSDK: SDK = require('node-appwrite') as SDK; export const AppwriteSDK: SDK = require("node-appwrite") as SDK;
export const appwriteSystemEvents = [
{
name: "account.create",
description: "This event triggers when the account is created.",
},
{
name: "account.update.email",
description: "This event triggers when the account email address is updated.",
},
{
name: "account.update.name",
description: "This event triggers when the account name is updated.",
},
{
name: "account.update.password",
description: "This event triggers when the account password is updated.",
},
{
name: "account.update.prefs",
description: "This event triggers when the account preferences are updated.",
},
{
name: "account.recovery.create",
description: "This event triggers when the account recovery token is created.",
},
{
name: "account.recovery.update",
description: "This event triggers when the account recovery token is validated.",
},
{
name: "account.verification.create",
description: "This event triggers when the account verification token is created.",
},
{
name: "account.verification.update",
description: "This event triggers when the account verification token is validated.",
},
{
name: "account.delete",
description: "This event triggers when the account is deleted.",
},
{
name: "account.sessions.create",
description: "This event triggers when the account session is created.",
},
{
name: "account.delete",
description: "This event triggers when the account is deleted.",
},
{
name: "account.sessions.create",
description: "This event triggers when the account session is created.",
},
{
name: "account.sessions.delete",
description: "This event triggers when the account session is deleted.",
},
{
name: "database.collections.create",
description: "This event triggers when a database collection is created.",
},
{
name: "database.collections.update",
description: "This event triggers when a database collection is updated.",
},
{
name: "database.collections.delete",
description: "This event triggers when a database collection is deleted.",
},
{
name: "database.documents.create",
description: "This event triggers when a database document is created.",
},
{
name: "database.documents.update",
description: "This event triggers when a database document is updated.",
},
{
name: "database.documents.delete",
description: "This event triggers when a database document is deleted.",
},
{
name: "functions.create",
description: "This event triggers when a function is created.",
},
{
name: "functions.update",
description: "This event triggers when a function is updated.",
},
{
name: "functions.delete",
description: "This event triggers when a function is deleted.",
},
{
name: "functions.tags.create",
description: "This event triggers when a function tag is created.",
},
{
name: "functions.tags.update",
description: "This event triggers when a function tag is updated.",
},
{
name: "functions.tags.delete",
description: "This event triggers when a function tag is deleted.",
},
{
name: "functions.executions.create",
description: "This event triggers when a function execution is created.",
},
{
name: "functions.executions.update",
description: "This event triggers when a function execution is updated.",
},
{
name: "storage.files.create",
description: "This event triggers when a storage file is created.",
},
{
name: "storage.files.update",
description: "This event triggers when a storage file is updated.",
},
{
name: "storage.files.delete",
description: "This event triggers when a storage file is deleted.",
},
{
name: "users.create",
description: "This event triggers when a user is created from the users API.",
},
{
name: "users.update.prefs",
description: "This event triggers when a user preference is updated from the users API.",
},
{
name: "users.update.status",
description: "This event triggers when a user status is updated from the users API.",
},
{
name: "users.delete",
description: "This event triggers when a user is deleted from users API.",
},
{
name: "users.sessions.delete",
description: "This event triggers when a user session is deleted from users API.",
},
{
name: "teams.create",
description: "This event triggers when a team is created.",
},
{
name: "teams.update",
description: "This event triggers when a team is updated.",
},
{
name: "teams.delete",
description: "This event triggers when a team is deleted.",
},
{
name: "teams.memberships.create",
description: "This event triggers when a team memberships is created.",
},
{
name: "teams.memberships.update",
description: "This event triggers when a team membership is updated.",
},
{
name: "teams.memberships.update.status",
description: "This event triggers when a team memberships status is updated.",
},
{
name: "teams.memberships.delete",
description: "This event triggers when a team memberships is deleted.",
},
];
export const appwriteFunctionRuntimes = [
"dotnet-3.1",
"dotnet-5.0",
"dart-2.10",
"dart-2.12",
"deno-1.5",
"deno-1.6",
"deno-1.8",
"python-3.8",
"python-3.9",
"ruby-2.7",
"ruby-3.0",
"php-7.4",
"php-8.0",
"node-14.5",
"node-15.5",
];

View file

@ -0,0 +1,13 @@
import { TreeItem } from "vscode";
export abstract class EditableTreeItemBase<T> extends TreeItem {
public abstract setValue(value: T): Promise<void>;
constructor(contextValuePrefix: string, public readonly value: T, description?: string) {
super(typeof value === "string" ? value : "No label");
this.contextValue = `editable_${contextValuePrefix}`;
this.description = description ?? contextValuePrefix;
}
public abstract prompt(): Promise<void>;
}

View file

@ -0,0 +1,38 @@
import { QuickPickItem, QuickPickOptions, window } from "vscode";
import { EditableTreeItemBase } from "./EditableTreeItemBase";
export abstract class EnumEditableTreeItemBase extends EditableTreeItemBase<string[]> {
public abstract options: string[] | QuickPickItem[];
public quickPickOptions: QuickPickOptions;
constructor(contextValuePrefix: string, public readonly value: string[], description?: string) {
super(contextValuePrefix, value, description);
this.quickPickOptions = {};
}
public async prompt(): Promise<void> {
const value = await window.showQuickPick(
this.options.map<QuickPickItem>((option: QuickPickItem | string): QuickPickItem => {
if (typeof option === "string") {
return { label: option, picked: this.value.includes(option) };
}
const picked = this.value.includes(option.label);
return { ...option, picked, alwaysShow: picked };
}).sort((a, b) => {
if (a.picked) {
return -1;
}
if (b.picked) {
return 1;
}
return 0;
}),
{ ...this.quickPickOptions, canPickMany: true }
);
if (value !== undefined) {
this.setValue(value.map((item) => item.label));
}
}
}

View file

@ -0,0 +1,18 @@
import { TreeItem, window } from "vscode";
export class EditableTreeItem extends TreeItem {
public readonly setValue: (value: string) => Promise<void>;
constructor(label: string, contextValuePrefix: string, public readonly value: string, setValue: (value: string) => Promise<void>) {
super(label);
this.setValue = setValue;
this.contextValue = `editable_${contextValuePrefix}`;
}
public async prompt(): Promise<void> {
const value = await window.showInputBox({ value: this.value });
if (value !== undefined) {
this.setValue(value);
}
}
}

View file

@ -0,0 +1,22 @@
import { InputBoxOptions, window } from "vscode";
import { EditableTreeItemBase } from "./EditableTreeItemBase";
export abstract class StringEditableTreeItemBase extends EditableTreeItemBase<string> {
public abstract setValue(value: string): Promise<void>;
public inputBoxOptions: InputBoxOptions;
constructor(contextValuePrefix: string, public readonly value: string, description?: string) {
super(contextValuePrefix, value, description);
this.inputBoxOptions = {
prompt: description,
};
}
public async prompt(): Promise<void> {
const value = await window.showInputBox({ value: this.value, ...this.inputBoxOptions });
if (value !== undefined) {
this.setValue(value);
}
}
}

View file

@ -1,17 +1,21 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode"; import { MarkdownString, ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { Function } from "../../appwrite"; import { Function } from "../../appwrite";
import { AppwriteTreeItemBase } from "../../ui/AppwriteTreeItemBase"; import { AppwriteTreeItemBase } from "../../ui/AppwriteTreeItemBase";
import { ExecutionsTreeItem } from './ExecutionsTreeItem'; import { msToDate } from '../../utils/date';
import { ExecutionsTreeItem } from './executions/ExecutionsTreeItem';
import { FunctionsTreeItemProvider } from './FunctionsTreeItemProvider'; import { FunctionsTreeItemProvider } from './FunctionsTreeItemProvider';
import { TagsTreeItem } from './TagsTreeItem'; import { FunctionSettingsTreeItem } from './settings/FunctionSettingsTreeItem';
import { TagsTreeItem } from './tags/TagsTreeItem';
export class FunctionTreeItem extends AppwriteTreeItemBase { export class FunctionTreeItem extends AppwriteTreeItemBase {
constructor(public func: Function, public readonly provider: FunctionsTreeItemProvider) { constructor(public func: Function, public readonly provider: FunctionsTreeItemProvider) {
super(undefined, func.name); super(undefined, func.name);
this.tooltip = new MarkdownString(`ID: ${func.$id} \nLast updated: ${msToDate(func.dateUpdated)} \nCreated: ${msToDate(func.dateCreated)}`);
this.description = func.env;
} }
public async getChildren(): Promise<TreeItem[]> { public async getChildren(): Promise<TreeItem[]> {
return [new TagsTreeItem(this), new ExecutionsTreeItem(this)]; return [new FunctionSettingsTreeItem(this), new TagsTreeItem(this), new ExecutionsTreeItem(this)];
} }
public async refresh(): Promise<void> { public async refresh(): Promise<void> {

View file

@ -37,10 +37,7 @@ export class FunctionsTreeItemProvider implements vscode.TreeDataProvider<vscode
if (list) { if (list) {
const functionTreeItems = list.functions.map((func: Function) => new FunctionTreeItem(func, this)) ?? []; const functionTreeItems = list.functions.map((func: Function) => new FunctionTreeItem(func, this)) ?? [];
const headerItem: vscode.TreeItem = { return functionTreeItems;
label: `Total functions: ${list.sum}`,
};
return [headerItem, ...functionTreeItems];
} }
return [{ label: "No functions found" }]; return [{ label: "No functions found" }];

View file

@ -1,8 +1,8 @@
import dayjs = require('dayjs');
import { MarkdownString, ThemeColor, ThemeIcon, TreeItem } from "vscode"; import { MarkdownString, ThemeColor, ThemeIcon, TreeItem } from "vscode";
import { Execution, ExecutionStatus } from "../../appwrite"; import { Execution, ExecutionStatus } from "../../../appwrite";
import { functionsClient } from "../../client"; import { functionsClient } from "../../../client";
import { ext } from "../../extensionVariables"; import { ext } from "../../../extensionVariables";
import { msToDate } from "../../../utils/date";
import { ExecutionsTreeItem } from "./ExecutionsTreeItem"; import { ExecutionsTreeItem } from "./ExecutionsTreeItem";
const executionStatusIcons: Record<ExecutionStatus, ThemeIcon> = { const executionStatusIcons: Record<ExecutionStatus, ThemeIcon> = {
@ -22,13 +22,14 @@ export class ExecutionTreeItem extends TreeItem {
public isAutoRefreshing: boolean = false; public isAutoRefreshing: boolean = false;
private refreshCount: number = 0; private refreshCount: number = 0;
constructor(public readonly parent: ExecutionsTreeItem, private readonly execution: Execution) { constructor(public readonly parent: ExecutionsTreeItem, public readonly execution: Execution) {
super(execution.$id); super(execution.$id);
this.label = this.getLabel(execution); this.label = this.getLabel(execution);
this.iconPath = executionStatusIcons[execution.status]; this.iconPath = executionStatusIcons[execution.status];
const md = `Id: ${execution.$id} \nCreated: ${this.getCreated(execution)} \nTrigger: ${execution.trigger}`; const md = `Id: ${execution.$id} \nCreated: ${this.getCreated(execution)} \nTrigger: ${execution.trigger}`;
this.tooltip = new MarkdownString(md); this.tooltip = new MarkdownString(md);
this.description = execution.trigger; this.description = execution.trigger;
this.contextValue = this.getContextValue(execution);
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting"; this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
this.autoRefresh(); this.autoRefresh();
} }
@ -47,6 +48,7 @@ export class ExecutionTreeItem extends TreeItem {
return; return;
} }
this.contextValue = this.getContextValue(execution);
this.iconPath = executionStatusIcons[execution.status]; this.iconPath = executionStatusIcons[execution.status];
this.label = this.getLabel(execution); this.label = this.getLabel(execution);
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting"; this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
@ -57,17 +59,28 @@ export class ExecutionTreeItem extends TreeItem {
} }
getLabel(execution: Execution): string { getLabel(execution: Execution): string {
if (execution.status === "completed") { if (execution.status === "completed" || execution.status === "failed") {
return `${this.getCreated(execution)} (${execution.time.toPrecision(2)}s)`; return `${this.getCreated(execution)} (${execution.time.toPrecision(2)}s)`;
} }
return `${this.getCreated(execution)} (${execution.status})`; return `${this.getCreated(execution)} (${execution.status})`;
} }
getContextValue(execution: Execution): string {
if (execution.status === "completed" || execution.status === "failed") {
if (execution.stderr === "" && execution.stdout === "") {
return "execution_noErrorOrOutput";
}
if (execution.stderr === "") {
return "execution_outputOnly";
}
if (execution.stdout === "") {
return "execution_errorOnly";
}
}
return "execution";
}
getCreated(execution: Execution): string { getCreated(execution: Execution): string {
return dayjs(execution.dateCreated).format("LTS"); return msToDate(execution.dateCreated);
} }
contextValue = "tag";
} }

View file

@ -1,9 +1,9 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode"; import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { Execution, ExecutionList } from '../../appwrite'; import { Execution, ExecutionList } from '../../../appwrite';
import { functionsClient } from "../../client"; import { functionsClient } from "../../../client";
import { AppwriteTreeItemBase } from '../../ui/AppwriteTreeItemBase'; import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
import { ExecutionTreeItem } from './ExecutionTreeItem'; import { ExecutionTreeItem } from './ExecutionTreeItem';
import { FunctionTreeItem } from './FunctionTreeItem'; import { FunctionTreeItem } from '../FunctionTreeItem';
export class ExecutionsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> { export class ExecutionsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
constructor(public readonly parent: FunctionTreeItem) { constructor(public readonly parent: FunctionTreeItem) {

View file

@ -0,0 +1,31 @@
import { QuickPickItem, QuickPickOptions } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { appwriteSystemEvents } from "../../../constants";
import { ext } from "../../../extensionVariables";
import { EnumEditableTreeItemBase } from "../../common/EnumEditableTreeItem";
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
export class EventsTreeItem extends EnumEditableTreeItemBase {
public quickPickOptions: QuickPickOptions = {
placeHolder: "Select which system events should trigger this function.",
matchOnDescription: true
};
public options: string[] | QuickPickItem[] = appwriteSystemEvents.map((event) => ({
label: event.name,
description: event.description.replace("This event t", "T")
}));
public readonly func: Function;
constructor(public readonly parent: FunctionSettingsTreeItem) {
super("System events", parent.func.events);
this.func = parent.func;
this.label = parent.func.events.length === 0 ? 'None' : `${parent.func.events.length} active`;
}
public async setValue(value: string[]): Promise<void> {
await functionsClient?.update(this.func.$id, this.func.name, [], this.func.vars, value, this.func.schedule, this.func.timeout);
ext.tree?.functions?.refresh();
}
}

View file

@ -0,0 +1,45 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { AppwriteTreeItemBase } from "../../../ui/AppwriteTreeItemBase";
import { ChildTreeItem } from "../../ChildTreeItem";
import { FunctionTreeItem } from "../FunctionTreeItem";
import { EventsTreeItem } from "./EventsTreeItem";
import { NameTreeItem } from "./NameTreeItem";
import { ScheduleTreeItem } from "./ScheduleTreeItem";
import { TimeoutTreeItem } from "./TimeoutTreeItem";
import { VarsTreeItem } from "./VarsTreeItem";
export class FunctionSettingsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
public readonly func: Function;
constructor(public readonly parent: FunctionTreeItem) {
super(parent, "Settings");
this.func = parent.func;
}
public async getChildren(): Promise<TreeItem[]> {
if (!functionsClient) {
return [];
}
const children = [
new NameTreeItem(this),
new ScheduleTreeItem(this),
new TimeoutTreeItem(this.func),
new EventsTreeItem(this),
new VarsTreeItem(this),
];
return children;
}
labelItem(label: string, value: string): TreeItem {
return new ChildTreeItem(this, { label: value === "" ? "None" : value, description: label });
}
collapsibleState = TreeItemCollapsibleState.Collapsed;
contextValue = "functionSettings";
iconPath = new ThemeIcon("settings");
}

View file

@ -0,0 +1,43 @@
import { InputBoxOptions, MarkdownString } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from '../../common/StringEditableTreeItem';
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
const tooltip = "Function name";
const description = "Function name. Max length: 128 chars.";
const tooLongInvalid = "Value exceeds maximum length of 128 characters.";
export function validateFunctionName(value: string): string | undefined {
if (value.length > 128) {
return tooLongInvalid;
}
}
export class NameTreeItem extends StringEditableTreeItemBase {
public readonly func: Function;
inputBoxOptions: InputBoxOptions = {
validateInput: (value) => {
if (value.length > 128) {
return tooLongInvalid;
}
},
prompt: description,
};
public async setValue(value: string): Promise<void> {
if (value.length === 0) {
return;
}
await functionsClient?.update(this.func.$id, value, [], this.func.vars, this.func.events, this.func.schedule, this.func.timeout);
ext.tree?.functions?.refresh();
}
constructor(private readonly parent: FunctionSettingsTreeItem) {
super("Name", parent.func.name);
this.func = parent.func;
this.tooltip = new MarkdownString(tooltip);
}
}

View file

@ -0,0 +1,44 @@
import { InputBoxOptions, MarkdownString } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import cron from "cron-validate";
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
import cronstrue from "cronstrue";
import { StringEditableTreeItemBase } from '../../common/StringEditableTreeItem';
export class ScheduleTreeItem extends StringEditableTreeItemBase {
private readonly func: Function;
inputBoxOptions: InputBoxOptions = {
validateInput: (value) => {
if (value === "") {
return;
}
const cronResult = cron(value);
if (!cronResult.isValid()) {
return cronResult.getError().join(", ");
}
},
value: this.value === "" ? "0 0 * * *" : this.value,
prompt: "Function execution schedule in CRON format. Leave blank for no schedule. https://crontab.guru/examples.html",
};
public async setValue(value: string): Promise<void> {
await functionsClient?.update(this.func.$id, this.func.name, [], this.func.vars, this.func.events, value === "" ? undefined : value, this.func.timeout);
ext.tree?.functions?.refresh();
}
constructor(private readonly parent: FunctionSettingsTreeItem) {
super("Schedule", parent.func.schedule);
this.func = parent.func;
this.tooltip = new MarkdownString(`Function execution schedule in CRON format`);
this.label = `${this.value}`;
const cronResult = cron(parent.func.schedule);
if (cronResult.isValid()) {
this.label = cronstrue.toString(this.value, { verbose: true });
} else {
this.label = this.value === "" ? "None" : "Invalid CRON";
}
}
}

View file

@ -0,0 +1,48 @@
import { InputBoxOptions, MarkdownString } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from "../../common/StringEditableTreeItem";
function isNumeric(str: string) {
console.log("here");
return !isNaN(+str);
}
export class TimeoutTreeItem extends StringEditableTreeItemBase {
inputBoxOptions: InputBoxOptions = {
validateInput: (value) => {
if (!isNumeric(value)) {
return "Input must be an integer.";
}
if (+value > 900) {
return "Value exceeds the maximum of 900 seconds (15 minutes)";
}
if (+value < 0) {
return "Value cannot be negative";
}
},
prompt: "Function maximum execution time in seconds. Maximum of 900 seconds (15 minutes).",
};
public async setValue(value: string): Promise<void> {
await functionsClient?.update(
this.func.$id,
this.func.name,
[],
this.func.vars,
this.func.events,
this.func.schedule,
parseInt(value)
);
ext.tree?.functions?.refresh();
}
constructor(private readonly func: Function) {
super("Timeout", func.timeout.toString());
this.tooltip = new MarkdownString(`Function maximum execution time in seconds.`);
this.label = `${this.value}s`;
}
}

View file

@ -0,0 +1,64 @@
import { InputBoxOptions, MarkdownString, window } from "vscode";
import { Function } from "../../../appwrite";
import { functionsClient } from "../../../client";
import { ext } from "../../../extensionVariables";
import { StringEditableTreeItemBase } from "../../common/StringEditableTreeItem";
import { VarsTreeItem } from "./VarsTreeItem";
const tooltip = "Environment var";
const description = "Function name. Max length: 128 chars.";
const tooLongInvalid = "Value exceeds maximum length of 128 characters.";
export async function keyValuePrompt(keyInit?: string, valueInit?: string): Promise<{ key: string; value: string } | undefined> {
const key = await window.showInputBox({ value: keyInit, prompt: "Environment variable name" });
if (key === undefined) {
return;
}
const value = await window.showInputBox({ value: valueInit, prompt: "Environment variable value" });
if (value === undefined) {
return;
}
return { key, value };
}
export class VarTreeItem extends StringEditableTreeItemBase {
public readonly func: Function;
inputBoxOptions: InputBoxOptions = {
validateInput: (value) => {
if (value.length > 128) {
return tooLongInvalid;
}
},
prompt: description,
};
public async setValue(value: string, key?: string): Promise<void> {
if (value.length === 0) {
return;
}
const newVars = { ...this.func.vars };
newVars[this.key] = value;
if (key) {
delete newVars[this.key];
newVars[key] = value;
}
await functionsClient?.update(this.func.$id, this.func.name, [], newVars, this.func.events, this.func.schedule, this.func.timeout);
ext.tree?.functions?.refresh();
}
constructor(public readonly parent: VarsTreeItem, public readonly key: string, value: string) {
super("var", value);
this.func = parent.parent.func;
this.tooltip = new MarkdownString(tooltip);
this.label = `${key}=${value}`;
this.description = undefined;
}
public async prompt(): Promise<void> {
const keyval = await keyValuePrompt(this.key, this.value);
if (keyval) {
this.setValue(keyval.value, keyval.key);
}
}
}

View file

@ -0,0 +1,21 @@
import { TreeItem, TreeItemCollapsibleState } from "vscode";
import { Vars } from "../../../appwrite";
import { AppwriteTreeItemBase } from "../../../ui/AppwriteTreeItemBase";
import { FunctionSettingsTreeItem } from "./FunctionSettingsTreeItem";
import { VarTreeItem } from "./VarTreeItem";
export class VarsTreeItem extends AppwriteTreeItemBase<FunctionSettingsTreeItem> {
public readonly vars: Vars;
constructor(parent: FunctionSettingsTreeItem) {
super(parent, "Environment variables");
this.vars = parent.func.vars;
this.description = undefined;
}
public async getChildren(): Promise<TreeItem[]> {
return Object.keys(this.vars).map((key) => new VarTreeItem(this, key, this.vars[key]));
}
contextValue = "vars";
collapsibleState = TreeItemCollapsibleState.Collapsed;
}

View file

@ -1,16 +1,14 @@
import { ThemeIcon, TreeItem } from "vscode"; import { ThemeIcon, TreeItem } from "vscode";
import { Tag } from '../../appwrite'; import { Tag } from '../../../appwrite';
import { TagsTreeItem } from './TagsTreeItem'; import { TagsTreeItem } from './TagsTreeItem';
export class TagTreeItem extends TreeItem { export class TagTreeItem extends TreeItem {
constructor(public readonly parent: TagsTreeItem, public readonly tag: Tag) {
constructor(public readonly parent: TagsTreeItem, tag: Tag) {
super(tag.$id); super(tag.$id);
const func = parent.parent.func; const func = parent.parent.func;
const active = func.tag === tag.$id; const active = func.tag === tag.$id;
this.label = `${tag.$id}${active ? ' (Active)' : ''}`; this.label = `${tag.$id}${active ? ' (Active)' : ''}`;
this.iconPath = new ThemeIcon(active ? 'circle-filled' : 'circle-outline'); this.iconPath = new ThemeIcon(active ? 'circle-filled' : 'circle-outline');
this.contextValue = `tag${active ? '_active' : ''}`;
} }
contextValue = "tag";
} }

View file

@ -1,7 +1,7 @@
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode"; import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
import { functionsClient } from "../../client"; import { functionsClient } from "../../../client";
import { AppwriteTreeItemBase } from '../../ui/AppwriteTreeItemBase'; import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
import { FunctionTreeItem } from './FunctionTreeItem'; import { FunctionTreeItem } from '../FunctionTreeItem';
import { TagTreeItem } from './TagTreeItem'; import { TagTreeItem } from './TagTreeItem';
export class TagsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> { export class TagsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {

5
src/utils/date.ts Normal file
View file

@ -0,0 +1,5 @@
import dayjs = require('dayjs');
export function msToDate(ms: number): string {
return dayjs(ms).format("LTS");
}

View file

@ -4,14 +4,13 @@ import { ext } from "../extensionVariables";
import * as path from "path"; import * as path from "path";
import * as os from "os"; import * as os from "os";
import * as fs from "fs"; import * as fs from "fs";
import { ReadStream } from 'node:fs';
export async function getTarReadStream(folder: Uri): Promise<ReadStream | undefined> { export async function getTarReadStream(folder: Uri): Promise<string | undefined> {
try { try {
const folderName = path.basename(folder.path); const folderName = path.basename(folder.path);
const tarName = `${folderName}.tar.gz`; const tarName = `${folderName}.tar.gz`;
const cwd = folder.fsPath; const cwd = path.resolve(folder.fsPath, '..');
if (cwd === undefined) { if (cwd === undefined) {
window.showErrorMessage("No workspace open."); window.showErrorMessage("No workspace open.");
return; return;
@ -20,17 +19,11 @@ export async function getTarReadStream(folder: Uri): Promise<ReadStream | undefi
const tarFilePath = path.join(os.tmpdir(), tarName); const tarFilePath = path.join(os.tmpdir(), tarName);
tar.create({ gzip: true, cwd: cwd }, [path.relative(cwd, folder.fsPath)]).pipe(fs.createWriteStream(tarFilePath, { emitClose: true})); tar.create({ gzip: true, cwd: cwd }, [path.relative(cwd, folder.fsPath)]).pipe(fs.createWriteStream(tarFilePath));
const stream = fs.createReadStream(tarFilePath); ext.outputChannel.appendLog(`Created ${tarFilePath}`);
stream.on('close', () => {
try { return tarFilePath;
fs.unlinkSync(tarFilePath);
} catch (e) {
//
}
});
return stream;
} catch (e) { } catch (e) {
ext.outputChannel?.appendLog("Error creating tar.gz: " + e); ext.outputChannel?.appendLog("Error creating tar.gz: " + e);

0
src/utils/validation.ts Normal file
View file