Feature: functions (#15)
* Basic functions create tag and create execution feature * Detailed function support, create tag still broken * create tag and pick folder * edit changelog and bump extension version * fix linting
This commit is contained in:
parent
b4e5fdcd20
commit
6cbf15379c
52 changed files with 1722 additions and 4573 deletions
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -6,6 +6,16 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.1.0] - 2021-5-29
|
||||
|
||||
## Functions!
|
||||
### Added
|
||||
- Ability to create and delete Appwrite functions
|
||||
- Edit function settings
|
||||
- View, and delete tags (creating tags is broken currently)
|
||||
- Create and view function executions
|
||||
- View execution output and errors
|
||||
|
||||
## [0.0.9] - 2021-5-21
|
||||
- Remove temporary fix for Appwrite https://github.com/appwrite/appwrite/issues/1171. Upstream issue was resolved.
|
||||
|
||||
|
|
4716
package-lock.json
generated
4716
package-lock.json
generated
File diff suppressed because it is too large
Load diff
198
package.json
198
package.json
|
@ -2,7 +2,7 @@
|
|||
"name": "vscode-appwrite",
|
||||
"displayName": "Appwrite",
|
||||
"description": "Manage your Appwrite resources right from VS Code!",
|
||||
"version": "0.0.9",
|
||||
"version": "0.1.0",
|
||||
"engines": {
|
||||
"vscode": "^1.55.0"
|
||||
},
|
||||
|
@ -30,7 +30,9 @@
|
|||
"onView:Users",
|
||||
"onView:Database",
|
||||
"onView:Health",
|
||||
"onCommand:vscode-appwrite.AddProject"
|
||||
"onView:Functions",
|
||||
"onCommand:vscode-appwrite.AddProject",
|
||||
"onCommand:vscode-appwrite.CreateTag"
|
||||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
|
@ -184,10 +186,99 @@
|
|||
"title": "Refresh projects",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.refreshFunctions",
|
||||
"title": "Refresh functions",
|
||||
"icon": "$(refresh)"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.removeProject",
|
||||
"title": "Remove project",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.CreateTag",
|
||||
"title": "Create function tag",
|
||||
"icon": "$(cloud-upload)"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.deleteTag",
|
||||
"title": "Delete tag",
|
||||
"icon": "$(trash)"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.CreateExecution",
|
||||
"title": "Execute"
|
||||
},
|
||||
{
|
||||
"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": {
|
||||
|
@ -211,6 +302,10 @@
|
|||
{
|
||||
"id": "Projects",
|
||||
"name": "Projects"
|
||||
},
|
||||
{
|
||||
"id": "Functions",
|
||||
"name": "Functions (Preview)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -276,14 +371,24 @@
|
|||
"when": "view == Storage",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.openFunctionsDocumentation",
|
||||
"when": "view == Functions",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.refreshProjects",
|
||||
"when": "view == Projects",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.addProject",
|
||||
"when": "view == Projects",
|
||||
"command": "vscode-appwrite.refreshFunctions",
|
||||
"when": "view == Functions",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.createFunction",
|
||||
"when": "view == Functions",
|
||||
"group": "navigation"
|
||||
}
|
||||
],
|
||||
|
@ -383,6 +488,85 @@
|
|||
{
|
||||
"command": "vscode-appwrite.removeProject",
|
||||
"when": "viewItem =~ /(appwriteProject)/"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.CreateExecution",
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"command": "vscode-appwrite.CreateTag",
|
||||
"when": "viewItem =~ /^tags$/",
|
||||
"group": "inline"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"command": "vscode-appwrite.CreateTag",
|
||||
"when": "explorerResourceIsFolder == true",
|
||||
"group": "appwrite@1"
|
||||
}
|
||||
],
|
||||
"commandPalette": [
|
||||
|
@ -499,6 +683,7 @@
|
|||
"@types/glob": "^7.1.3",
|
||||
"@types/mocha": "^8.0.4",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/tar": "^4.0.4",
|
||||
"@types/vscode": "^1.55.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.14.1",
|
||||
"@typescript-eslint/parser": "^4.14.1",
|
||||
|
@ -513,8 +698,11 @@
|
|||
"webpack-cli": "^4.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cron-validate": "^1.4.3",
|
||||
"cronstrue": "^1.113.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"fs-extra": "^9.1.0",
|
||||
"node-appwrite": "^2.2.1"
|
||||
"node-appwrite": "^2.2.3",
|
||||
"tar": "^6.1.0"
|
||||
}
|
||||
}
|
||||
|
|
87
src/appwrite.d.ts
vendored
87
src/appwrite.d.ts
vendored
|
@ -1,3 +1,6 @@
|
|||
import { ReadStream } from 'fs';
|
||||
import { Stream } from 'node:stream';
|
||||
|
||||
export type Token = {
|
||||
/**
|
||||
* Token ID.
|
||||
|
@ -287,7 +290,7 @@ export type Rule = {
|
|||
list: string[];
|
||||
};
|
||||
|
||||
interface Permissions {
|
||||
export type Permissions = {
|
||||
read: string[];
|
||||
write: string[];
|
||||
}
|
||||
|
@ -356,11 +359,90 @@ export type AppwriteHealth = {
|
|||
};
|
||||
|
||||
export type StorageClient = {
|
||||
createFile: (file: any, read: string[], write: string[]) => Promise<any>;
|
||||
createFile: (file: any, read?: string[], write?: string[]) => Promise<any>;
|
||||
listFiles: () => Promise<any>;
|
||||
getFile: (fileId: string) => Promise<any>;
|
||||
};
|
||||
|
||||
type Vars = Record<string, any>;
|
||||
|
||||
export type Function = {
|
||||
'$id': string;
|
||||
'$permissions': Permissions;
|
||||
name: string;
|
||||
dateCreated: number;
|
||||
dateUpdated: number;
|
||||
status: string;
|
||||
env: string;
|
||||
tag: string;
|
||||
vars: Vars;
|
||||
events: string[];
|
||||
schedule: string;
|
||||
scheduleNext: number;
|
||||
schedulePrevious: number;
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export type FunctionsList = {
|
||||
sum: number;
|
||||
functions: Function[];
|
||||
}
|
||||
|
||||
export type Tag = {
|
||||
'$id': string;
|
||||
functionId: string;
|
||||
dateCreated: number;
|
||||
command: string;
|
||||
size: string;
|
||||
};
|
||||
|
||||
export type TagList = {
|
||||
sum: number;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
export type ExecutionStatus = "waiting" | "processing" | "completed" | "failed";
|
||||
|
||||
export type Execution = {
|
||||
'$id': string;
|
||||
functionId: string;
|
||||
dateCreated: number;
|
||||
trigger: string;
|
||||
status: ExecutionStatus;
|
||||
exitCode: number;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
time: number;
|
||||
};
|
||||
|
||||
export type ExecutionList = {
|
||||
sum: number;
|
||||
executions: Execution[];
|
||||
};
|
||||
|
||||
export type Search = {
|
||||
search?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
orderType?: 'ASC' | 'DESC';
|
||||
};
|
||||
|
||||
export type FunctionsClient = {
|
||||
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>;
|
||||
get: (functionId: string) => 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>;
|
||||
delete: (functionId: string) => Promise<any>;
|
||||
createTag: (id: string, command: string, code: ReadStream) => Promise<any>;
|
||||
listTags: (id: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC') => Promise<any>;
|
||||
getTag: (functionId: string, tagId: string) => Promise<any>;
|
||||
deleteTag: (functionId: string, tagId: string) => Promise<any>;
|
||||
createExecution: (functionId: string, data?: string) => Promise<any>;
|
||||
listExecutions: (functionId: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC') => Promise<any>;
|
||||
getExecution: (functionId: string, executionId: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export type SDK = {
|
||||
Client: new () => Client;
|
||||
|
||||
|
@ -368,4 +450,5 @@ export type SDK = {
|
|||
Health: new (client: Client) => HealthClient;
|
||||
Database: new (client: Client) => DatabaseClient;
|
||||
Storage: new (client: Client) => StorageClient;
|
||||
Functions: new (client: Client) => FunctionsClient;
|
||||
};
|
||||
|
|
52
src/appwrite/Functions.ts
Normal file
52
src/appwrite/Functions.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Client, Execution, ExecutionList, FunctionsClient, TagList, Vars } from "../appwrite";
|
||||
import { AppwriteSDK } from '../constants';
|
||||
import AppwriteCall from '../utils/AppwriteCall';
|
||||
import { ReadStream } from 'node:fs';
|
||||
|
||||
export class Functions {
|
||||
private readonly functions: FunctionsClient;
|
||||
|
||||
constructor(client: Client) {
|
||||
this.functions = new AppwriteSDK.Functions(client);
|
||||
}
|
||||
|
||||
public async create(name: string, execute: string[], env: string, vars?: Vars, events?: string[], schedule?: string, timeout?: number): Promise<any> {
|
||||
return await AppwriteCall(this.functions.create(name, execute, env, vars, events, schedule, timeout));
|
||||
}
|
||||
public async list(search?: string, offset?: number, limit?: number, orderType?: 'ASC' | 'DESC'): Promise<any> {
|
||||
return await AppwriteCall(this.functions.list(search, offset, limit, orderType));
|
||||
}
|
||||
public async get(functionId: string): Promise<any> {
|
||||
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> {
|
||||
return await AppwriteCall(this.functions.update(functionId, name, execute, vars, events, schedule, timeout));
|
||||
}
|
||||
public async updateTag(functionId: string, tagId: string): Promise<any> {
|
||||
return await AppwriteCall(this.functions.updateTag(functionId, tagId));
|
||||
}
|
||||
public async delete(functionId: string): Promise<void> {
|
||||
return await AppwriteCall(this.functions.delete(functionId));
|
||||
}
|
||||
public async createTag(functionId: string, command: string, code: ReadStream): Promise<any> {
|
||||
return await AppwriteCall(this.functions.createTag(functionId, command, code));
|
||||
}
|
||||
public async listTags(id: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC'): Promise<TagList | undefined> {
|
||||
return await AppwriteCall<TagList>(this.functions.listTags(id, search, offset, limit, orderType));
|
||||
}
|
||||
public async getTag(functionId: string, tagId: string): Promise<any> {
|
||||
return await AppwriteCall(this.functions.getTag(functionId, tagId));
|
||||
}
|
||||
public async deleteTag(functionId: string, tagId: string): Promise<void> {
|
||||
return await AppwriteCall(this.functions.deleteTag(functionId, tagId));
|
||||
}
|
||||
public async createExecution(functionId: string, data?: string): Promise<any> {
|
||||
return await AppwriteCall(this.functions.createExecution(functionId, data));
|
||||
}
|
||||
public async listExecutions(functionId: string, search?: string, limit?: number, offset?: number, orderType?: 'ASC' | 'DESC'): Promise<ExecutionList | undefined> {
|
||||
return await AppwriteCall(this.functions.listExecutions(functionId, search, offset, limit, orderType));
|
||||
}
|
||||
public async getExecution(functionId: string, executionId: string): Promise<Execution | undefined> {
|
||||
return await AppwriteCall(this.functions.getExecution(functionId, executionId));
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import { ReadStream } from 'node:fs';
|
||||
import { Client, FilesList, StorageClient } from "../appwrite";
|
||||
import { AppwriteSDK } from '../constants';
|
||||
import AppwriteCall from "../utils/AppwriteCall";
|
||||
|
@ -12,4 +13,8 @@ export class Storage {
|
|||
public async listFiles(): Promise<FilesList | undefined> {
|
||||
return await AppwriteCall(this.storage.listFiles());
|
||||
}
|
||||
|
||||
public async createFile(file: ReadStream): Promise<void> {
|
||||
return await AppwriteCall(this.storage.createFile(file));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Client } from "./appwrite";
|
||||
import { Database } from "./appwrite/Database";
|
||||
import { Functions } from './appwrite/Functions';
|
||||
import { Health } from "./appwrite/Health";
|
||||
import { Storage } from "./appwrite/Storage";
|
||||
import { Users } from "./appwrite/Users";
|
||||
|
@ -12,6 +13,8 @@ export let usersClient: Users | undefined;
|
|||
export let healthClient: Health | undefined;
|
||||
export let databaseClient: Database | undefined;
|
||||
export let storageClient: Storage | undefined;
|
||||
export let functionsClient: Functions | undefined;
|
||||
|
||||
|
||||
function initAppwriteClient({ endpoint, projectId, secret, selfSigned }: AppwriteProjectConfiguration) {
|
||||
client = new AppwriteSDK.Client();
|
||||
|
@ -22,6 +25,7 @@ function initAppwriteClient({ endpoint, projectId, secret, selfSigned }: Appwrit
|
|||
healthClient = new Health(client);
|
||||
databaseClient = new Database(client);
|
||||
storageClient = new Storage(client);
|
||||
functionsClient = new Functions(client);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
@ -36,4 +40,5 @@ export function createAppwriteClient(config?: AppwriteProjectConfiguration): voi
|
|||
healthClient = undefined;
|
||||
databaseClient = undefined;
|
||||
storageClient = undefined;
|
||||
functionsClient = undefined;
|
||||
}
|
||||
|
|
9
src/commands/common/editValue.ts
Normal file
9
src/commands/common/editValue.ts
Normal 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();
|
||||
}
|
7
src/commands/functions/activateTag.ts
Normal file
7
src/commands/functions/activateTag.ts
Normal 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);
|
||||
}
|
11
src/commands/functions/copyExecutionErrors.ts
Normal file
11
src/commands/functions/copyExecutionErrors.ts
Normal 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);
|
||||
}
|
11
src/commands/functions/copyExecutionOutput.ts
Normal file
11
src/commands/functions/copyExecutionOutput.ts
Normal 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);
|
||||
}
|
10
src/commands/functions/createExecution.ts
Normal file
10
src/commands/functions/createExecution.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { functionsClient } from '../../client';
|
||||
import { ext } from '../../extensionVariables';
|
||||
import { FunctionTreeItem } from '../../tree/functions/FunctionTreeItem';
|
||||
|
||||
export async function createExecution(functionTreeItem: FunctionTreeItem): Promise<void> {
|
||||
const func = functionTreeItem.func;
|
||||
ext.outputChannel.appendLog(`Creating execution for function ${func.name}`);
|
||||
|
||||
await functionsClient?.createExecution(func.$id);
|
||||
}
|
19
src/commands/functions/createFunction.ts
Normal file
19
src/commands/functions/createFunction.ts
Normal 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);
|
||||
}
|
16
src/commands/functions/createFunctionVar.ts
Normal file
16
src/commands/functions/createFunctionVar.ts
Normal 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);
|
||||
}
|
||||
}
|
41
src/commands/functions/createTag.ts
Normal file
41
src/commands/functions/createTag.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { ProgressLocation, Uri, window } from "vscode";
|
||||
import { functionsClient, storageClient } from "../../client";
|
||||
import { getTarReadStream } from "../../utils/tar";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import * as fs from "fs";
|
||||
import { TagsTreeItem } from "../../tree/functions/tags/TagsTreeItem";
|
||||
import { selectWorkspaceFolder } from "../../utils/workspace";
|
||||
export async function createTag(item: TagsTreeItem | Uri): Promise<void> {
|
||||
if (item instanceof Uri) {
|
||||
window.withProgress({ location: ProgressLocation.Notification, title: "Creating tag..." }, async (_progress, _token) => {
|
||||
await createTagFromUri(item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (item instanceof TagsTreeItem) {
|
||||
const folder = await selectWorkspaceFolder("Select folder of your function code.");
|
||||
console.log(folder);
|
||||
window.withProgress({ location: ProgressLocation.Notification, title: "Creating tag..." }, async (_progress, _token) => {
|
||||
await createTagFromUri(Uri.parse(folder));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function createTagFromUri(uri: Uri): Promise<void> {
|
||||
const tarFilePath = await getTarReadStream(uri);
|
||||
if (functionsClient === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tarFilePath === undefined) {
|
||||
ext.outputChannel.appendLog("Error creating tar file.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await functionsClient.createTag("60b1836a8e5d9", "python ./hello.py", fs.createReadStream(tarFilePath));
|
||||
await storageClient?.createFile(fs.createReadStream(tarFilePath));
|
||||
} catch (e) {
|
||||
ext.outputChannel.appendLog("Creating tag error: " + e);
|
||||
}
|
||||
}
|
9
src/commands/functions/deleteFunction.ts
Normal file
9
src/commands/functions/deleteFunction.ts
Normal 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);
|
||||
}
|
13
src/commands/functions/deleteFunctionVar.ts
Normal file
13
src/commands/functions/deleteFunctionVar.ts
Normal 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);
|
||||
}
|
11
src/commands/functions/deleteTag.ts
Normal file
11
src/commands/functions/deleteTag.ts
Normal 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);
|
||||
}
|
15
src/commands/functions/openExecutionsInBrowser.ts
Normal file
15
src/commands/functions/openExecutionsInBrowser.ts
Normal 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);
|
||||
}
|
15
src/commands/functions/openFunctionSettingsInBrowser.ts
Normal file
15
src/commands/functions/openFunctionSettingsInBrowser.ts
Normal 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);
|
||||
}
|
13
src/commands/functions/openFunctionTagsInBrowser.ts
Normal file
13
src/commands/functions/openFunctionTagsInBrowser.ts
Normal 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);
|
||||
}
|
11
src/commands/functions/viewExecutionErrors.ts
Normal file
11
src/commands/functions/viewExecutionErrors.ts
Normal 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');
|
||||
}
|
11
src/commands/functions/viewExecutionOutput.ts
Normal file
11
src/commands/functions/viewExecutionOutput.ts
Normal 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');
|
||||
}
|
|
@ -5,7 +5,8 @@ const documentationLinks = {
|
|||
users: 'https://appwrite.io/docs/server/users',
|
||||
database: 'https://appwrite.io/docs/client/database',
|
||||
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;
|
||||
|
|
|
@ -25,6 +25,22 @@ import { viewUserPrefs } from "./users/viewUserPrefs";
|
|||
import { editPermission } from "./database/permissions/editPermission";
|
||||
import { setActiveProject } from "./project/setActiveProject";
|
||||
import { removeProject } from "./project/removeProject";
|
||||
import { createTag } from './functions/createTag';
|
||||
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 {
|
||||
constructor(private readonly context: ExtensionContext) {}
|
||||
|
@ -56,6 +72,9 @@ export function registerCommands(context: ExtensionContext): void {
|
|||
});
|
||||
};
|
||||
|
||||
/** Common **/
|
||||
registerCommand("editValue", editValue);
|
||||
|
||||
/** General **/
|
||||
registerCommand("Connect", connectAppwrite, "all");
|
||||
|
||||
|
@ -98,4 +117,23 @@ export function registerCommands(context: ExtensionContext): void {
|
|||
registerCommand("setActiveProject", setActiveProject, "all");
|
||||
registerCommand("refreshProjects", undefined, "projects");
|
||||
registerCommand("removeProject", removeProject, "all");
|
||||
|
||||
/** Functions **/
|
||||
registerCommand("refreshFunctions", undefined, "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);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { commands, Uri } from "vscode";
|
|||
import { clientConfig } from "../../client";
|
||||
import { UserTreeItem } from "../../tree/users/UserTreeItem";
|
||||
|
||||
function getConsoleUrlFromEndpoint(endpoint: string): string {
|
||||
export function getConsoleUrlFromEndpoint(endpoint: string): string {
|
||||
const url = new URL(endpoint);
|
||||
return `${url.origin}/console`;
|
||||
}
|
||||
|
|
197
src/constants.ts
197
src/constants.ts
|
@ -1,4 +1,197 @@
|
|||
import type { SDK } from './appwrite';
|
||||
import type { SDK } from "./appwrite";
|
||||
|
||||
// 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",
|
||||
];
|
||||
|
|
|
@ -4,6 +4,7 @@ import { registerCommands } from "./commands/registerCommands";
|
|||
import { ext } from "./extensionVariables";
|
||||
import { getActiveProjectConfiguration } from "./settings";
|
||||
import { DatabaseTreeItemProvider } from "./tree/database/DatabaseTreeItemProvider";
|
||||
import { FunctionsTreeItemProvider } from './tree/functions/FunctionsTreeItemProvider';
|
||||
import { HealthTreeItemProvider } from "./tree/health/HealthTreeItemProvider";
|
||||
import { ProjectsTreeItemProvider } from "./tree/projects/ProjectsTreeItemProvider";
|
||||
import { StorageTreeItemProvider } from "./tree/storage/StorageTreeItemProvider";
|
||||
|
@ -16,12 +17,14 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
const databaseTreeItemProvider = new DatabaseTreeItemProvider();
|
||||
const storageTreeItemProvider = new StorageTreeItemProvider();
|
||||
const projectsTreeItemProvider = new ProjectsTreeItemProvider();
|
||||
const functionsTreeItemProvider = new FunctionsTreeItemProvider();
|
||||
|
||||
vscode.window.registerTreeDataProvider("Users", userTreeItemProvider);
|
||||
vscode.window.registerTreeDataProvider("Health", healthTreeItemProvider);
|
||||
vscode.window.registerTreeDataProvider("Database", databaseTreeItemProvider);
|
||||
vscode.window.registerTreeDataProvider("Storage", storageTreeItemProvider);
|
||||
vscode.window.registerTreeDataProvider("Projects", projectsTreeItemProvider);
|
||||
vscode.window.registerTreeDataProvider("Functions", functionsTreeItemProvider);
|
||||
|
||||
const activeProject = await getActiveProjectConfiguration();
|
||||
createAppwriteClient(activeProject);
|
||||
|
@ -35,6 +38,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
|
|||
database: databaseTreeItemProvider,
|
||||
storage: storageTreeItemProvider,
|
||||
projects: projectsTreeItemProvider,
|
||||
functions: functionsTreeItemProvider
|
||||
};
|
||||
|
||||
registerCommands(context);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ExtensionContext } from "vscode";
|
||||
import { DatabaseTreeItemProvider } from './tree/database/DatabaseTreeItemProvider';
|
||||
import { FunctionsTreeItemProvider } from './tree/functions/FunctionsTreeItemProvider';
|
||||
import { HealthTreeItemProvider } from './tree/health/HealthTreeItemProvider';
|
||||
import { ProjectsTreeItemProvider } from './tree/projects/ProjectsTreeItemProvider';
|
||||
import { StorageTreeItemProvider } from './tree/storage/StorageTreeItemProvider';
|
||||
|
@ -12,12 +13,13 @@ export type AppwriteTree = {
|
|||
database?: DatabaseTreeItemProvider;
|
||||
storage?: StorageTreeItemProvider;
|
||||
projects?: ProjectsTreeItemProvider;
|
||||
functions?: FunctionsTreeItemProvider;
|
||||
};
|
||||
|
||||
export type Ext = {
|
||||
context?: ExtensionContext;
|
||||
outputChannel?: AppwriteOutputChannel;
|
||||
outputChannel: AppwriteOutputChannel;
|
||||
tree?: AppwriteTree;
|
||||
};
|
||||
|
||||
export const ext: Ext = {};
|
||||
export const ext: Ext = {} as Ext;
|
||||
|
|
13
src/tree/common/EditableTreeItemBase.ts
Normal file
13
src/tree/common/EditableTreeItemBase.ts
Normal 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>;
|
||||
}
|
38
src/tree/common/EnumEditableTreeItem.ts
Normal file
38
src/tree/common/EnumEditableTreeItem.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
18
src/tree/common/SimpleEditableTreeItem.ts
Normal file
18
src/tree/common/SimpleEditableTreeItem.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
22
src/tree/common/StringEditableTreeItem.ts
Normal file
22
src/tree/common/StringEditableTreeItem.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,11 +41,11 @@ export class DatabaseTreeItemProvider implements vscode.TreeDataProvider<vscode.
|
|||
|
||||
const collectionsList = await AppwriteCall<CollectionsList, CollectionsList>(databaseSdk.listCollections());
|
||||
if (collectionsList) {
|
||||
const userTreeItems = collectionsList.collections.map((collection: Collection) => new CollectionTreeItem(collection, this)) ?? [];
|
||||
const collectionTreeItems = collectionsList.collections.map((collection: Collection) => new CollectionTreeItem(collection, this)) ?? [];
|
||||
const headerItem: vscode.TreeItem = {
|
||||
label: `Total collections: ${collectionsList.sum}`,
|
||||
};
|
||||
return [headerItem, ...userTreeItems];
|
||||
return [headerItem, ...collectionTreeItems];
|
||||
}
|
||||
|
||||
return [{ label: "No collections found" }];
|
||||
|
|
30
src/tree/functions/FunctionTreeItem.ts
Normal file
30
src/tree/functions/FunctionTreeItem.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { MarkdownString, ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
|
||||
import { Function } from "../../appwrite";
|
||||
import { AppwriteTreeItemBase } from "../../ui/AppwriteTreeItemBase";
|
||||
import { msToDate } from '../../utils/date';
|
||||
import { ExecutionsTreeItem } from './executions/ExecutionsTreeItem';
|
||||
import { FunctionsTreeItemProvider } from './FunctionsTreeItemProvider';
|
||||
import { FunctionSettingsTreeItem } from './settings/FunctionSettingsTreeItem';
|
||||
import { TagsTreeItem } from './tags/TagsTreeItem';
|
||||
|
||||
export class FunctionTreeItem extends AppwriteTreeItemBase {
|
||||
constructor(public func: Function, public readonly provider: FunctionsTreeItemProvider) {
|
||||
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[]> {
|
||||
return [new FunctionSettingsTreeItem(this), new TagsTreeItem(this), new ExecutionsTreeItem(this)];
|
||||
}
|
||||
|
||||
public async refresh(): Promise<void> {
|
||||
this.provider.refreshChild(this);
|
||||
}
|
||||
|
||||
collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
|
||||
contextValue = "function";
|
||||
|
||||
iconPath = new ThemeIcon("symbol-event");
|
||||
}
|
52
src/tree/functions/FunctionsTreeItemProvider.ts
Normal file
52
src/tree/functions/FunctionsTreeItemProvider.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import * as vscode from "vscode";
|
||||
import { client } from "../../client";
|
||||
import { Function, FunctionsList } from "../../appwrite";
|
||||
import { AppwriteSDK } from "../../constants";
|
||||
import { AppwriteTreeItemBase } from "../../ui/AppwriteTreeItemBase";
|
||||
import { ext } from "../../extensionVariables";
|
||||
import { EventEmitter, TreeItem } from "vscode";
|
||||
import { FunctionTreeItem } from "./FunctionTreeItem";
|
||||
|
||||
export class FunctionsTreeItemProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
|
||||
private _onDidChangeTreeData: EventEmitter<TreeItem | undefined | void> = new EventEmitter<TreeItem | undefined | void>();
|
||||
|
||||
readonly onDidChangeTreeData: vscode.Event<vscode.TreeItem | undefined | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
refresh(): void {
|
||||
ext.outputChannel?.appendLine("Refreshing functions tree provider...");
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
|
||||
refreshChild(child: vscode.TreeItem): void {
|
||||
this._onDidChangeTreeData.fire(child);
|
||||
}
|
||||
|
||||
getTreeItem(element: vscode.TreeItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
async getChildren(parent?: AppwriteTreeItemBase | TreeItem): Promise<vscode.TreeItem[]> {
|
||||
if (client === undefined) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
if (parent === undefined) {
|
||||
const functionsSdk = new AppwriteSDK.Functions(client);
|
||||
|
||||
const list: FunctionsList = await functionsSdk.list();
|
||||
|
||||
if (list) {
|
||||
const functionTreeItems = list.functions.map((func: Function) => new FunctionTreeItem(func, this)) ?? [];
|
||||
return functionTreeItems;
|
||||
}
|
||||
|
||||
return [{ label: "No functions found" }];
|
||||
}
|
||||
|
||||
if (parent instanceof AppwriteTreeItemBase) {
|
||||
return parent.getChildren?.() ?? [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
86
src/tree/functions/executions/ExecutionTreeItem.ts
Normal file
86
src/tree/functions/executions/ExecutionTreeItem.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import { MarkdownString, ThemeColor, ThemeIcon, TreeItem } from "vscode";
|
||||
import { Execution, ExecutionStatus } from "../../../appwrite";
|
||||
import { functionsClient } from "../../../client";
|
||||
import { ext } from "../../../extensionVariables";
|
||||
import { msToDate } from "../../../utils/date";
|
||||
import { ExecutionsTreeItem } from "./ExecutionsTreeItem";
|
||||
|
||||
const executionStatusIcons: Record<ExecutionStatus, ThemeIcon> = {
|
||||
processing: new ThemeIcon("loading"),
|
||||
waiting: new ThemeIcon("circle-outline"),
|
||||
completed: new ThemeIcon("circle-filled", new ThemeColor("testing.iconPassed")),
|
||||
failed: new ThemeIcon("circle-filled", new ThemeColor("testing.iconFailed")),
|
||||
};
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
export class ExecutionTreeItem extends TreeItem {
|
||||
public isAutoRefreshing: boolean = false;
|
||||
private refreshCount: number = 0;
|
||||
|
||||
constructor(public readonly parent: ExecutionsTreeItem, public readonly execution: Execution) {
|
||||
super(execution.$id);
|
||||
this.label = this.getLabel(execution);
|
||||
this.iconPath = executionStatusIcons[execution.status];
|
||||
const md = `Id: ${execution.$id} \nCreated: ${this.getCreated(execution)} \nTrigger: ${execution.trigger}`;
|
||||
this.tooltip = new MarkdownString(md);
|
||||
this.description = execution.trigger;
|
||||
this.contextValue = this.getContextValue(execution);
|
||||
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
|
||||
this.autoRefresh();
|
||||
}
|
||||
|
||||
async autoRefresh(): Promise<void> {
|
||||
if (!this.isAutoRefreshing) {
|
||||
return;
|
||||
}
|
||||
this.refreshCount++;
|
||||
ext.outputChannel.appendLog("Refreshing execution.");
|
||||
const execution = await functionsClient?.getExecution(this.parent.parent.func.$id, this.execution.$id);
|
||||
|
||||
if (!execution) {
|
||||
ext.outputChannel.appendLog("Execution is undefined");
|
||||
this.isAutoRefreshing = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.contextValue = this.getContextValue(execution);
|
||||
this.iconPath = executionStatusIcons[execution.status];
|
||||
this.label = this.getLabel(execution);
|
||||
this.isAutoRefreshing = execution.status === "processing" || execution.status === "waiting";
|
||||
|
||||
ext.tree?.functions?.refreshChild(this);
|
||||
await sleep(1000);
|
||||
this.autoRefresh();
|
||||
}
|
||||
|
||||
getLabel(execution: Execution): string {
|
||||
if (execution.status === "completed" || execution.status === "failed") {
|
||||
return `${this.getCreated(execution)} (${execution.time.toPrecision(2)}s)`;
|
||||
}
|
||||
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 {
|
||||
return msToDate(execution.dateCreated);
|
||||
}
|
||||
}
|
27
src/tree/functions/executions/ExecutionsTreeItem.ts
Normal file
27
src/tree/functions/executions/ExecutionsTreeItem.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
|
||||
import { Execution, ExecutionList } from '../../../appwrite';
|
||||
import { functionsClient } from "../../../client";
|
||||
import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
|
||||
import { ExecutionTreeItem } from './ExecutionTreeItem';
|
||||
import { FunctionTreeItem } from '../FunctionTreeItem';
|
||||
|
||||
export class ExecutionsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
|
||||
constructor(public readonly parent: FunctionTreeItem) {
|
||||
super(parent, "Executions");
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeItem[]> {
|
||||
if (!functionsClient) {
|
||||
return [];
|
||||
}
|
||||
const executions: ExecutionList | undefined = await functionsClient.listExecutions(this.parent.func.$id, undefined, undefined, undefined, 'DESC');
|
||||
const children = executions?.executions.map((execution: Execution) => new ExecutionTreeItem(this, execution)) ?? [new TreeItem('No exeuctions.')];
|
||||
return children;
|
||||
}
|
||||
|
||||
collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
|
||||
contextValue = "executions";
|
||||
|
||||
iconPath = new ThemeIcon("history");
|
||||
}
|
31
src/tree/functions/settings/EventsTreeItem.ts
Normal file
31
src/tree/functions/settings/EventsTreeItem.ts
Normal 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();
|
||||
}
|
||||
}
|
45
src/tree/functions/settings/FunctionSettingsTreeItem.ts
Normal file
45
src/tree/functions/settings/FunctionSettingsTreeItem.ts
Normal 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");
|
||||
}
|
43
src/tree/functions/settings/NameTreeItem.ts
Normal file
43
src/tree/functions/settings/NameTreeItem.ts
Normal 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);
|
||||
}
|
||||
}
|
44
src/tree/functions/settings/ScheduleTreeItem.ts
Normal file
44
src/tree/functions/settings/ScheduleTreeItem.ts
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
48
src/tree/functions/settings/TimeoutTreeItem.ts
Normal file
48
src/tree/functions/settings/TimeoutTreeItem.ts
Normal 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`;
|
||||
}
|
||||
}
|
64
src/tree/functions/settings/VarTreeItem.ts
Normal file
64
src/tree/functions/settings/VarTreeItem.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
21
src/tree/functions/settings/VarsTreeItem.ts
Normal file
21
src/tree/functions/settings/VarsTreeItem.ts
Normal 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;
|
||||
}
|
17
src/tree/functions/tags/TagTreeItem.ts
Normal file
17
src/tree/functions/tags/TagTreeItem.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { MarkdownString, ThemeIcon, TreeItem } from "vscode";
|
||||
import { Tag } from '../../../appwrite';
|
||||
import { msToDate } from '../../../utils/date';
|
||||
import { TagsTreeItem } from './TagsTreeItem';
|
||||
|
||||
export class TagTreeItem extends TreeItem {
|
||||
constructor(public readonly parent: TagsTreeItem, public readonly tag: Tag) {
|
||||
super(tag.$id);
|
||||
const func = parent.parent.func;
|
||||
const active = func.tag === tag.$id;
|
||||
this.label = `${msToDate(tag.dateCreated)}${active ? ' (Active)' : ''}`;
|
||||
this.description = tag.$id;
|
||||
this.iconPath = new ThemeIcon(active ? 'circle-filled' : 'circle-outline');
|
||||
this.contextValue = `tag${active ? '_active' : ''}`;
|
||||
this.tooltip = new MarkdownString(`ID: ${tag.$id} \nCreated: ${msToDate(tag.dateCreated)} \nCommand: ${tag.command} \nSize: ${tag.size}B`);
|
||||
}
|
||||
}
|
25
src/tree/functions/tags/TagsTreeItem.ts
Normal file
25
src/tree/functions/tags/TagsTreeItem.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from "vscode";
|
||||
import { functionsClient } from "../../../client";
|
||||
import { AppwriteTreeItemBase } from '../../../ui/AppwriteTreeItemBase';
|
||||
import { FunctionTreeItem } from '../FunctionTreeItem';
|
||||
import { TagTreeItem } from './TagTreeItem';
|
||||
|
||||
export class TagsTreeItem extends AppwriteTreeItemBase<FunctionTreeItem> {
|
||||
constructor(public readonly parent: FunctionTreeItem) {
|
||||
super(parent, "Tags");
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeItem[]> {
|
||||
if (!functionsClient) {
|
||||
return [];
|
||||
}
|
||||
const tags = await functionsClient.listTags(this.parent.func.$id);
|
||||
return tags?.tags.sort((a, b) => b.dateCreated - a.dateCreated).map((tag) => new TagTreeItem(this, tag)) ?? [new TreeItem('No tags.')];
|
||||
}
|
||||
|
||||
collapsibleState = TreeItemCollapsibleState.Collapsed;
|
||||
|
||||
contextValue = "tags";
|
||||
|
||||
iconPath = new ThemeIcon("tag");
|
||||
}
|
|
@ -10,7 +10,7 @@ export default function AppwriteCall<T, R = T>(
|
|||
): Promise<R | undefined> {
|
||||
return promise.then(
|
||||
(successResp) => {
|
||||
ext.outputChannel?.appendLog("Appwrite call success");
|
||||
ext.outputChannel?.appendLog(`Appwrite call success:`);
|
||||
if (onSuccess) {
|
||||
return onSuccess((successResp as unknown) as T);
|
||||
}
|
||||
|
|
5
src/utils/date.ts
Normal file
5
src/utils/date.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import dayjs = require('dayjs');
|
||||
|
||||
export function msToDate(ms: number): string {
|
||||
return dayjs(ms).format("LTS");
|
||||
}
|
31
src/utils/tar.ts
Normal file
31
src/utils/tar.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import tar = require("tar");
|
||||
import { Uri, window, workspace } from "vscode";
|
||||
import { ext } from "../extensionVariables";
|
||||
import * as path from "path";
|
||||
import * as os from "os";
|
||||
import * as fs from "fs";
|
||||
|
||||
export async function getTarReadStream(folder: Uri): Promise<string | undefined> {
|
||||
try {
|
||||
const folderName = path.basename(folder.path);
|
||||
|
||||
const tarName = `${folderName}.tar.gz`;
|
||||
const cwd = path.resolve(folder.fsPath, '..');
|
||||
if (cwd === undefined) {
|
||||
window.showErrorMessage("No workspace open.");
|
||||
return;
|
||||
}
|
||||
ext.outputChannel.appendLog(`Creating '${tarName}' in '${workspace.workspaceFolders?.[0].uri.fsPath}'...`);
|
||||
|
||||
const tarFilePath = path.join(os.tmpdir(), tarName);
|
||||
|
||||
tar.create({ gzip: true, cwd: cwd }, [path.relative(cwd, folder.fsPath)]).pipe(fs.createWriteStream(tarFilePath));
|
||||
|
||||
ext.outputChannel.appendLog(`Created ${tarFilePath}`);
|
||||
|
||||
return tarFilePath;
|
||||
|
||||
} catch (e) {
|
||||
ext.outputChannel?.appendLog("Error creating tar.gz: " + e);
|
||||
}
|
||||
}
|
0
src/utils/validation.ts
Normal file
0
src/utils/validation.ts
Normal file
84
src/utils/workspace.ts
Normal file
84
src/utils/workspace.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from "path";
|
||||
import * as vscode from "vscode";
|
||||
import { QuickPickItem } from 'vscode';
|
||||
|
||||
export interface IAzureQuickPickItem<T = undefined> extends QuickPickItem {
|
||||
/**
|
||||
* An optional id to uniquely identify this item across sessions, used in persisting previous selections
|
||||
* If not specified, a hash of the label will be used
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
data: T;
|
||||
|
||||
/**
|
||||
* Callback to use when this item is picked, instead of returning the pick
|
||||
* Only applies when used as part of an `AzureWizard`
|
||||
* This is not compatible with `canPickMany`
|
||||
*/
|
||||
onPicked?: () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* The group that this pick belongs to. Set `IAzureQuickPickOptions.enableGrouping` for this property to take effect
|
||||
* Only applies when used as part of an `AzureWizard`
|
||||
*/
|
||||
group?: string;
|
||||
|
||||
/**
|
||||
* Optionally used to suppress persistence for this item, defaults to `false`
|
||||
*/
|
||||
suppressPersistence?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export async function selectWorkspaceFolder(placeHolder: string): Promise<string> {
|
||||
return await selectWorkspaceItem(placeHolder, {
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
defaultUri:
|
||||
vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0
|
||||
? vscode.workspace.workspaceFolders[0].uri
|
||||
: undefined,
|
||||
openLabel: "Select",
|
||||
});
|
||||
}
|
||||
|
||||
export async function selectWorkspaceFile(placeHolder: string, fileExtensions?: string[]): Promise<string> {
|
||||
const filters: { [name: string]: string[] } = {};
|
||||
if (fileExtensions) {
|
||||
filters.Artifacts = fileExtensions;
|
||||
}
|
||||
return await selectWorkspaceItem(placeHolder, {
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
openLabel: "Select",
|
||||
filters: filters,
|
||||
});
|
||||
}
|
||||
|
||||
export async function selectWorkspaceItem(placeHolder: string, options: vscode.OpenDialogOptions): Promise<string> {
|
||||
let folder: IAzureQuickPickItem<string | undefined> | undefined;
|
||||
if (vscode.workspace.workspaceFolders) {
|
||||
const folderPicks: IAzureQuickPickItem<string | undefined>[] = await Promise.all(
|
||||
vscode.workspace.workspaceFolders.map((f: vscode.WorkspaceFolder) => {
|
||||
return { label: path.basename(f.uri.fsPath), description: f.uri.fsPath, data: f.uri.fsPath };
|
||||
})
|
||||
);
|
||||
|
||||
folderPicks.push({ label: "$(file-directory) Browse...", description: "", data: undefined });
|
||||
folder = await vscode.window.showQuickPick(folderPicks, { placeHolder });
|
||||
}
|
||||
|
||||
if (folder?.data) {
|
||||
return folder.data;
|
||||
} else {
|
||||
return (await vscode.window.showOpenDialog(options))?.[0].fsPath ?? '';
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue