mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-06-11 17:12:47 +02:00
(feat) Add ability to disable plugins and show bundled plugins (#337)
This commit is contained in:
@@ -36,3 +36,4 @@ out
|
|||||||
tmp
|
tmp
|
||||||
.zed
|
.zed
|
||||||
codebook.toml
|
codebook.toml
|
||||||
|
target
|
||||||
|
|||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.3.7/schema.json",
|
"$schema": "https://biomejs.dev/schemas/2.3.10/schema.json",
|
||||||
"linter": {
|
"linter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"rules": {
|
"rules": {
|
||||||
@@ -39,13 +39,13 @@
|
|||||||
"!**/dist",
|
"!**/dist",
|
||||||
"!**/build",
|
"!**/build",
|
||||||
"!scripts",
|
"!scripts",
|
||||||
"!packages/plugin-runtime",
|
|
||||||
"!packages/plugin-runtime-types",
|
|
||||||
"!src-tauri",
|
"!src-tauri",
|
||||||
"!src-web/tailwind.config.cjs",
|
"!src-web/tailwind.config.cjs",
|
||||||
"!src-web/postcss.config.cjs",
|
"!src-web/postcss.config.cjs",
|
||||||
"!src-web/vite.config.ts",
|
"!src-web/vite.config.ts",
|
||||||
"!src-web/routeTree.gen.ts"
|
"!src-web/routeTree.gen.ts",
|
||||||
|
"!packages/plugin-runtime-types/lib",
|
||||||
|
"!**/bindings"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+34
-50
@@ -15,6 +15,7 @@
|
|||||||
"plugins-external/template-function-faker",
|
"plugins-external/template-function-faker",
|
||||||
"plugins/action-copy-curl",
|
"plugins/action-copy-curl",
|
||||||
"plugins/action-copy-grpcurl",
|
"plugins/action-copy-grpcurl",
|
||||||
|
"plugins/action-send-folder",
|
||||||
"plugins/auth-apikey",
|
"plugins/auth-apikey",
|
||||||
"plugins/auth-aws",
|
"plugins/auth-aws",
|
||||||
"plugins/auth-basic",
|
"plugins/auth-basic",
|
||||||
@@ -4047,6 +4048,10 @@
|
|||||||
"resolved": "plugins/action-copy-grpcurl",
|
"resolved": "plugins/action-copy-grpcurl",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@yaak/action-send-folder": {
|
||||||
|
"resolved": "plugins/action-send-folder",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@yaak/auth-apikey": {
|
"node_modules/@yaak/auth-apikey": {
|
||||||
"resolved": "plugins/auth-apikey",
|
"resolved": "plugins/auth-apikey",
|
||||||
"link": true
|
"link": true
|
||||||
@@ -4690,9 +4695,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/async": {
|
"node_modules/async": {
|
||||||
"version": "3.2.4",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
|
||||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/async-each": {
|
"node_modules/async-each": {
|
||||||
@@ -8719,9 +8724,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/glob": {
|
"node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -10773,12 +10778,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jws": {
|
"node_modules/jws": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
|
||||||
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
|
"integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jwa": "^1.4.1",
|
"jwa": "^1.4.2",
|
||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -11591,9 +11596,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mdast-util-to-hast": {
|
"node_modules/mdast-util-to-hast": {
|
||||||
"version": "13.2.0",
|
"version": "13.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
|
||||||
"integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==",
|
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.0",
|
"@types/hast": "^3.0.0",
|
||||||
@@ -13559,15 +13564,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-to-postmanv2": {
|
"node_modules/openapi-to-postmanv2": {
|
||||||
"version": "5.0.0",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/openapi-to-postmanv2/-/openapi-to-postmanv2-5.7.0.tgz",
|
||||||
"integrity": "sha512-ousMf9rXKen9tscJQ0H8BE+hfgOvFRb2SspYwGnQTmnjzkPNejHUQpNmRUIK/gZ6ZwiNWAnQCKWNogaZXUlFew==",
|
"integrity": "sha512-fhwjpL+CF1kOKX5G1m5E/tnZWc9KuSXZA4oOGZv9c4NzKtC3ecUHxtp4KTvwzFh8/b+ssSZo+blC/3OK4/9ySA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "8.11.0",
|
"ajv": "^8.11.0",
|
||||||
"ajv-draft-04": "1.0.0",
|
"ajv-draft-04": "1.0.0",
|
||||||
"ajv-formats": "2.1.1",
|
"ajv-formats": "2.1.1",
|
||||||
"async": "3.2.4",
|
"async": "3.2.6",
|
||||||
"commander": "2.20.3",
|
"commander": "2.20.3",
|
||||||
"graphlib": "2.1.8",
|
"graphlib": "2.1.8",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
@@ -13589,22 +13594,6 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/openapi-to-postmanv2/node_modules/ajv": {
|
|
||||||
"version": "8.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
|
|
||||||
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"fast-deep-equal": "^3.1.1",
|
|
||||||
"json-schema-traverse": "^1.0.0",
|
|
||||||
"require-from-string": "^2.0.2",
|
|
||||||
"uri-js": "^4.2.2"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/epoberezkin"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/openapi-to-postmanv2/node_modules/ajv-draft-04": {
|
"node_modules/openapi-to-postmanv2/node_modules/ajv-draft-04": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
|
||||||
@@ -14554,9 +14543,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/qs": {
|
"node_modules/qs": {
|
||||||
"version": "6.14.0",
|
"version": "6.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz",
|
||||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
"integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"side-channel": "^1.1.0"
|
"side-channel": "^1.1.0"
|
||||||
@@ -14786,16 +14775,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-syntax-highlighter": {
|
"node_modules/react-syntax-highlighter": {
|
||||||
"version": "15.6.1",
|
"version": "15.6.6",
|
||||||
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz",
|
||||||
"integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==",
|
"integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.3.1",
|
"@babel/runtime": "^7.3.1",
|
||||||
"highlight.js": "^10.4.1",
|
"highlight.js": "^10.4.1",
|
||||||
"highlightjs-vue": "^1.0.0",
|
"highlightjs-vue": "^1.0.0",
|
||||||
"lowlight": "^1.17.0",
|
"lowlight": "^1.17.0",
|
||||||
"prismjs": "^1.27.0",
|
"prismjs": "^1.30.0",
|
||||||
"refractor": "^3.6.0"
|
"refractor": "^3.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@@ -18029,15 +18018,6 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uri-js": {
|
|
||||||
"version": "4.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
|
||||||
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": "^2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/urix": {
|
"node_modules/urix": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
|
||||||
@@ -19110,6 +19090,10 @@
|
|||||||
"name": "@yaak/action-copy-grpcurl",
|
"name": "@yaak/action-copy-grpcurl",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
},
|
},
|
||||||
|
"plugins/action-send-folder": {
|
||||||
|
"name": "@yaak/action-send-folder",
|
||||||
|
"version": "0.1.0"
|
||||||
|
},
|
||||||
"plugins/auth-apikey": {
|
"plugins/auth-apikey": {
|
||||||
"name": "@yaak/auth-apikey",
|
"name": "@yaak/auth-apikey",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"plugins-external/template-function-faker",
|
"plugins-external/template-function-faker",
|
||||||
"plugins/action-copy-curl",
|
"plugins/action-copy-curl",
|
||||||
"plugins/action-copy-grpcurl",
|
"plugins/action-copy-grpcurl",
|
||||||
|
"plugins/action-send-folder",
|
||||||
"plugins/auth-apikey",
|
"plugins/auth-apikey",
|
||||||
"plugins/auth-aws",
|
"plugins/auth-aws",
|
||||||
"plugins/auth-basic",
|
"plugins/auth-basic",
|
||||||
|
|||||||
+1
-1
@@ -423,7 +423,7 @@ export type ListCookieNamesRequest = {};
|
|||||||
|
|
||||||
export type ListCookieNamesResponse = { names: Array<string>, };
|
export type ListCookieNamesResponse = { names: Array<string>, };
|
||||||
|
|
||||||
export type ListFoldersRequest = Record<string, never>;
|
export type ListFoldersRequest = {};
|
||||||
|
|
||||||
export type ListFoldersResponse = { folders: Array<Folder>, };
|
export type ListFoldersResponse = { folders: Array<Folder>, };
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {
|
import type {
|
||||||
CallHttpAuthenticationActionArgs,
|
CallHttpAuthenticationActionArgs,
|
||||||
CallHttpAuthenticationRequest,
|
CallHttpAuthenticationRequest,
|
||||||
CallHttpAuthenticationResponse,
|
CallHttpAuthenticationResponse,
|
||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
GetHttpAuthenticationSummaryResponse,
|
GetHttpAuthenticationSummaryResponse,
|
||||||
HttpAuthenticationAction,
|
HttpAuthenticationAction,
|
||||||
} from '../bindings/gen_events';
|
} from '../bindings/gen_events';
|
||||||
import { MaybePromise } from '../helpers';
|
import type { MaybePromise } from '../helpers';
|
||||||
import { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
type AddDynamicMethod<T> = {
|
type AddDynamicMethod<T> = {
|
||||||
dynamic?: (
|
dynamic?: (
|
||||||
@@ -16,6 +16,7 @@ type AddDynamicMethod<T> = {
|
|||||||
) => MaybePromise<Partial<T> | null | undefined>;
|
) => MaybePromise<Partial<T> | null | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern
|
||||||
type AddDynamic<T> = T extends any
|
type AddDynamic<T> = T extends any
|
||||||
? T extends { inputs?: FormInput[] }
|
? T extends { inputs?: FormInput[] }
|
||||||
? Omit<T, 'inputs'> & {
|
? Omit<T, 'inputs'> & {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { FilterResponse } from '../bindings/gen_events';
|
import type { FilterResponse } from '../bindings/gen_events';
|
||||||
import type { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
export type FilterPlugin = {
|
export type FilterPlugin = {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
|
import type { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events';
|
||||||
import type { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
export type GrpcRequestActionPlugin = GrpcRequestAction & {
|
export type GrpcRequestActionPlugin = GrpcRequestAction & {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ImportResources } from '../bindings/gen_events';
|
import type { ImportResources } from '../bindings/gen_events';
|
||||||
import { AtLeast, MaybePromise } from '../helpers';
|
import type { AtLeast, MaybePromise } from '../helpers';
|
||||||
import type { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
type RootFields = 'name' | 'id' | 'model';
|
type RootFields = 'name' | 'id' | 'model';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
|
import type { CallTemplateFunctionArgs, FormInput, TemplateFunction } from '../bindings/gen_events';
|
||||||
import { MaybePromise } from '../helpers';
|
import type { MaybePromise } from '../helpers';
|
||||||
import { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
type AddDynamicMethod<T> = {
|
type AddDynamicMethod<T> = {
|
||||||
dynamic?: (
|
dynamic?: (
|
||||||
@@ -9,6 +9,7 @@ type AddDynamicMethod<T> = {
|
|||||||
) => MaybePromise<Partial<T> | null | undefined>;
|
) => MaybePromise<Partial<T> | null | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: distributive conditional type pattern
|
||||||
type AddDynamic<T> = T extends any
|
type AddDynamic<T> = T extends any
|
||||||
? T extends { inputs?: FormInput[] }
|
? T extends { inputs?: FormInput[] }
|
||||||
? Omit<T, 'inputs'> & {
|
? Omit<T, 'inputs'> & {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
import { Theme } from '../bindings/gen_events';
|
import type { Theme } from '../bindings/gen_events';
|
||||||
|
|
||||||
export type ThemePlugin = Theme;
|
export type ThemePlugin = Theme;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { AuthenticationPlugin } from './AuthenticationPlugin';
|
import type { AuthenticationPlugin } from './AuthenticationPlugin';
|
||||||
|
|
||||||
import type { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
import type { FilterPlugin } from './FilterPlugin';
|
import type { FilterPlugin } from './FilterPlugin';
|
||||||
import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
import type { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin';
|
||||||
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
|
||||||
import type { WebsocketRequestActionPlugin } from './WebsocketRequestActionPlugin';
|
import type { WebsocketRequestActionPlugin } from './WebsocketRequestActionPlugin';
|
||||||
import type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
import type { WorkspaceActionPlugin } from './WorkspaceActionPlugin';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PluginContext } from '@yaakapp-internal/plugins';
|
import type { PluginContext } from '@yaakapp-internal/plugins';
|
||||||
import type { BootRequest, InternalEvent } from '@yaakapp/api';
|
import type { BootRequest, InternalEvent } from '@yaakapp/api';
|
||||||
import type { EventChannel } from './EventChannel';
|
import type { EventChannel } from './EventChannel';
|
||||||
import { PluginInstance, PluginWorkerData } from './PluginInstance';
|
import { PluginInstance, type PluginWorkerData } from './PluginInstance';
|
||||||
|
|
||||||
export class PluginHandle {
|
export class PluginHandle {
|
||||||
#instance: PluginInstance;
|
#instance: PluginInstance;
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { applyFormInputDefaults, validateTemplateFunctionArgs } from '@yaakapp-internal/lib/templateFunction';
|
|
||||||
import {
|
import {
|
||||||
|
applyFormInputDefaults,
|
||||||
|
validateTemplateFunctionArgs,
|
||||||
|
} from '@yaakapp-internal/lib/templateFunction';
|
||||||
|
import type {
|
||||||
BootRequest,
|
BootRequest,
|
||||||
DeleteKeyValueResponse,
|
DeleteKeyValueResponse,
|
||||||
DeleteModelResponse,
|
DeleteModelResponse,
|
||||||
@@ -12,9 +15,13 @@ import {
|
|||||||
HttpAuthenticationAction,
|
HttpAuthenticationAction,
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
HttpRequestAction,
|
HttpRequestAction,
|
||||||
|
ImportResources,
|
||||||
InternalEvent,
|
InternalEvent,
|
||||||
InternalEventPayload,
|
InternalEventPayload,
|
||||||
ListCookieNamesResponse,
|
ListCookieNamesResponse,
|
||||||
|
ListFoldersResponse,
|
||||||
|
ListHttpRequestsRequest,
|
||||||
|
ListHttpRequestsResponse,
|
||||||
ListWorkspacesResponse,
|
ListWorkspacesResponse,
|
||||||
PluginContext,
|
PluginContext,
|
||||||
PromptTextResponse,
|
PromptTextResponse,
|
||||||
@@ -22,11 +29,12 @@ import {
|
|||||||
RenderHttpRequestResponse,
|
RenderHttpRequestResponse,
|
||||||
SendHttpRequestResponse,
|
SendHttpRequestResponse,
|
||||||
TemplateFunction,
|
TemplateFunction,
|
||||||
|
TemplateRenderRequest,
|
||||||
TemplateRenderResponse,
|
TemplateRenderResponse,
|
||||||
UpsertModelResponse,
|
UpsertModelResponse,
|
||||||
WindowInfoResponse,
|
WindowInfoResponse,
|
||||||
} from '@yaakapp-internal/plugins';
|
} from '@yaakapp-internal/plugins';
|
||||||
import { Context, PluginDefinition } from '@yaakapp/api';
|
import type { Context, PluginDefinition } from '@yaakapp/api';
|
||||||
import console from 'node:console';
|
import console from 'node:console';
|
||||||
import { type Stats, statSync, watch } from 'node:fs';
|
import { type Stats, statSync, watch } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -56,7 +64,7 @@ export class PluginInstance {
|
|||||||
await this.#onMessage(event);
|
await this.#onMessage(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#mod = {} as any;
|
this.#mod = {};
|
||||||
|
|
||||||
const fileChangeCallback = async () => {
|
const fileChangeCallback = async () => {
|
||||||
await this.#mod?.dispose?.();
|
await this.#mod?.dispose?.();
|
||||||
@@ -120,8 +128,7 @@ export class PluginInstance {
|
|||||||
if (reply != null) {
|
if (reply != null) {
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
type: 'import_response',
|
type: 'import_response',
|
||||||
// deno-lint-ignore no-explicit-any
|
resources: reply.resources as ImportResources,
|
||||||
resources: reply.resources as any,
|
|
||||||
};
|
};
|
||||||
this.#sendPayload(context, replyPayload, replyId);
|
this.#sendPayload(context, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
@@ -262,7 +269,7 @@ export class PluginInstance {
|
|||||||
payload.type === 'get_template_function_config_request' &&
|
payload.type === 'get_template_function_config_request' &&
|
||||||
Array.isArray(this.#mod?.templateFunctions)
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
) {
|
) {
|
||||||
let templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
|
const templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
|
||||||
if (templateFunction == null) {
|
if (templateFunction == null) {
|
||||||
this.#sendEmpty(context, replyId);
|
this.#sendEmpty(context, replyId);
|
||||||
return;
|
return;
|
||||||
@@ -381,10 +388,7 @@ export class PluginInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (payload.type === 'call_folder_action_request' && Array.isArray(this.#mod.folderActions)) {
|
||||||
payload.type === 'call_folder_action_request' &&
|
|
||||||
Array.isArray(this.#mod.folderActions)
|
|
||||||
) {
|
|
||||||
const action = this.#mod.folderActions[payload.index];
|
const action = this.#mod.folderActions[payload.index];
|
||||||
if (typeof action?.onSelect === 'function') {
|
if (typeof action?.onSelect === 'function') {
|
||||||
await action.onSelect(ctx, payload.args);
|
await action.onSelect(ctx, payload.args);
|
||||||
@@ -703,12 +707,15 @@ export class PluginInstance {
|
|||||||
return httpRequest;
|
return httpRequest;
|
||||||
},
|
},
|
||||||
list: async (args?: { folderId?: string }) => {
|
list: async (args?: { folderId?: string }) => {
|
||||||
const payload = {
|
const payload: InternalEventPayload = {
|
||||||
type: 'list_http_requests_request',
|
type: 'list_http_requests_request',
|
||||||
folderId: args?.folderId,
|
folderId: args?.folderId,
|
||||||
} as any;
|
} satisfies ListHttpRequestsRequest & { type: 'list_http_requests_request' };
|
||||||
const { httpRequests } = await this.#sendForReply<any>(context, payload);
|
const { httpRequests } = await this.#sendForReply<ListHttpRequestsResponse>(
|
||||||
return httpRequests as any[];
|
context,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
return httpRequests;
|
||||||
},
|
},
|
||||||
create: async (args) => {
|
create: async (args) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
@@ -747,11 +754,9 @@ export class PluginInstance {
|
|||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
const payload = {
|
const payload = { type: 'list_folders_request' } as const;
|
||||||
type: 'list_folders_request',
|
const { folders } = await this.#sendForReply<ListFoldersResponse>(context, payload);
|
||||||
} as any;
|
return folders;
|
||||||
const { folders } = await this.#sendForReply<any>(context, payload);
|
|
||||||
return folders as any[];
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cookies: {
|
cookies: {
|
||||||
@@ -774,9 +779,10 @@ export class PluginInstance {
|
|||||||
* Invoke Yaak's template engine to render a value. If the value is a nested type
|
* Invoke Yaak's template engine to render a value. If the value is a nested type
|
||||||
* (eg. object), it will be recursively rendered.
|
* (eg. object), it will be recursively rendered.
|
||||||
*/
|
*/
|
||||||
render: async (args) => {
|
render: async (args: TemplateRenderRequest) => {
|
||||||
const payload = { type: 'template_render_request', ...args } as const;
|
const payload = { type: 'template_render_request', ...args } as const;
|
||||||
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
|
const result = await this.#sendForReply<TemplateRenderResponse>(context, payload);
|
||||||
|
// biome-ignore lint/suspicious/noExplicitAny: That's okay
|
||||||
return result.data as any;
|
return result.data as any;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -809,15 +815,19 @@ export class PluginInstance {
|
|||||||
workspace: {
|
workspace: {
|
||||||
list: async () => {
|
list: async () => {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: 'list_workspaces_request'
|
type: 'list_workspaces_request',
|
||||||
} as InternalEventPayload;
|
} as InternalEventPayload;
|
||||||
const response = await this.#sendForReply<ListWorkspacesResponse>(context, payload);
|
const response = await this.#sendForReply<ListWorkspacesResponse>(context, payload);
|
||||||
return response.workspaces.map((w) => ({
|
return response.workspaces.map((w) => {
|
||||||
id: w.id,
|
// Internal workspace info includes label field not in public API
|
||||||
name: w.name,
|
type WorkspaceInfoInternal = typeof w & { label?: string };
|
||||||
// Hide label from plugin authors, but keep it for internal routing
|
return {
|
||||||
_label: (w as any).label as string,
|
id: w.id,
|
||||||
}));
|
name: w.name,
|
||||||
|
// Hide label from plugin authors, but keep it for internal routing
|
||||||
|
_label: (w as WorkspaceInfoInternal).label as string,
|
||||||
|
};
|
||||||
|
});
|
||||||
},
|
},
|
||||||
withContext: (workspaceHandle: { id: string; name: string; _label?: string }) => {
|
withContext: (workspaceHandle: { id: string; name: string; _label?: string }) => {
|
||||||
// Create a new context with the workspace's window label
|
// Create a new context with the workspace's window label
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { CallHttpAuthenticationActionArgs, CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
import type { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||||
import { Context, DynamicAuthenticationArg, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
import type {
|
||||||
|
CallHttpAuthenticationActionArgs,
|
||||||
|
CallTemplateFunctionArgs,
|
||||||
|
} from '@yaakapp-internal/plugins';
|
||||||
|
|
||||||
export async function applyDynamicFormInput(
|
export async function applyDynamicFormInput(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
@@ -18,15 +21,28 @@ export async function applyDynamicFormInput(
|
|||||||
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
|
args: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[],
|
||||||
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
|
callArgs: CallTemplateFunctionArgs | CallHttpAuthenticationActionArgs,
|
||||||
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
|
): Promise<(DynamicTemplateFunctionArg | DynamicAuthenticationArg)[]> {
|
||||||
const resolvedArgs: any[] = [];
|
const resolvedArgs: (DynamicTemplateFunctionArg | DynamicAuthenticationArg)[] = [];
|
||||||
for (const { dynamic, ...arg } of args) {
|
for (const { dynamic, ...arg } of args) {
|
||||||
const newArg: any = {
|
const dynamicResult =
|
||||||
|
typeof dynamic === 'function'
|
||||||
|
? await dynamic(
|
||||||
|
ctx,
|
||||||
|
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
|
||||||
|
)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const newArg = {
|
||||||
...arg,
|
...arg,
|
||||||
...(typeof dynamic === 'function' ? await dynamic(ctx, callArgs as any) : undefined),
|
...dynamicResult,
|
||||||
};
|
} as DynamicTemplateFunctionArg | DynamicAuthenticationArg;
|
||||||
|
|
||||||
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
|
if ('inputs' in newArg && Array.isArray(newArg.inputs)) {
|
||||||
try {
|
try {
|
||||||
newArg.inputs = await applyDynamicFormInput(ctx, newArg.inputs, callArgs as any);
|
newArg.inputs = await applyDynamicFormInput(
|
||||||
|
ctx,
|
||||||
|
newArg.inputs as DynamicTemplateFunctionArg[],
|
||||||
|
callArgs as CallTemplateFunctionArgs & CallHttpAuthenticationActionArgs,
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Failed to apply dynamic form input', e);
|
console.error('Failed to apply dynamic form input', e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import WebSocket from 'ws';
|
|||||||
|
|
||||||
const port = process.env.PORT;
|
const port = process.env.PORT;
|
||||||
if (!port) {
|
if (!port) {
|
||||||
throw new Error('Plugin runtime missing PORT')
|
throw new Error('Plugin runtime missing PORT');
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = process.env.HOST;
|
const host = process.env.HOST;
|
||||||
if (!host) {
|
if (!host) {
|
||||||
throw new Error('Plugin runtime missing HOST')
|
throw new Error('Plugin runtime missing HOST');
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginToAppEvents = new EventChannel();
|
const pluginToAppEvents = new EventChannel();
|
||||||
@@ -26,7 +26,7 @@ ws.on('message', async (e: Buffer) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
ws.on('open', () => console.log('Plugin runtime connected to websocket'));
|
ws.on('open', () => console.log('Plugin runtime connected to websocket'));
|
||||||
ws.on('error', (err: any) => console.error('Plugin runtime websocket error', err));
|
ws.on('error', (err: unknown) => console.error('Plugin runtime websocket error', err));
|
||||||
ws.on('close', (code: number) => console.log('Plugin runtime websocket closed', code));
|
ws.on('close', (code: number) => console.log('Plugin runtime websocket closed', code));
|
||||||
|
|
||||||
// Listen for incoming events from plugins
|
// Listen for incoming events from plugins
|
||||||
@@ -39,7 +39,12 @@ async function handleIncoming(msg: string) {
|
|||||||
const pluginEvent: InternalEvent = JSON.parse(msg);
|
const pluginEvent: InternalEvent = JSON.parse(msg);
|
||||||
// Handle special event to bootstrap plugin
|
// Handle special event to bootstrap plugin
|
||||||
if (pluginEvent.payload.type === 'boot_request') {
|
if (pluginEvent.payload.type === 'boot_request') {
|
||||||
const plugin = new PluginHandle(pluginEvent.pluginRefId, pluginEvent.context, pluginEvent.payload, pluginToAppEvents);
|
const plugin = new PluginHandle(
|
||||||
|
pluginEvent.pluginRefId,
|
||||||
|
pluginEvent.context,
|
||||||
|
pluginEvent.payload,
|
||||||
|
pluginToAppEvents,
|
||||||
|
);
|
||||||
plugins[pluginEvent.pluginRefId] = plugin;
|
plugins[pluginEvent.pluginRefId] = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
import process from "node:process";
|
import process from 'node:process';
|
||||||
|
|
||||||
export function interceptStdout(
|
export function interceptStdout(intercept: (text: string) => string) {
|
||||||
intercept: (text: string) => string,
|
|
||||||
) {
|
|
||||||
const old_stdout_write = process.stdout.write;
|
const old_stdout_write = process.stdout.write;
|
||||||
const old_stderr_write = process.stderr.write;
|
const old_stderr_write = process.stderr.write;
|
||||||
|
|
||||||
process.stdout.write = (function (write) {
|
process.stdout.write = ((write) =>
|
||||||
return function (text: string) {
|
((text: string, ...args: never[]) => {
|
||||||
arguments[0] = interceptor(text, intercept);
|
write.call(process.stdout, interceptor(text, intercept), ...args);
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
write.apply(process.stdout, arguments as any);
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}) as typeof process.stdout.write)(process.stdout.write);
|
||||||
})(process.stdout.write);
|
|
||||||
|
|
||||||
process.stderr.write = (function (write) {
|
process.stderr.write = ((write) =>
|
||||||
return function (text: string) {
|
((text: string, ...args: never[]) => {
|
||||||
arguments[0] = interceptor(text, intercept);
|
write.call(process.stderr, interceptor(text, intercept), ...args);
|
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
write.apply(process.stderr, arguments as any);
|
|
||||||
return true;
|
return true;
|
||||||
};
|
}) as typeof process.stderr.write)(process.stderr.write);
|
||||||
})(process.stderr.write);
|
|
||||||
|
|
||||||
// puts back to original
|
// puts back to original
|
||||||
return function unhook() {
|
return function unhook() {
|
||||||
@@ -32,6 +24,5 @@ export function interceptStdout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function interceptor(text: string, fn: (text: string) => string) {
|
function interceptor(text: string, fn: (text: string) => string) {
|
||||||
return fn(text).replace(/\n$/, "") +
|
return fn(text).replace(/\n$/, '') + (fn(text) && /\n$/.test(text) ? '\n' : '');
|
||||||
(fn(text) && /\n$/.test(text) ? "\n" : "");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,15 @@ export function migrateTemplateFunctionSelectOptions(
|
|||||||
): TemplateFunctionPlugin {
|
): TemplateFunctionPlugin {
|
||||||
const migratedArgs = f.args.map((a) => {
|
const migratedArgs = f.args.map((a) => {
|
||||||
if (a.type === 'select') {
|
if (a.type === 'select') {
|
||||||
a.options = a.options.map((o) => ({
|
// Migrate old options that had 'name' instead of 'label'
|
||||||
...o,
|
type LegacyOption = { label?: string; value: string; name?: string };
|
||||||
label: o.label || (o as any).name,
|
a.options = a.options.map((o) => {
|
||||||
}));
|
const legacy = o as LegacyOption;
|
||||||
|
return {
|
||||||
|
label: legacy.label ?? legacy.name ?? '',
|
||||||
|
value: legacy.value,
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { applyFormInputDefaults } from '@yaakapp-internal/lib/templateFunction';
|
import { applyFormInputDefaults } from '@yaakapp-internal/lib/templateFunction';
|
||||||
import { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||||
import { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
import type { Context, DynamicTemplateFunctionArg } from '@yaakapp/api';
|
||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
import { applyDynamicFormInput } from '../src/common';
|
import { applyDynamicFormInput } from '../src/common';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@yaak/action-send-folder",
|
||||||
|
"displayName": "Send All",
|
||||||
|
"description": "Send all HTTP requests in a folder sequentially",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mountain-loop/yaak.git",
|
||||||
|
"directory": "plugins/action-send-folder"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "yaakcli build",
|
||||||
|
"dev": "yaakcli dev"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import type { PluginDefinition } from '@yaakapp/api';
|
||||||
|
|
||||||
|
export const plugin: PluginDefinition = {
|
||||||
|
folderActions: [
|
||||||
|
{
|
||||||
|
label: 'Send All',
|
||||||
|
icon: 'send_horizontal',
|
||||||
|
async onSelect(ctx, args) {
|
||||||
|
const targetFolder = args.folder;
|
||||||
|
|
||||||
|
// Get all folders and HTTP requests
|
||||||
|
const [allFolders, allRequests] = await Promise.all([
|
||||||
|
ctx.folder.list(),
|
||||||
|
ctx.httpRequest.list(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Build a set of all folder IDs that are descendants of the target folder
|
||||||
|
const folderIds = new Set<string>([targetFolder.id]);
|
||||||
|
const addDescendants = (parentId: string) => {
|
||||||
|
for (const folder of allFolders) {
|
||||||
|
if (folder.folderId === parentId && !folderIds.has(folder.id)) {
|
||||||
|
folderIds.add(folder.id);
|
||||||
|
addDescendants(folder.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addDescendants(targetFolder.id);
|
||||||
|
|
||||||
|
// Filter HTTP requests to those in the target folder or its descendants
|
||||||
|
const requestsToSend = allRequests.filter(
|
||||||
|
(req) => req.folderId != null && folderIds.has(req.folderId),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (requestsToSend.length === 0) {
|
||||||
|
await ctx.toast.show({
|
||||||
|
message: 'No requests in folder',
|
||||||
|
icon: 'info',
|
||||||
|
color: 'info',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send each request sequentially
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
for (const request of requestsToSend) {
|
||||||
|
try {
|
||||||
|
await ctx.httpRequest.send({ httpRequest: request });
|
||||||
|
successCount++;
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
console.error(`Failed to send request ${request.id}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show summary toast
|
||||||
|
if (errorCount === 0) {
|
||||||
|
await ctx.toast.show({
|
||||||
|
message: `Sent ${successCount} request${successCount !== 1 ? 's' : ''}`,
|
||||||
|
icon: 'send_horizontal',
|
||||||
|
color: 'success',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await ctx.toast.show({
|
||||||
|
message: `Sent ${successCount}, failed ${errorCount}`,
|
||||||
|
icon: 'alert_triangle',
|
||||||
|
color: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json"
|
||||||
|
}
|
||||||
@@ -1276,7 +1276,7 @@ async fn cmd_install_plugin<R: Runtime>(
|
|||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
) -> YaakResult<Plugin> {
|
) -> YaakResult<Plugin> {
|
||||||
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &directory).await?;
|
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &directory, true).await?;
|
||||||
|
|
||||||
Ok(app_handle.db().upsert_plugin(
|
Ok(app_handle.db().upsert_plugin(
|
||||||
&Plugin { directory: directory.into(), url, ..Default::default() },
|
&Plugin { directory: directory.into(), url, ..Default::default() },
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ use yaak_plugins::error::Error::PluginErr;
|
|||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{
|
||||||
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
|
Color, DeleteKeyValueResponse, EmptyPayload, ErrorResponse, FindHttpResponsesResponse,
|
||||||
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
|
GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent,
|
||||||
InternalEventPayload, ListCookieNamesResponse, ListHttpRequestsResponse, ListWorkspacesResponse,
|
InternalEventPayload, ListCookieNamesResponse, ListHttpRequestsResponse,
|
||||||
RenderGrpcRequestResponse, RenderHttpRequestResponse, SendHttpRequestResponse,
|
ListWorkspacesResponse, RenderGrpcRequestResponse, RenderHttpRequestResponse,
|
||||||
SetKeyValueResponse, ShowToastRequest, TemplateRenderResponse, WindowInfoResponse,
|
SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, TemplateRenderResponse,
|
||||||
WindowNavigateEvent, WorkspaceInfo,
|
WindowInfoResponse, WindowNavigateEvent, WorkspaceInfo,
|
||||||
};
|
};
|
||||||
use yaak_plugins::plugin_handle::PluginHandle;
|
use yaak_plugins::plugin_handle::PluginHandle;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
@@ -107,7 +107,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
Workspace(app_handle.db().upsert_workspace(m, &UpdateSource::Plugin)?)
|
Workspace(app_handle.db().upsert_workspace(m, &UpdateSource::Plugin)?)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(PluginErr("Upsert not supported for this model type".into()).into())
|
return Err(PluginErr("Upsert not supported for this model type".into()).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -118,14 +118,10 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
InternalEventPayload::DeleteModelRequest(req) => {
|
InternalEventPayload::DeleteModelRequest(req) => {
|
||||||
let model = match req.model.as_str() {
|
let model = match req.model.as_str() {
|
||||||
"http_request" => AnyModel::HttpRequest(
|
"http_request" => AnyModel::HttpRequest(
|
||||||
app_handle
|
app_handle.db().delete_http_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||||
.db()
|
|
||||||
.delete_http_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
|
||||||
),
|
),
|
||||||
"grpc_request" => AnyModel::GrpcRequest(
|
"grpc_request" => AnyModel::GrpcRequest(
|
||||||
app_handle
|
app_handle.db().delete_grpc_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||||
.db()
|
|
||||||
.delete_grpc_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
|
||||||
),
|
),
|
||||||
"websocket_request" => AnyModel::WebsocketRequest(
|
"websocket_request" => AnyModel::WebsocketRequest(
|
||||||
app_handle
|
app_handle
|
||||||
@@ -133,17 +129,13 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
|
|||||||
.delete_websocket_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
.delete_websocket_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||||
),
|
),
|
||||||
"folder" => AnyModel::Folder(
|
"folder" => AnyModel::Folder(
|
||||||
app_handle
|
app_handle.db().delete_folder_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||||
.db()
|
|
||||||
.delete_folder_by_id(&req.id, &UpdateSource::Plugin)?,
|
|
||||||
),
|
),
|
||||||
"environment" => AnyModel::Environment(
|
"environment" => AnyModel::Environment(
|
||||||
app_handle
|
app_handle.db().delete_environment_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||||
.db()
|
|
||||||
.delete_environment_by_id(&req.id, &UpdateSource::Plugin)?,
|
|
||||||
),
|
),
|
||||||
_ => {
|
_ => {
|
||||||
return Err(PluginErr("Delete not supported for this model type".into()).into())
|
return Err(PluginErr("Delete not supported for this model type".into()).into());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
[lints.rust]
|
||||||
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cargo-clippy"))'] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-plugin = { workspace = true, features = ["build"] }
|
tauri-plugin = { workspace = true, features = ["build"] }
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ impl<'a> DbContext<'a> {
|
|||||||
self.find_one(PluginIden::Id, id)
|
self.find_one(PluginIden::Id, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_plugin_by_directory(&self, directory: &str) -> Option<Plugin> {
|
||||||
|
self.find_optional(PluginIden::Directory, directory)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_plugins(&self) -> Result<Vec<Plugin>> {
|
pub fn list_plugins(&self) -> Result<Vec<Plugin>> {
|
||||||
self.find_all()
|
self.find_all()
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -423,7 +423,7 @@ export type ListCookieNamesRequest = {};
|
|||||||
|
|
||||||
export type ListCookieNamesResponse = { names: Array<string>, };
|
export type ListCookieNamesResponse = { names: Array<string>, };
|
||||||
|
|
||||||
export type ListFoldersRequest = Record<string, never>;
|
export type ListFoldersRequest = {};
|
||||||
|
|
||||||
export type ListFoldersResponse = { folders: Array<Folder>, };
|
export type ListFoldersResponse = { folders: Array<Folder>, };
|
||||||
|
|
||||||
|
|||||||
@@ -1351,8 +1351,8 @@ pub struct ListHttpRequestsResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default)]
|
||||||
#[ts(export, export_to = "gen_events.ts")]
|
#[ts(export, type = "{}", export_to = "gen_events.ts")]
|
||||||
pub struct ListFoldersRequest {}
|
pub struct ListFoldersRequest {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ pub async fn download_and_install<R: Runtime>(
|
|||||||
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
|
zip_extract::extract(Cursor::new(&bytes), &plugin_dir, true)?;
|
||||||
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
|
info!("Extracted plugin {} to {}", plugin_version.id, plugin_dir_str);
|
||||||
|
|
||||||
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &plugin_dir_str).await?;
|
plugin_manager.add_plugin_by_dir(&PluginContext::new(&window), &plugin_dir_str, true).await?;
|
||||||
|
|
||||||
window.db().upsert_plugin(
|
window.db().upsert_plugin(
|
||||||
&Plugin {
|
&Plugin {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::error::Error::{
|
use crate::error::Error::{
|
||||||
AuthPluginNotFound, ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr,
|
self, AuthPluginNotFound, ClientNotInitializedErr, PluginErr, PluginNotFoundErr,
|
||||||
|
UnknownEventErr,
|
||||||
};
|
};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
@@ -35,10 +36,10 @@ use tokio::net::TcpListener;
|
|||||||
use tokio::sync::mpsc::error::TrySendError;
|
use tokio::sync::mpsc::error::TrySendError;
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
use tokio::time::{Instant, timeout};
|
use tokio::time::{Instant, timeout};
|
||||||
use yaak_models::models::Environment;
|
use yaak_models::models::{Environment, Plugin};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::render::make_vars_hashmap;
|
use yaak_models::render::make_vars_hashmap;
|
||||||
use yaak_models::util::generate_id;
|
use yaak_models::util::{UpdateSource, generate_id};
|
||||||
use yaak_templates::error::Error::RenderError;
|
use yaak_templates::error::Error::RenderError;
|
||||||
use yaak_templates::error::Result as TemplateResult;
|
use yaak_templates::error::Result as TemplateResult;
|
||||||
use yaak_templates::{RenderErrorBehavior, RenderOptions, render_json_value_raw};
|
use yaak_templates::{RenderErrorBehavior, RenderOptions, render_json_value_raw};
|
||||||
@@ -46,18 +47,13 @@ use yaak_templates::{RenderErrorBehavior, RenderOptions, render_json_value_raw};
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PluginManager {
|
pub struct PluginManager {
|
||||||
subscribers: Arc<Mutex<HashMap<String, mpsc::Sender<InternalEvent>>>>,
|
subscribers: Arc<Mutex<HashMap<String, mpsc::Sender<InternalEvent>>>>,
|
||||||
plugins: Arc<Mutex<Vec<PluginHandle>>>,
|
plugin_handles: Arc<Mutex<Vec<PluginHandle>>>,
|
||||||
kill_tx: tokio::sync::watch::Sender<bool>,
|
kill_tx: tokio::sync::watch::Sender<bool>,
|
||||||
ws_service: Arc<PluginRuntimeServerWebsocket>,
|
ws_service: Arc<PluginRuntimeServerWebsocket>,
|
||||||
vendored_plugin_dir: PathBuf,
|
vendored_plugin_dir: PathBuf,
|
||||||
pub(crate) installed_plugin_dir: PathBuf,
|
pub(crate) installed_plugin_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct PluginCandidate {
|
|
||||||
dir: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PluginManager {
|
impl PluginManager {
|
||||||
pub fn new<R: Runtime>(app_handle: AppHandle<R>) -> PluginManager {
|
pub fn new<R: Runtime>(app_handle: AppHandle<R>) -> PluginManager {
|
||||||
let (events_tx, mut events_rx) = mpsc::channel(128);
|
let (events_tx, mut events_rx) = mpsc::channel(128);
|
||||||
@@ -80,7 +76,7 @@ impl PluginManager {
|
|||||||
.join("installed-plugins");
|
.join("installed-plugins");
|
||||||
|
|
||||||
let plugin_manager = PluginManager {
|
let plugin_manager = PluginManager {
|
||||||
plugins: Default::default(),
|
plugin_handles: Default::default(),
|
||||||
subscribers: Default::default(),
|
subscribers: Default::default(),
|
||||||
ws_service: Arc::new(ws_service.clone()),
|
ws_service: Arc::new(ws_service.clone()),
|
||||||
kill_tx: kill_server_tx,
|
kill_tx: kill_server_tx,
|
||||||
@@ -109,7 +105,7 @@ impl PluginManager {
|
|||||||
|
|
||||||
// Handle when client plugin runtime disconnects
|
// Handle when client plugin runtime disconnects
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
while let Some(_) = client_disconnect_rx.recv().await {
|
while (client_disconnect_rx.recv().await).is_some() {
|
||||||
// Happens when the app is closed
|
// Happens when the app is closed
|
||||||
info!("Plugin runtime client disconnected");
|
info!("Plugin runtime client disconnected");
|
||||||
}
|
}
|
||||||
@@ -163,10 +159,10 @@ impl PluginManager {
|
|||||||
plugin_manager
|
plugin_manager
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_plugin_dirs<R: Runtime>(
|
async fn list_available_plugins<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
) -> Vec<PluginCandidate> {
|
) -> Result<Vec<Plugin>> {
|
||||||
let plugins_dir = if is_dev() {
|
let plugins_dir = if is_dev() {
|
||||||
// Use plugins directly for easy development
|
// Use plugins directly for easy development
|
||||||
env::current_dir()
|
env::current_dir()
|
||||||
@@ -178,18 +174,27 @@ impl PluginManager {
|
|||||||
|
|
||||||
info!("Loading bundled plugins from {plugins_dir:?}");
|
info!("Loading bundled plugins from {plugins_dir:?}");
|
||||||
|
|
||||||
let bundled_plugin_dirs: Vec<PluginCandidate> = read_plugins_dir(&plugins_dir)
|
// Read bundled plugin directories from disk
|
||||||
|
let bundled_plugin_dirs: Vec<String> = read_plugins_dir(&plugins_dir)
|
||||||
.await
|
.await
|
||||||
.expect(format!("Failed to read plugins dir: {:?}", plugins_dir).as_str())
|
.expect(&format!("Failed to read plugins dir: {:?}", plugins_dir));
|
||||||
.iter()
|
|
||||||
.map(|d| PluginCandidate { dir: d.into() })
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let plugins = app_handle.db().list_plugins().unwrap_or_default();
|
// Ensure all bundled plugins make it into the database
|
||||||
let installed_plugin_dirs: Vec<PluginCandidate> =
|
for dir in &bundled_plugin_dirs {
|
||||||
plugins.iter().map(|p| PluginCandidate { dir: p.directory.to_owned() }).collect();
|
if app_handle.db().get_plugin_by_directory(dir).is_none() {
|
||||||
|
app_handle.db().upsert_plugin(
|
||||||
|
&Plugin {
|
||||||
|
directory: dir.clone(),
|
||||||
|
enabled: true,
|
||||||
|
url: None,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&UpdateSource::Background,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[bundled_plugin_dirs, installed_plugin_dirs].concat()
|
Ok(app_handle.db().list_plugins()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
|
pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
|
||||||
@@ -202,16 +207,18 @@ impl PluginManager {
|
|||||||
plugin_context: &PluginContext,
|
plugin_context: &PluginContext,
|
||||||
plugin: &PluginHandle,
|
plugin: &PluginHandle,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Terminate the plugin
|
// Terminate the plugin if it's enabled
|
||||||
self.send_to_plugin_and_wait(
|
if plugin.enabled {
|
||||||
plugin_context,
|
self.send_to_plugin_and_wait(
|
||||||
plugin,
|
plugin_context,
|
||||||
&InternalEventPayload::TerminateRequest,
|
plugin,
|
||||||
)
|
&InternalEventPayload::TerminateRequest,
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the plugin from the list
|
// Remove the plugin from the list
|
||||||
let mut plugins = self.plugins.lock().await;
|
let mut plugins = self.plugin_handles.lock().await;
|
||||||
let pos = plugins.iter().position(|p| p.ref_id == plugin.ref_id);
|
let pos = plugins.iter().position(|p| p.ref_id == plugin.ref_id);
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
plugins.remove(pos);
|
plugins.remove(pos);
|
||||||
@@ -220,7 +227,12 @@ impl PluginManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_plugin_by_dir(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
|
pub async fn add_plugin_by_dir(
|
||||||
|
&self,
|
||||||
|
plugin_context: &PluginContext,
|
||||||
|
dir: &str,
|
||||||
|
enabled: bool,
|
||||||
|
) -> Result<()> {
|
||||||
info!("Adding plugin by dir {dir}");
|
info!("Adding plugin by dir {dir}");
|
||||||
|
|
||||||
let maybe_tx = self.ws_service.app_to_plugin_events_tx.lock().await;
|
let maybe_tx = self.ws_service.app_to_plugin_events_tx.lock().await;
|
||||||
@@ -228,32 +240,32 @@ impl PluginManager {
|
|||||||
None => return Err(ClientNotInitializedErr),
|
None => return Err(ClientNotInitializedErr),
|
||||||
Some(tx) => tx,
|
Some(tx) => tx,
|
||||||
};
|
};
|
||||||
let plugin_handle = PluginHandle::new(dir, tx.clone())?;
|
let plugin_handle = PluginHandle::new(dir, enabled, tx.clone())?;
|
||||||
let dir_path = Path::new(dir);
|
let dir_path = Path::new(dir);
|
||||||
let is_vendored = dir_path.starts_with(self.vendored_plugin_dir.as_path());
|
let is_vendored = dir_path.starts_with(self.vendored_plugin_dir.as_path());
|
||||||
let is_installed = dir_path.starts_with(self.installed_plugin_dir.as_path());
|
let is_installed = dir_path.starts_with(self.installed_plugin_dir.as_path());
|
||||||
|
|
||||||
// Boot the plugin
|
// Boot the plugin if it's enabled
|
||||||
let event = timeout(
|
if enabled {
|
||||||
Duration::from_secs(5),
|
let event = self
|
||||||
self.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
plugin_context,
|
plugin_context,
|
||||||
&plugin_handle,
|
&plugin_handle,
|
||||||
&InternalEventPayload::BootRequest(BootRequest {
|
&InternalEventPayload::BootRequest(BootRequest {
|
||||||
dir: dir.to_string(),
|
dir: dir.to_string(),
|
||||||
watch: !is_vendored && !is_installed,
|
watch: !is_vendored && !is_installed,
|
||||||
}),
|
}),
|
||||||
),
|
)
|
||||||
)
|
.await?;
|
||||||
.await??;
|
|
||||||
|
|
||||||
if !matches!(event.payload, InternalEventPayload::BootResponse) {
|
if !matches!(event.payload, InternalEventPayload::BootResponse) {
|
||||||
return Err(UnknownEventErr);
|
return Err(UnknownEventErr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut plugins = self.plugins.lock().await;
|
let mut plugin_handles = self.plugin_handles.lock().await;
|
||||||
plugins.retain(|p| p.dir != dir);
|
plugin_handles.retain(|p| p.dir != dir);
|
||||||
plugins.push(plugin_handle.clone());
|
plugin_handles.push(plugin_handle.clone());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -263,22 +275,24 @@ impl PluginManager {
|
|||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
plugin_context: &PluginContext,
|
plugin_context: &PluginContext,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
info!("Initializing all plugins");
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let candidates = self.list_plugin_dirs(app_handle).await;
|
for plugin in self.list_available_plugins(app_handle).await?.clone() {
|
||||||
for candidate in candidates.clone() {
|
// First remove the plugin if it exists and is enabled
|
||||||
// First remove the plugin if it exists
|
if let Some(plugin_handle) = self.get_plugin_by_dir(&plugin.directory).await {
|
||||||
if let Some(plugin) = self.get_plugin_by_dir(candidate.dir.as_str()).await {
|
if let Err(e) = self.remove_plugin(plugin_context, &plugin_handle).await {
|
||||||
if let Err(e) = self.remove_plugin(plugin_context, &plugin).await {
|
error!("Failed to remove plugin {} {e:?}", plugin.directory);
|
||||||
error!("Failed to remove plugin {} {e:?}", candidate.dir);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Err(e) = self.add_plugin_by_dir(plugin_context, candidate.dir.as_str()).await {
|
if let Err(e) =
|
||||||
warn!("Failed to add plugin {} {e:?}", candidate.dir);
|
self.add_plugin_by_dir(plugin_context, &plugin.directory, plugin.enabled).await
|
||||||
|
{
|
||||||
|
warn!("Failed to add plugin {} {e:?}", plugin.directory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let plugins = self.plugins.lock().await;
|
let plugins = self.plugin_handles.lock().await;
|
||||||
let names = plugins.iter().map(|p| p.dir.to_string()).collect::<Vec<String>>();
|
let names = plugins.iter().map(|p| p.dir.to_string()).collect::<Vec<String>>();
|
||||||
info!(
|
info!(
|
||||||
"Initialized {} plugins in {:?}:\n - {}",
|
"Initialized {} plugins in {:?}:\n - {}",
|
||||||
@@ -324,15 +338,15 @@ impl PluginManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_plugin_by_ref_id(&self, ref_id: &str) -> Option<PluginHandle> {
|
pub async fn get_plugin_by_ref_id(&self, ref_id: &str) -> Option<PluginHandle> {
|
||||||
self.plugins.lock().await.iter().find(|p| p.ref_id == ref_id).cloned()
|
self.plugin_handles.lock().await.iter().find(|p| p.ref_id == ref_id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_plugin_by_dir(&self, dir: &str) -> Option<PluginHandle> {
|
pub async fn get_plugin_by_dir(&self, dir: &str) -> Option<PluginHandle> {
|
||||||
self.plugins.lock().await.iter().find(|p| p.dir == dir).cloned()
|
self.plugin_handles.lock().await.iter().find(|p| p.dir == dir).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_plugin_by_name(&self, name: &str) -> Option<PluginHandle> {
|
pub async fn get_plugin_by_name(&self, name: &str) -> Option<PluginHandle> {
|
||||||
for plugin in self.plugins.lock().await.iter().cloned() {
|
for plugin in self.plugin_handles.lock().await.iter().cloned() {
|
||||||
let info = plugin.info();
|
let info = plugin.info();
|
||||||
if info.name == name {
|
if info.name == name {
|
||||||
return Some(plugin);
|
return Some(plugin);
|
||||||
@@ -347,9 +361,19 @@ impl PluginManager {
|
|||||||
plugin: &PluginHandle,
|
plugin: &PluginHandle,
|
||||||
payload: &InternalEventPayload,
|
payload: &InternalEventPayload,
|
||||||
) -> Result<InternalEvent> {
|
) -> Result<InternalEvent> {
|
||||||
|
if !plugin.enabled {
|
||||||
|
return Err(Error::PluginErr(format!("Plugin {} is disabled", plugin.metadata.name)));
|
||||||
|
}
|
||||||
|
|
||||||
let events =
|
let events =
|
||||||
self.send_to_plugins_and_wait(plugin_context, payload, vec![plugin.to_owned()]).await?;
|
self.send_to_plugins_and_wait(plugin_context, payload, vec![plugin.to_owned()]).await?;
|
||||||
Ok(events.first().unwrap().to_owned())
|
Ok(events
|
||||||
|
.first()
|
||||||
|
.ok_or(Error::PluginErr(format!(
|
||||||
|
"No plugin events returned for: {}",
|
||||||
|
plugin.metadata.name
|
||||||
|
)))?
|
||||||
|
.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_and_wait(
|
async fn send_and_wait(
|
||||||
@@ -357,7 +381,7 @@ impl PluginManager {
|
|||||||
plugin_context: &PluginContext,
|
plugin_context: &PluginContext,
|
||||||
payload: &InternalEventPayload,
|
payload: &InternalEventPayload,
|
||||||
) -> Result<Vec<InternalEvent>> {
|
) -> Result<Vec<InternalEvent>> {
|
||||||
let plugins = { self.plugins.lock().await.clone() };
|
let plugins = { self.plugin_handles.lock().await.clone() };
|
||||||
self.send_to_plugins_and_wait(plugin_context, payload, plugins).await
|
self.send_to_plugins_and_wait(plugin_context, payload, plugins).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +397,7 @@ impl PluginManager {
|
|||||||
// 1. Build the events with IDs and everything
|
// 1. Build the events with IDs and everything
|
||||||
let events_to_send = plugins
|
let events_to_send = plugins
|
||||||
.iter()
|
.iter()
|
||||||
|
.filter(|p| p.enabled)
|
||||||
.map(|p| p.build_event_to_send(plugin_context, payload, None))
|
.map(|p| p.build_event_to_send(plugin_context, payload, None))
|
||||||
.collect::<Vec<InternalEvent>>();
|
.collect::<Vec<InternalEvent>>();
|
||||||
|
|
||||||
@@ -383,19 +408,28 @@ impl PluginManager {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut found_events = Vec::new();
|
let mut found_events = Vec::new();
|
||||||
|
|
||||||
while let Some(event) = rx.recv().await {
|
let collect_events = async {
|
||||||
let matched_sent_event = events_to_send
|
while let Some(event) = rx.recv().await {
|
||||||
.iter()
|
let matched_sent_event =
|
||||||
.find(|e| Some(e.id.to_owned()) == event.reply_id)
|
events_to_send.iter().any(|e| Some(e.id.to_owned()) == event.reply_id);
|
||||||
.is_some();
|
if matched_sent_event {
|
||||||
if matched_sent_event {
|
found_events.push(event.clone());
|
||||||
found_events.push(event.clone());
|
};
|
||||||
};
|
|
||||||
|
|
||||||
let found_them_all = found_events.len() == events_to_send.len();
|
let found_them_all = found_events.len() == events_to_send.len();
|
||||||
if found_them_all {
|
if found_them_all {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Timeout after 10 seconds to prevent hanging forever if plugin doesn't respond
|
||||||
|
if timeout(Duration::from_secs(5), collect_events).await.is_err() {
|
||||||
|
warn!(
|
||||||
|
"Timeout waiting for plugin responses. Got {}/{} responses",
|
||||||
|
found_events.len(),
|
||||||
|
events_to_send.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
found_events
|
found_events
|
||||||
@@ -586,7 +620,7 @@ impl PluginManager {
|
|||||||
// We don't want to fail for this op because the UI will not be able to list any auth types then
|
// We don't want to fail for this op because the UI will not be able to list any auth types then
|
||||||
let render_opt = RenderOptions { error_behavior: RenderErrorBehavior::ReturnEmpty };
|
let render_opt = RenderOptions { error_behavior: RenderErrorBehavior::ReturnEmpty };
|
||||||
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
||||||
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
let context_id = format!("{:x}", md5::compute(model_id));
|
||||||
|
|
||||||
let event = self
|
let event = self
|
||||||
.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
@@ -754,7 +788,7 @@ impl PluginManager {
|
|||||||
// We don't want to fail for this op because the UI will not be able to list any auth types then
|
// We don't want to fail for this op because the UI will not be able to list any auth types then
|
||||||
let render_opt = RenderOptions { error_behavior: RenderErrorBehavior::ReturnEmpty };
|
let render_opt = RenderOptions { error_behavior: RenderErrorBehavior::ReturnEmpty };
|
||||||
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
||||||
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
let context_id = format!("{:x}", md5::compute(model_id));
|
||||||
let event = self
|
let event = self
|
||||||
.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
&PluginContext::new(window),
|
&PluginContext::new(window),
|
||||||
@@ -804,7 +838,7 @@ impl PluginManager {
|
|||||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||||
|
|
||||||
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
let context_id = format!("{:x}", md5::compute(model_id));
|
||||||
self.send_to_plugin_and_wait(
|
self.send_to_plugin_and_wait(
|
||||||
&PluginContext::new(window),
|
&PluginContext::new(window),
|
||||||
&plugin,
|
&plugin,
|
||||||
@@ -831,7 +865,7 @@ impl PluginManager {
|
|||||||
plugin_context: &PluginContext,
|
plugin_context: &PluginContext,
|
||||||
) -> Result<CallHttpAuthenticationResponse> {
|
) -> Result<CallHttpAuthenticationResponse> {
|
||||||
let disabled = match req.values.get("disabled") {
|
let disabled = match req.values.get("disabled") {
|
||||||
Some(JsonPrimitive::Boolean(v)) => v.clone(),
|
Some(JsonPrimitive::Boolean(v)) => *v,
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ use tokio::sync::{Mutex, mpsc};
|
|||||||
pub struct PluginHandle {
|
pub struct PluginHandle {
|
||||||
pub ref_id: String,
|
pub ref_id: String,
|
||||||
pub dir: String,
|
pub dir: String,
|
||||||
|
pub enabled: bool,
|
||||||
pub(crate) to_plugin_tx: Arc<Mutex<mpsc::Sender<InternalEvent>>>,
|
pub(crate) to_plugin_tx: Arc<Mutex<mpsc::Sender<InternalEvent>>>,
|
||||||
pub(crate) metadata: PluginMetadata,
|
pub(crate) metadata: PluginMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PluginHandle {
|
impl PluginHandle {
|
||||||
pub fn new(dir: &str, tx: mpsc::Sender<InternalEvent>) -> Result<Self> {
|
pub fn new(dir: &str, enabled: bool, tx: mpsc::Sender<InternalEvent>) -> Result<Self> {
|
||||||
let ref_id = gen_id();
|
let ref_id = gen_id();
|
||||||
let metadata = get_plugin_meta(&Path::new(dir))?;
|
let metadata = get_plugin_meta(&Path::new(dir))?;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ impl PluginHandle {
|
|||||||
ref_id: ref_id.clone(),
|
ref_id: ref_id.clone(),
|
||||||
dir: dir.to_string(),
|
dir: dir.to_string(),
|
||||||
to_plugin_tx: Arc::new(Mutex::new(tx)),
|
to_plugin_tx: Arc::new(Mutex::new(tx)),
|
||||||
|
enabled,
|
||||||
metadata,
|
metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,10 +73,17 @@ impl PluginRuntimeServerWebsocket {
|
|||||||
|
|
||||||
// Skip non-text messages
|
// Skip non-text messages
|
||||||
if !msg.is_text() {
|
if !msg.is_text() {
|
||||||
return;
|
warn!("Received non-text message from plugin runtime");
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg_text = msg.into_text().unwrap();
|
let msg_text = match msg.into_text() {
|
||||||
|
Ok(text) => text,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to convert message to text: {e:?}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
let event = match serde_json::from_str::<InternalEventRawPayload>(&msg_text) {
|
let event = match serde_json::from_str::<InternalEventRawPayload>(&msg_text) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -117,9 +124,18 @@ impl PluginRuntimeServerWebsocket {
|
|||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
Some(event) => {
|
Some(event) => {
|
||||||
let event_bytes = serde_json::to_string(&event).unwrap();
|
let event_bytes = match serde_json::to_string(&event) {
|
||||||
|
Ok(bytes) => bytes,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to serialize event: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
let msg = Message::text(event_bytes);
|
let msg = Message::text(event_bytes);
|
||||||
ws_sender.send(msg).await.unwrap();
|
if let Err(e) = ws_sender.send(msg).await {
|
||||||
|
error!("Failed to send message to plugin runtime: {:?}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
export function unescape_template(template: string): any;
|
|
||||||
export function escape_template(template: string): any;
|
|
||||||
export function parse_template(template: string): any;
|
export function parse_template(template: string): any;
|
||||||
|
export function escape_template(template: string): any;
|
||||||
|
export function unescape_template(template: string): any;
|
||||||
|
|||||||
+4
-4
@@ -165,10 +165,10 @@ function takeFromExternrefTable0(idx) {
|
|||||||
* @param {string} template
|
* @param {string} template
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export function unescape_template(template) {
|
export function parse_template(template) {
|
||||||
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN;
|
||||||
const ret = wasm.unescape_template(ptr0, len0);
|
const ret = wasm.parse_template(ptr0, len0);
|
||||||
if (ret[2]) {
|
if (ret[2]) {
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
}
|
}
|
||||||
@@ -193,10 +193,10 @@ export function escape_template(template) {
|
|||||||
* @param {string} template
|
* @param {string} template
|
||||||
* @returns {any}
|
* @returns {any}
|
||||||
*/
|
*/
|
||||||
export function parse_template(template) {
|
export function unescape_template(template) {
|
||||||
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||||
const len0 = WASM_VECTOR_LEN;
|
const len0 = WASM_VECTOR_LEN;
|
||||||
const ret = wasm.parse_template(ptr0, len0);
|
const ret = wasm.unescape_template(ptr0, len0);
|
||||||
if (ret[2]) {
|
if (ret[2]) {
|
||||||
throw takeFromExternrefTable0(ret[1]);
|
throw takeFromExternrefTable0(ret[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -5,6 +5,7 @@ import { useAtomValue } from 'jotai';
|
|||||||
import type { CSSProperties, ReactNode } from 'react';
|
import type { CSSProperties, ReactNode } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||||
|
import { useFolderActions } from '../hooks/useFolderActions';
|
||||||
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
import { useLatestHttpResponse } from '../hooks/useLatestHttpResponse';
|
||||||
import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
import { sendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
@@ -30,6 +31,12 @@ interface Props {
|
|||||||
export function FolderLayout({ folder, style }: Props) {
|
export function FolderLayout({ folder, style }: Props) {
|
||||||
const folders = useAtomValue(foldersAtom);
|
const folders = useAtomValue(foldersAtom);
|
||||||
const requests = useAtomValue(allRequestsAtom);
|
const requests = useAtomValue(allRequestsAtom);
|
||||||
|
const folderActions = useFolderActions();
|
||||||
|
const sendAllAction = useMemo(
|
||||||
|
() => folderActions.find((a) => a.label === 'Send All'),
|
||||||
|
[folderActions],
|
||||||
|
);
|
||||||
|
|
||||||
const children = useMemo(() => {
|
const children = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
...folders.filter((f) => f.folderId === folder.id),
|
...folders.filter((f) => f.folderId === folder.id),
|
||||||
@@ -37,6 +44,10 @@ export function FolderLayout({ folder, style }: Props) {
|
|||||||
];
|
];
|
||||||
}, [folder.id, folders, requests]);
|
}, [folder.id, folders, requests]);
|
||||||
|
|
||||||
|
const handleSendAll = useCallback(() => {
|
||||||
|
sendAllAction?.call(folder);
|
||||||
|
}, [sendAllAction, folder]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={style} className="p-6 pt-4 overflow-y-auto @container">
|
<div style={style} className="p-6 pt-4 overflow-y-auto @container">
|
||||||
<HStack space={2} alignItems="center">
|
<HStack space={2} alignItems="center">
|
||||||
@@ -48,6 +59,8 @@ export function FolderLayout({ folder, style }: Props) {
|
|||||||
color="secondary"
|
color="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="border"
|
variant="border"
|
||||||
|
onClick={handleSendAll}
|
||||||
|
disabled={sendAllAction == null}
|
||||||
>
|
>
|
||||||
Send All
|
Send All
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import { openUrl } from '@tauri-apps/plugin-opener';
|
import { openUrl } from '@tauri-apps/plugin-opener';
|
||||||
import type { Plugin } from '@yaakapp-internal/models';
|
import type { Plugin } from '@yaakapp-internal/models';
|
||||||
import { pluginsAtom } from '@yaakapp-internal/models';
|
import { patchModel, pluginsAtom } from '@yaakapp-internal/models';
|
||||||
import type { PluginVersion } from '@yaakapp-internal/plugins';
|
import type { PluginVersion } from '@yaakapp-internal/plugins';
|
||||||
import {
|
import {
|
||||||
checkPluginUpdates,
|
checkPluginUpdates,
|
||||||
@@ -18,6 +18,7 @@ import { usePluginsKey, useRefreshPlugins } from '../../hooks/usePlugins';
|
|||||||
import { showConfirmDelete } from '../../lib/confirm';
|
import { showConfirmDelete } from '../../lib/confirm';
|
||||||
import { minPromiseMillis } from '../../lib/minPromiseMillis';
|
import { minPromiseMillis } from '../../lib/minPromiseMillis';
|
||||||
import { Button } from '../core/Button';
|
import { Button } from '../core/Button';
|
||||||
|
import { Checkbox } from '../core/Checkbox';
|
||||||
import { CountBadge } from '../core/CountBadge';
|
import { CountBadge } from '../core/CountBadge';
|
||||||
import { Icon } from '../core/Icon';
|
import { Icon } from '../core/Icon';
|
||||||
import { IconButton } from '../core/IconButton';
|
import { IconButton } from '../core/IconButton';
|
||||||
@@ -34,6 +35,8 @@ import { SelectFile } from '../SelectFile';
|
|||||||
export function SettingsPlugins() {
|
export function SettingsPlugins() {
|
||||||
const [directory, setDirectory] = useState<string | null>(null);
|
const [directory, setDirectory] = useState<string | null>(null);
|
||||||
const plugins = useAtomValue(pluginsAtom);
|
const plugins = useAtomValue(pluginsAtom);
|
||||||
|
const bundledPlugins = plugins.filter((p) => p.url == null);
|
||||||
|
const installedPlugins = plugins.filter((p) => p.url != null);
|
||||||
const createPlugin = useInstallPlugin();
|
const createPlugin = useInstallPlugin();
|
||||||
const refreshPlugins = useRefreshPlugins();
|
const refreshPlugins = useRefreshPlugins();
|
||||||
const [tab, setTab] = useState<string>();
|
const [tab, setTab] = useState<string>();
|
||||||
@@ -49,7 +52,12 @@ export function SettingsPlugins() {
|
|||||||
{
|
{
|
||||||
label: 'Installed',
|
label: 'Installed',
|
||||||
value: 'installed',
|
value: 'installed',
|
||||||
rightSlot: <CountBadge count={plugins.length} />,
|
rightSlot: <CountBadge count={installedPlugins.length} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bundled',
|
||||||
|
value: 'bundled',
|
||||||
|
rightSlot: <CountBadge count={bundledPlugins.length} />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -101,6 +109,9 @@ export function SettingsPlugins() {
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</TabContent>
|
</TabContent>
|
||||||
|
<TabContent value="bundled" className="pb-0">
|
||||||
|
<BundledPlugins />
|
||||||
|
</TabContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -119,6 +130,27 @@ function PluginTableRowForInstalledPlugin({ plugin }: { plugin: Plugin }) {
|
|||||||
name={info.name}
|
name={info.name}
|
||||||
displayName={info.displayName}
|
displayName={info.displayName}
|
||||||
url={plugin.url}
|
url={plugin.url}
|
||||||
|
showCheckbox={true}
|
||||||
|
showUninstall={true}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PluginTableRowForBundledPlugin({ plugin }: { plugin: Plugin }) {
|
||||||
|
const info = usePluginInfo(plugin.id).data;
|
||||||
|
if (info == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PluginTableRow
|
||||||
|
plugin={plugin}
|
||||||
|
version={info.version}
|
||||||
|
name={info.name}
|
||||||
|
displayName={info.displayName}
|
||||||
|
url={plugin.url}
|
||||||
|
showCheckbox={true}
|
||||||
|
showUninstall={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -134,6 +166,7 @@ function PluginTableRowForRemotePluginVersion({ pluginVersion }: { pluginVersion
|
|||||||
name={pluginVersion.name}
|
name={pluginVersion.name}
|
||||||
displayName={pluginVersion.displayName}
|
displayName={pluginVersion.displayName}
|
||||||
url={pluginVersion.url}
|
url={pluginVersion.url}
|
||||||
|
showCheckbox={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -144,12 +177,16 @@ function PluginTableRow({
|
|||||||
version,
|
version,
|
||||||
displayName,
|
displayName,
|
||||||
url,
|
url,
|
||||||
|
showCheckbox = true,
|
||||||
|
showUninstall = true,
|
||||||
}: {
|
}: {
|
||||||
plugin: Plugin | null;
|
plugin: Plugin | null;
|
||||||
name: string;
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
url: string | null;
|
url: string | null;
|
||||||
|
showCheckbox?: boolean;
|
||||||
|
showUninstall?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const updates = usePluginUpdates();
|
const updates = usePluginUpdates();
|
||||||
const latestVersion = updates.data?.plugins.find((u) => u.name === name)?.version;
|
const latestVersion = updates.data?.plugins.find((u) => u.name === name)?.version;
|
||||||
@@ -158,9 +195,26 @@ function PluginTableRow({
|
|||||||
mutationFn: (name: string) => installPlugin(name, null),
|
mutationFn: (name: string) => installPlugin(name, null),
|
||||||
});
|
});
|
||||||
const uninstall = usePromptUninstall(plugin?.id ?? null, displayName);
|
const uninstall = usePromptUninstall(plugin?.id ?? null, displayName);
|
||||||
|
const refreshPlugins = useRefreshPlugins();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
{showCheckbox && (
|
||||||
|
<TableCell className="!py-0">
|
||||||
|
<Checkbox
|
||||||
|
hideLabel
|
||||||
|
title={plugin?.enabled ? 'Disable plugin' : 'Enable plugin'}
|
||||||
|
checked={plugin?.enabled ?? false}
|
||||||
|
disabled={plugin == null}
|
||||||
|
onChange={async (enabled) => {
|
||||||
|
if (plugin) {
|
||||||
|
await patchModel(plugin, { enabled });
|
||||||
|
refreshPlugins.mutate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
<TableCell className="font-semibold">
|
<TableCell className="font-semibold">
|
||||||
{url ? (
|
{url ? (
|
||||||
<Link noUnderline href={url}>
|
<Link noUnderline href={url}>
|
||||||
@@ -170,6 +224,9 @@ function PluginTableRow({
|
|||||||
displayName
|
displayName
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<InlineCode>{name}</InlineCode>
|
||||||
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<HStack space={1.5}>
|
<HStack space={1.5}>
|
||||||
<InlineCode>{version}</InlineCode>
|
<InlineCode>{version}</InlineCode>
|
||||||
@@ -206,7 +263,7 @@ function PluginTableRow({
|
|||||||
Install
|
Install
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
{uninstall != null && (
|
{showUninstall && uninstall != null && (
|
||||||
<Button
|
<Button
|
||||||
size="xs"
|
size="xs"
|
||||||
title="Uninstall plugin"
|
title="Uninstall plugin"
|
||||||
@@ -253,6 +310,7 @@ function PluginSearch() {
|
|||||||
<Table scrollable>
|
<Table scrollable>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHeaderCell>Display Name</TableHeaderCell>
|
||||||
<TableHeaderCell>Name</TableHeaderCell>
|
<TableHeaderCell>Name</TableHeaderCell>
|
||||||
<TableHeaderCell>Version</TableHeaderCell>
|
<TableHeaderCell>Version</TableHeaderCell>
|
||||||
<TableHeaderCell />
|
<TableHeaderCell />
|
||||||
@@ -271,7 +329,7 @@ function PluginSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InstalledPlugins() {
|
function InstalledPlugins() {
|
||||||
const plugins = useAtomValue(pluginsAtom);
|
const plugins = useAtomValue(pluginsAtom).filter((p) => p.url != null);
|
||||||
|
|
||||||
return plugins.length === 0 ? (
|
return plugins.length === 0 ? (
|
||||||
<div className="pb-4">
|
<div className="pb-4">
|
||||||
@@ -285,6 +343,8 @@ function InstalledPlugins() {
|
|||||||
<Table scrollable>
|
<Table scrollable>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
<TableHeaderCell className="w-0" />
|
||||||
|
<TableHeaderCell>Display Name</TableHeaderCell>
|
||||||
<TableHeaderCell>Name</TableHeaderCell>
|
<TableHeaderCell>Name</TableHeaderCell>
|
||||||
<TableHeaderCell>Version</TableHeaderCell>
|
<TableHeaderCell>Version</TableHeaderCell>
|
||||||
<TableHeaderCell />
|
<TableHeaderCell />
|
||||||
@@ -299,6 +359,33 @@ function InstalledPlugins() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function BundledPlugins() {
|
||||||
|
const plugins = useAtomValue(pluginsAtom).filter((p) => p.url == null);
|
||||||
|
|
||||||
|
return plugins.length === 0 ? (
|
||||||
|
<div className="pb-4">
|
||||||
|
<EmptyStateText className="text-center">No bundled plugins found.</EmptyStateText>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Table scrollable>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableHeaderCell className="w-0" />
|
||||||
|
<TableHeaderCell>Display Name</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Name</TableHeaderCell>
|
||||||
|
<TableHeaderCell>Version</TableHeaderCell>
|
||||||
|
<TableHeaderCell />
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<tbody className="divide-y divide-surface-highlight">
|
||||||
|
{plugins.map((p) => (
|
||||||
|
<PluginTableRowForBundledPlugin key={p.id} plugin={p} />
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function usePromptUninstall(pluginId: string | null, name: string) {
|
function usePromptUninstall(pluginId: string | null, name: string) {
|
||||||
const mut = useMutation({
|
const mut = useMutation({
|
||||||
mutationKey: ['uninstall_plugin', pluginId],
|
mutationKey: ['uninstall_plugin', pluginId],
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import { selectAtom } from 'jotai/utils';
|
|||||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
import { moveToWorkspace } from '../commands/moveToWorkspace';
|
import { moveToWorkspace } from '../commands/moveToWorkspace';
|
||||||
import { openFolderSettings } from '../commands/openFolderSettings';
|
import { openFolderSettings } from '../commands/openFolderSettings';
|
||||||
import { activeCookieJarAtom } from '../hooks/useActiveCookieJar';
|
|
||||||
import { activeEnvironmentAtom } from '../hooks/useActiveEnvironment';
|
|
||||||
import { activeFolderIdAtom } from '../hooks/useActiveFolderId';
|
import { activeFolderIdAtom } from '../hooks/useActiveFolderId';
|
||||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||||
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
import { activeWorkspaceAtom, activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||||
@@ -49,7 +47,6 @@ import { jotaiStore } from '../lib/jotai';
|
|||||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||||
import { isSidebarFocused } from '../lib/scopes';
|
import { isSidebarFocused } from '../lib/scopes';
|
||||||
import { navigateToRequestOrFolderOrWorkspace } from '../lib/setWorkspaceSearchParams';
|
import { navigateToRequestOrFolderOrWorkspace } from '../lib/setWorkspaceSearchParams';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
|
||||||
import type { ContextMenuProps, DropdownItem } from './core/Dropdown';
|
import type { ContextMenuProps, DropdownItem } from './core/Dropdown';
|
||||||
import { Dropdown } from './core/Dropdown';
|
import { Dropdown } from './core/Dropdown';
|
||||||
import type { FieldDef } from './core/Editor/filter/extension';
|
import type { FieldDef } from './core/Editor/filter/extension';
|
||||||
@@ -331,20 +328,6 @@ function Sidebar({ className }: { className?: string }) {
|
|||||||
leftSlot: <Icon icon="folder_cog" />,
|
leftSlot: <Icon icon="folder_cog" />,
|
||||||
onSelect: () => openFolderSettings(child.id),
|
onSelect: () => openFolderSettings(child.id),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: 'Send All',
|
|
||||||
hidden: !(items.length === 1 && child.model === 'folder'),
|
|
||||||
leftSlot: <Icon icon="send_horizontal" />,
|
|
||||||
onSelect: () => {
|
|
||||||
const environment = jotaiStore.get(activeEnvironmentAtom);
|
|
||||||
const cookieJar = jotaiStore.get(activeCookieJarAtom);
|
|
||||||
invokeCmd('cmd_send_folder', {
|
|
||||||
folderId: child.id,
|
|
||||||
environmentId: environment?.id,
|
|
||||||
cookieJarId: cookieJar?.id,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Send',
|
label: 'Send',
|
||||||
hotKeyAction: 'request.send',
|
hotKeyAction: 'request.send',
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import { jotaiStore } from '../lib/jotai';
|
|||||||
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
import { minPromiseMillis } from '../lib/minPromiseMillis';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||||
|
import { useDebouncedValue } from './useDebouncedValue';
|
||||||
import { invalidateAllPluginInfo } from './usePluginInfo';
|
import { invalidateAllPluginInfo } from './usePluginInfo';
|
||||||
|
|
||||||
export function usePluginsKey() {
|
export function usePluginsKey() {
|
||||||
return useAtomValue(pluginsAtom)
|
const pluginKey = useAtomValue(pluginsAtom)
|
||||||
.map((p) => p.id + p.updatedAt)
|
.map((p) => p.id + p.updatedAt)
|
||||||
.join(',');
|
.join(',');
|
||||||
|
|
||||||
|
// Debounce plugins both for efficiency and to give plugins a chance to reload after the DB updates
|
||||||
|
return useDebouncedValue(pluginKey, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
|
|
||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user