mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
🌊 Add Data Flows to Directus 🌊 (#12522)
* Replace attachment with correct icon
* Use standardized options formatting
* Improve preview styling, fix names
* Format IDs of DB read operation as csv
* Remove flow active state from header
* Don't return null for unknown flows
* Fix webhook trigger not showing id
* Fix alignment of attachment
* Make heading secondary if it's the reject handler
* Use flow name in subtitle of operation drawer
* Rename "Create new Operation" -> "Create Operation"
* Make name/key required
* Give name autofocus
* Add "uncaught exception" log message
* Various improvements on operations
* default status to "active"
* Add status dropdown at the bottom of trigger
* put status dot to the right of flow name header
* add toggle status option in context menu
* fix trigger options staging
* fix flow deletion
* show configured operation key on name hover
* prevent block pushing status toggle down
* ensure key is unique between operations in a flow
* allow add new panel when previous one is deleted
* fix staged panels temporarily disappear
The deletion of newTree.id causes it to "disconnect" when saving, thus causing it seemingly disappear. Using a cloneDeep prevents it from mutating the current stagedPanels
* hide key input when in query mode
* add write operation
* undo previous route props change
* include staged panel keys in key validation
* fix key validation logic
* add color to flow & insights
* ensure trigger does not have reject button
* prevent operation key error showing up when saving
* change context menu to Delete Operation
* fix add operation when removed operation is staged
* Hide ID in read preview when in query mode
* fix reject button showing without edit mode
* fix status toggle in flow overview
* simplify request operation methods & allow other
* fix preview function type
* simplify slot syntax
* add manual trigger
* simplify manual trigger handler
* prevent drawer closing on esc
* allow filter config without selecting collection
* improve affordance of add operation button
* fix loner reject button color
* Added emitEvents option to write operation (#13121)
* added emitEvent option toggle to write operation
* Re-gen package-lock
Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
* Clean up active/inactive toggle
* Align arrow to grid
* Visually align border radius of glow
* Tweak padding of footer in panels
* Remove init event
* Combine event triggers into single "Hook" trigger
* Fix mail operation
* Cleanup imports, return undefined for webhook executino flows
* Add border to panel footer
* Upgrade preview of hook trigger
* Don't warn on uncaught flow executions
* Clean up types
* Fix typing of reload
* Default to correct icon
* Add migrations to remove webhooks table
* Remove webhooks
* Update icons for triggers
* Reorganize triggers
* Merge flows and webhooks migrations
* Add permissions option to database read operatoin
* Add permissions configuration to database write
* Remove flow logs in favor of using directus_activity
* Upgrade webhook configuration, fix create operatoin
* Rename validation->condition
* Subdue everything when inactive
* Tweak tests
* Fix the test for real
* Remove circular FK trigger, please MSSQL
* Make things worse to please MSFT
* Add input
* Drop input scope from condition operation
* naming and description changes
* Default flow overview icon color to primary
* add danger styling to delete flow button
* fix hint buttons subdued style
* Hide trigger unlinked resolve btn when not editing
Don't show "check mark icon" or "operation arrow" on empty trigger when NOT in edit mode
* show email "to" value as CSV
* remove unused webhook.preview translation
* Default sort order of overview table to `name`
* Track activity / revisions in flows
* Extract w/ the intent to reuse revisions fetching
* Move Action type to shared to facilitate app use
* [WIP] Start rendering logs drawer from sidebar
* Fix type error (sorry Eron)
* add update operation
* add delete operation
* use parseJSON util in operations
* Add missing fields to flows system data
* Await promise in sleep operation
* fix e2e test missing flows & operations tables
* Add fallback title to flow logs drawer
* Add default value to flow prop for flow-dialog
* Hydrate flows store before moving to details page when creating flow
* Rename CRUD operations to item-*
* Change trigger options subtitle to Trigger Options
* Remove trigger name option
* Fix typescript complaining
* Remove two lines
* Fix notification operation
* Log error when executing a schedule flow
* Fix schedule flow activity tracking
* Fix notification operation when there is no user
* Make permissions for notifications configurable
* Do not drop non null constraint from column that is nullable
* Remove invalid option from activity seed
* Show resolve/reject dot when operation has successor
* Improve flow arrow placement
* Prevent arrow color from flickering
* Fix arrow being stuck when hovered while saving
* Fix arrows not being subdued on lone leaf operations
* Add tooltips to operation handles
* Remove option to trigger flow on init
* Move operation handle tooltip to icon
* Disconnect duplicated operation
* Fix deleting connected operations
* Make delete action name generic in v-workspace-panel
* Use flow-specific wording in flow edit tooltip
* Simplify hint handle check
* Fix deleting first operation
* Use useEditsGuard composable in flow component
* Add asynchronous option to webhook trigger
* Add option to make preview elements copyable
* Add hover transition to panels
* Register operation preview components as operation-preview-*
* Remove selectability of panel header and operation body
* Add return option to filter and operation triggers
* Add missing key
* Remove unnecessary ampersand from URLs in filter tests (#13523)
* Remove unused prop
* New translations en-US.yaml (Polish) (#13524)
* My favorite
* v9.11.1
* v9.11.1
* New translations en-US.yaml (Polish) (#13528)
* New translations en-US.yaml (Czech) (#13541)
* New translations en-US.yaml (Czech) (#13545)
* fix metadata for directus_folders (#13527)
* add `meta` to list responses for OAS (#13531)
* remove existing pasting check on slug values (#13532)
* Add copy button to user id on user info page (#13540)
* New translations en-US.yaml (Czech) (#13547)
* New translations en-US.yaml (Czech) (#13548)
* Added missing "DB_SSL_*_FILE" to the "_FILE" allow list. (#13539)
* Remove workaround in release flow (#13455)
This forces the release workflow to use `node@16.15` which includes `npm@8.5`.
* Remove npmrc files which prevent lockfile creation in workspaces (#13444)
* Remove npmrc files which prevent lockfile creation in workspaces
Since `v8.5.0` npm will detect that it is running inside a workspace and issue commands at the root package.
* Require a minimum npm version of 8.5.0
* Package-lock 🖤
* Don't consider SIGN_OUT an SSO error (#13389)
* Don't consider SIGN_OUT an SSO error
* Add SESSION_EXPIRED as valid reason
* Improve translation for require_value_to_be_set (#13363)
English + Dutch
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
* Fix field conditions optionDefaults computed property (#13563)
* fix: remove .value from options
* additional ref fix & type/null errors
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
* `last_page` type was `date` instead of `str` (#13577)
* `last_page` type was `date` instead of `str`
* Update docs/reference/system/users.md
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
* New Crowdin updates (#13557)
* New translations en-US.yaml (Romanian)
* New translations en-US.yaml (Indonesian)
* New translations en-US.yaml (Spanish, Chile)
* New translations en-US.yaml (Thai)
* New translations en-US.yaml (Spanish, Latin America)
* New translations en-US.yaml (Russian)
* New translations en-US.yaml (Polish)
* New translations en-US.yaml (Swedish)
* New translations en-US.yaml (Turkish)
* New translations en-US.yaml (Portuguese, Brazilian)
* New translations en-US.yaml (French)
* New translations en-US.yaml (Spanish)
* New translations en-US.yaml (Bulgarian)
* New translations en-US.yaml (Catalan)
* New translations en-US.yaml (Danish)
* New translations en-US.yaml (German)
* New translations en-US.yaml (Finnish)
* New translations en-US.yaml (Hungarian)
* New translations en-US.yaml (Chinese Simplified)
* New translations en-US.yaml (Italian)
* New translations en-US.yaml (Slovenian)
* New translations en-US.yaml (Ukrainian)
* New translations en-US.yaml (English, United Kingdom)
* New translations en-US.yaml (English, Canada)
* New translations en-US.yaml (French, Canada)
* New translations en-US.yaml (Croatian)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Czech)
* Fix validate query number comparison
Ref https://github.com/directus/directus/pull/13492#issuecomment-1138770254
* New translations en-US.yaml (Polish) (#13580)
* add to project (#13581)
* Allow authentication using MSSQL azure-active-directory-service-principal-secret (#11141)
* Extract ignored settings requires by azure authentication
* Change the way to extract initial database settings
* Fix invalid names after extracting from env util
* Replace missing var after solving conflicts
* Add default value to poolconfig
* This should unbreak it
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
* Create pull_request_template.md
* Update pull_request_template.md
* Add System token interface (#13549)
* Add system token interface
* use system token interface in users token field
* Update placeholder
* move notice below input
* fix clear value interaction
* update placeholder and notice text
* remove unused translation key
* rename class to match current naming
* fix bugs in disabled state and it's UX
Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
Co-authored-by: jaycammarano <jay.cammarano@gmail.com>
* New Crowdin updates (#13586)
* New translations en-US.yaml (Romanian)
* New translations en-US.yaml (Indonesian)
* New translations en-US.yaml (Spanish, Chile)
* New translations en-US.yaml (Thai)
* New translations en-US.yaml (Serbian (Latin))
* New translations en-US.yaml (Spanish, Latin America)
* New translations en-US.yaml (Russian)
* New translations en-US.yaml (Polish)
* New translations en-US.yaml (Portuguese)
* New translations en-US.yaml (Swedish)
* New translations en-US.yaml (Turkish)
* New translations en-US.yaml (Estonian)
* New translations en-US.yaml (Portuguese, Brazilian)
* New translations en-US.yaml (French)
* New translations en-US.yaml (Spanish)
* New translations en-US.yaml (Bulgarian)
* New translations en-US.yaml (Catalan)
* New translations en-US.yaml (Czech)
* New translations en-US.yaml (Danish)
* New translations en-US.yaml (German)
* New translations en-US.yaml (Finnish)
* New translations en-US.yaml (Hungarian)
* New translations en-US.yaml (Chinese Simplified)
* New translations en-US.yaml (Italian)
* New translations en-US.yaml (Japanese)
* New translations en-US.yaml (Dutch)
* New translations en-US.yaml (Slovenian)
* New translations en-US.yaml (Ukrainian)
* New translations en-US.yaml (English, United Kingdom)
* New translations en-US.yaml (English, Canada)
* New translations en-US.yaml (French, Canada)
* New translations en-US.yaml (Croatian)
* Add project_url to defaultTemplateData (#12033)
Might be useful in template footers.
* Update items.md
* Rename panel to tile
* Rename preview->overview
* Style flow log detail
* Log all parsed options
* Show used options in revision
* Finish log detail drawer
* new create flow flow
* fix firstOpen for new create flow flow
* update field layout for create flow form
* Fix TS typing
* Fix missing import
* Append random hash to key when duplicating operations
* Revert "Remove webhooks"
This reverts commit 044d3d8b66.
* Don't delete webhooks
* Make option preview selectable
* Prevent invalid linking when duplicating operations after creating operations
* Prevent sending of malformed query filter when deleting flow
* implement new manual trigger
* simplify payload for manual trigger
* use buttons instead of dropdown + run button
* add async option & loading state
* add collection check to manual trigger
* emit refresh after running flow in sidebar
* Add cross-instance messenger for reloading
* Use flow drawer for both create and edit
* Add manual trigger flow permissions to app recommended
Ensures that non-admin users can actually see the flows sidebar detail
* Add basic logs redaction
* Remove endpoint to trigger an operation
* Allow configuring location for manual trigger
* Rename "hook" trigger to "event"
* Tweak icon size
* Fix create flow button in info notice
* Make activity tracking full width
* Tweak descriptions
* Too long for comfort
* Remove mode option from item-* operations
* fix manual trigger empty collections option
* Add no-logs-yet message in sidebar detail
* Reset trigger options on change of trigger
* Rename `data`->`payload`
* Remove mode from preview of item-* operations
* Return operation options with "{{key}}" as raw value
* Show flow name in delete confirmation
* Add default generated name/key to new operations
* shorten arrows WIP
still needs icons moved
* rename note to description
* fix hint button icons
* update event hook type labels
* Animate resolve/reject arrow hints
* reorder event types
* Use x+4 instead of x+6 for new operation panels
* compress options to fit 6 lines in operation
* update hook labels
* animate trigger box shadow
sorry, rijk!
* update (global) disabled button color 1 shade
* Format times nicer
* Add placeholder for query
* add a note
* Fix formatting for curly brackets in translations
* Add item Create/update payload placeholder
* Add placeholder to user uuid
* Accept either null or undefined for nullable operation options
* Allow any string as request body
* Add more placeholders
* Consolidate filterScope and actionScope, filterCollections and actionCollections
* Rename flow note to description in types
Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
Co-authored-by: Brainslug <br41nslug@users.noreply.github.com>
Co-authored-by: Ben Haynes <ben@rngr.org>
Co-authored-by: Nicola Krumschmidt <nicola.krumschmidt@freenet.de>
Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com>
Co-authored-by: Jan-Willem <jan-willem@qdentity.nl>
Co-authored-by: Yasser Lahbibi <yasser.lahbibi@apenhet.com>
Co-authored-by: Louis <32719150+louisgavalda@users.noreply.github.com>
Co-authored-by: Jay Cammarano <67079013+jaycammarano@users.noreply.github.com>
Co-authored-by: Erick Torres <ericktorresh@gmail.com>
Co-authored-by: jaycammarano <jay.cammarano@gmail.com>
Co-authored-by: Yuriy Belenko <yura-bely@mail.ru>
Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
@@ -1,14 +1,21 @@
|
||||
export const APP_SHARED_DEPS = ['@directus/extensions-sdk', 'vue', 'vue-router', 'vue-i18n', 'pinia'];
|
||||
export const API_SHARED_DEPS = ['directus'];
|
||||
|
||||
export const APP_EXTENSION_TYPES = ['interface', 'display', 'layout', 'module', 'panel'] as const;
|
||||
export const API_EXTENSION_TYPES = ['hook', 'endpoint'] as const;
|
||||
export const EXTENSION_TYPES = [...APP_EXTENSION_TYPES, ...API_EXTENSION_TYPES] as const;
|
||||
export const APP_WITHOUT_HYBRID_EXTENSION_TYPES = ['interface', 'display', 'layout', 'module', 'panel'] as const;
|
||||
export const API_WITHOUT_HYBRID_EXTENSION_TYPES = ['hook', 'endpoint'] as const;
|
||||
export const HYBRID_EXTENSION_TYPES = ['operation'] as const;
|
||||
export const APP_EXTENSION_TYPES = [...APP_WITHOUT_HYBRID_EXTENSION_TYPES, ...HYBRID_EXTENSION_TYPES] as const;
|
||||
export const API_EXTENSION_TYPES = [...API_WITHOUT_HYBRID_EXTENSION_TYPES, ...HYBRID_EXTENSION_TYPES] as const;
|
||||
export const EXTENSION_TYPES = [
|
||||
...APP_WITHOUT_HYBRID_EXTENSION_TYPES,
|
||||
...API_WITHOUT_HYBRID_EXTENSION_TYPES,
|
||||
...HYBRID_EXTENSION_TYPES,
|
||||
] as const;
|
||||
|
||||
export const EXTENSION_PACK_TYPE = 'pack';
|
||||
export const APP_EXTENSION_PACKAGE_TYPES = [...APP_EXTENSION_TYPES, EXTENSION_PACK_TYPE] as const;
|
||||
export const API_EXTENSION_PACKAGE_TYPES = [...API_EXTENSION_TYPES, EXTENSION_PACK_TYPE] as const;
|
||||
export const EXTENSION_PACKAGE_TYPES = [...EXTENSION_TYPES, EXTENSION_PACK_TYPE] as const;
|
||||
export const PACK_EXTENSION_TYPE = 'pack';
|
||||
export const APP_EXTENSION_PACKAGE_TYPES = [...APP_EXTENSION_TYPES, PACK_EXTENSION_TYPE] as const;
|
||||
export const API_EXTENSION_PACKAGE_TYPES = [...API_EXTENSION_TYPES, PACK_EXTENSION_TYPE] as const;
|
||||
export const EXTENSION_PACKAGE_TYPES = [...EXTENSION_TYPES, PACK_EXTENSION_TYPE] as const;
|
||||
|
||||
export const EXTENSION_LANGUAGES = ['javascript', 'typescript'] as const;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Permission } from '.';
|
||||
import { Permission } from './permissions';
|
||||
|
||||
export type ShareScope = {
|
||||
collection: string;
|
||||
|
||||
10
packages/shared/src/types/activity.ts
Normal file
10
packages/shared/src/types/activity.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export enum Action {
|
||||
CREATE = 'create',
|
||||
UPDATE = 'update',
|
||||
DELETE = 'delete',
|
||||
REVERT = 'revert',
|
||||
COMMENT = 'comment',
|
||||
UPLOAD = 'upload',
|
||||
LOGIN = 'login',
|
||||
RUN = 'run',
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Router } from 'express';
|
||||
import { ApiExtensionContext } from './extensions';
|
||||
|
||||
type EndpointHandlerFunction = (router: Router, context: ApiExtensionContext) => void;
|
||||
interface EndpointAdvancedConfig {
|
||||
id: string;
|
||||
handler: EndpointHandlerFunction;
|
||||
}
|
||||
type EndpointExtensionContext = ApiExtensionContext & {
|
||||
emitter: any;
|
||||
};
|
||||
|
||||
export type EndpointConfig = EndpointHandlerFunction | EndpointAdvancedConfig;
|
||||
type EndpointConfigFunction = (router: Router, context: EndpointExtensionContext) => void;
|
||||
|
||||
type EndpointConfigObject = {
|
||||
id: string;
|
||||
handler: EndpointConfigFunction;
|
||||
};
|
||||
|
||||
export type EndpointConfig = EndpointConfigFunction | EndpointConfigObject;
|
||||
|
||||
@@ -8,7 +8,11 @@ export type EventContext = {
|
||||
accountability: Accountability | null;
|
||||
};
|
||||
|
||||
export type FilterHandler = (payload: any, meta: Record<string, any>, context: EventContext) => any | Promise<any>;
|
||||
export type FilterHandler<T = unknown> = (
|
||||
payload: T,
|
||||
meta: Record<string, any>,
|
||||
context: EventContext
|
||||
) => T | Promise<T>;
|
||||
export type ActionHandler = (meta: Record<string, any>, context: EventContext) => void;
|
||||
export type InitHandler = (meta: Record<string, any>) => void;
|
||||
export type ScheduleHandler = () => void;
|
||||
export type ScheduleHandler = () => void | Promise<void>;
|
||||
|
||||
@@ -1,51 +1,88 @@
|
||||
import { Knex } from 'knex';
|
||||
import { Logger } from 'pino';
|
||||
import { Ref } from 'vue';
|
||||
import {
|
||||
API_EXTENSION_PACKAGE_TYPES,
|
||||
API_EXTENSION_TYPES,
|
||||
API_WITHOUT_HYBRID_EXTENSION_TYPES,
|
||||
APP_EXTENSION_PACKAGE_TYPES,
|
||||
APP_EXTENSION_TYPES,
|
||||
APP_WITHOUT_HYBRID_EXTENSION_TYPES,
|
||||
EXTENSION_PACKAGE_TYPES,
|
||||
EXTENSION_PKG_KEY,
|
||||
EXTENSION_TYPES,
|
||||
HYBRID_EXTENSION_TYPES,
|
||||
LOCAL_TYPES,
|
||||
PACK_EXTENSION_TYPE,
|
||||
} from '../constants';
|
||||
import { Accountability } from './accountability';
|
||||
import {
|
||||
Collection,
|
||||
Field,
|
||||
Relation,
|
||||
DeepPartial,
|
||||
InterfaceConfig,
|
||||
DisplayConfig,
|
||||
LayoutConfig,
|
||||
ModuleConfig,
|
||||
PanelConfig,
|
||||
} from '.';
|
||||
import { LOCAL_TYPES } from '../constants';
|
||||
import { Ref } from 'vue';
|
||||
import { InterfaceConfig } from './interfaces';
|
||||
import { DisplayConfig } from './displays';
|
||||
import { LayoutConfig } from './layouts';
|
||||
import { ModuleConfig } from './modules';
|
||||
import { PanelConfig } from './panels';
|
||||
import { DeepPartial } from './misc';
|
||||
import { Field } from './fields';
|
||||
import { Relation } from './relations';
|
||||
import { Collection } from './collection';
|
||||
import { SchemaOverview } from './schema';
|
||||
|
||||
export type AppWithoutHybridExtensionType = typeof APP_WITHOUT_HYBRID_EXTENSION_TYPES[number];
|
||||
export type ApiWithoutHybridExtensionType = typeof API_WITHOUT_HYBRID_EXTENSION_TYPES[number];
|
||||
export type HybridExtensionType = typeof HYBRID_EXTENSION_TYPES[number];
|
||||
export type AppExtensionType = typeof APP_EXTENSION_TYPES[number];
|
||||
export type ApiExtensionType = typeof API_EXTENSION_TYPES[number];
|
||||
export type ExtensionType = typeof EXTENSION_TYPES[number];
|
||||
|
||||
export type PackExtensionType = typeof PACK_EXTENSION_TYPE;
|
||||
export type AppExtensionPackageType = typeof APP_EXTENSION_PACKAGE_TYPES[number];
|
||||
export type ApiExtensionPackageType = typeof API_EXTENSION_PACKAGE_TYPES[number];
|
||||
export type ExtensionPackageType = typeof EXTENSION_PACKAGE_TYPES[number];
|
||||
|
||||
export type Extension = {
|
||||
type ExtensionCommon = {
|
||||
path: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
|
||||
type: ExtensionPackageType;
|
||||
entrypoint?: string;
|
||||
host?: string;
|
||||
children?: string[];
|
||||
|
||||
local: boolean;
|
||||
};
|
||||
|
||||
type AppExtensionCommon = {
|
||||
type: AppWithoutHybridExtensionType;
|
||||
entrypoint: string;
|
||||
};
|
||||
|
||||
type ApiExtensionCommon = {
|
||||
type: ApiWithoutHybridExtensionType;
|
||||
entrypoint: string;
|
||||
};
|
||||
|
||||
type HybridExtensionCommon = {
|
||||
type: HybridExtensionType;
|
||||
entrypoint: { app: string; api: string };
|
||||
};
|
||||
|
||||
type PackExtensionCommon = {
|
||||
type: PackExtensionType;
|
||||
children: string[];
|
||||
};
|
||||
|
||||
type ExtensionLocalCommon = ExtensionCommon & {
|
||||
local: true;
|
||||
};
|
||||
|
||||
type ExtensionPackageCommon = ExtensionCommon & {
|
||||
version: string;
|
||||
host: string;
|
||||
local: false;
|
||||
};
|
||||
|
||||
export type ExtensionLocal = ExtensionLocalCommon & (AppExtensionCommon | ApiExtensionCommon | HybridExtensionCommon);
|
||||
export type ExtensionPackage = ExtensionPackageCommon &
|
||||
(AppExtensionCommon | ApiExtensionCommon | HybridExtensionCommon | PackExtensionCommon);
|
||||
|
||||
export type AppExtension = AppExtensionCommon & (ExtensionLocalCommon | ExtensionPackageCommon);
|
||||
export type ApiExtension = ApiExtensionCommon & (ExtensionLocalCommon | ExtensionPackageCommon);
|
||||
export type HybridExtension = HybridExtensionCommon & (ExtensionLocalCommon | ExtensionPackageCommon);
|
||||
export type Extension = ExtensionLocal | ExtensionPackage;
|
||||
|
||||
export type ExtensionManifestRaw = {
|
||||
name?: string;
|
||||
version?: string;
|
||||
@@ -53,25 +90,43 @@ export type ExtensionManifestRaw = {
|
||||
|
||||
[EXTENSION_PKG_KEY]?: {
|
||||
type?: string;
|
||||
path?: string;
|
||||
source?: string;
|
||||
path?: string | { app: string; api: string };
|
||||
source?: string | { app: string; api: string };
|
||||
host?: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type ExtensionManifestCommon = {
|
||||
host: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
type ExtensionManifestWithoutHybrid = {
|
||||
type: AppWithoutHybridExtensionType | ApiWithoutHybridExtensionType;
|
||||
path: string;
|
||||
source: string;
|
||||
};
|
||||
|
||||
type ExtensionManifestHybrid = {
|
||||
type: HybridExtensionType;
|
||||
path: { app: string; api: string };
|
||||
source: { app: string; api: string };
|
||||
};
|
||||
|
||||
type ExtensionManifestPack = {
|
||||
type: PackExtensionType;
|
||||
};
|
||||
|
||||
export type ExtensionOptions = ExtensionManifestCommon &
|
||||
(ExtensionManifestWithoutHybrid | ExtensionManifestHybrid | ExtensionManifestPack);
|
||||
|
||||
export type ExtensionManifest = {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies?: Record<string, string>;
|
||||
|
||||
[EXTENSION_PKG_KEY]: {
|
||||
type: ExtensionPackageType;
|
||||
path: string;
|
||||
source: string;
|
||||
host: string;
|
||||
hidden?: boolean;
|
||||
};
|
||||
[EXTENSION_PKG_KEY]: ExtensionOptions;
|
||||
};
|
||||
|
||||
export type AppExtensionConfigs = {
|
||||
@@ -87,7 +142,6 @@ export type ApiExtensionContext = {
|
||||
exceptions: any;
|
||||
database: Knex;
|
||||
env: Record<string, any>;
|
||||
emitter: any;
|
||||
logger: Logger;
|
||||
getSchema: (options?: { accountability?: Accountability; database?: Knex }) => Promise<SchemaOverview>;
|
||||
};
|
||||
|
||||
57
packages/shared/src/types/flows.ts
Normal file
57
packages/shared/src/types/flows.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export type TriggerType = 'event' | 'schedule' | 'operation' | 'webhook' | 'manual';
|
||||
type Status = 'active' | 'inactive';
|
||||
|
||||
export interface Flow {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description: string;
|
||||
status: Status;
|
||||
trigger: TriggerType | null;
|
||||
options: Record<string, any>;
|
||||
operation: Operation | null;
|
||||
accountability: 'all' | 'activity' | null;
|
||||
}
|
||||
|
||||
export interface Operation {
|
||||
id: string;
|
||||
name: string | null;
|
||||
key: string;
|
||||
type: string;
|
||||
position_x: number;
|
||||
position_y: number;
|
||||
options: Record<string, any>;
|
||||
resolve: Operation | null;
|
||||
reject: Operation | null;
|
||||
}
|
||||
|
||||
export interface FlowRaw {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
color: string;
|
||||
description: string;
|
||||
status: Status;
|
||||
trigger: TriggerType | null;
|
||||
options: Record<string, any>;
|
||||
operation: string | null;
|
||||
operations: OperationRaw[];
|
||||
date_created: string;
|
||||
user_created: string;
|
||||
accountability: 'all' | 'activity' | null;
|
||||
}
|
||||
|
||||
export interface OperationRaw {
|
||||
id: string;
|
||||
name: string | null;
|
||||
key: string;
|
||||
type: string;
|
||||
position_x: number;
|
||||
position_y: number;
|
||||
options: Record<string, any>;
|
||||
resolve: string | null;
|
||||
reject: string | null;
|
||||
flow: string;
|
||||
date_created: string;
|
||||
user_created: string;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import { ActionHandler, FilterHandler, InitHandler, ScheduleHandler } from './events';
|
||||
import { ApiExtensionContext } from './extensions';
|
||||
|
||||
type HookExtensionContext = ApiExtensionContext & {
|
||||
emitter: any;
|
||||
};
|
||||
|
||||
type RegisterFunctions = {
|
||||
filter: (event: string, handler: FilterHandler) => void;
|
||||
action: (event: string, handler: ActionHandler) => void;
|
||||
@@ -8,6 +12,6 @@ type RegisterFunctions = {
|
||||
schedule: (cron: string, handler: ScheduleHandler) => void;
|
||||
};
|
||||
|
||||
type HookHandlerFunction = (register: RegisterFunctions, context: ApiExtensionContext) => void;
|
||||
type HookConfigFunction = (register: RegisterFunctions, context: HookExtensionContext) => void;
|
||||
|
||||
export type HookConfig = HookHandlerFunction;
|
||||
export type HookConfig = HookConfigFunction;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './activity';
|
||||
export * from './accountability';
|
||||
export * from './collection';
|
||||
export * from './displays';
|
||||
@@ -6,6 +7,7 @@ export * from './events';
|
||||
export * from './extensions';
|
||||
export * from './fields';
|
||||
export * from './filter';
|
||||
export * from './flows';
|
||||
export * from './geometry';
|
||||
export * from './hooks';
|
||||
export * from './interfaces';
|
||||
@@ -14,6 +16,7 @@ export * from './layouts';
|
||||
export * from './misc';
|
||||
export * from './modules';
|
||||
export * from './notifications';
|
||||
export * from './operations';
|
||||
export * from './panels';
|
||||
export * from './permissions';
|
||||
export * from './presets';
|
||||
|
||||
37
packages/shared/src/types/operations.ts
Normal file
37
packages/shared/src/types/operations.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ComponentOptions } from 'vue';
|
||||
import { Accountability } from './accountability';
|
||||
import { ApiExtensionContext } from './extensions';
|
||||
import { Field } from './fields';
|
||||
import { DeepPartial } from './misc';
|
||||
import { FlowRaw } from './flows';
|
||||
|
||||
type OperationContext = ApiExtensionContext & {
|
||||
data: Record<string, unknown>;
|
||||
accountability: Accountability | null;
|
||||
};
|
||||
|
||||
export type OperationHandler<Options = Record<string, unknown>> = (
|
||||
options: Options,
|
||||
context: OperationContext
|
||||
) => unknown | Promise<unknown> | void;
|
||||
|
||||
export interface OperationAppConfig {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
description?: string;
|
||||
overview:
|
||||
| ((
|
||||
options: Record<string, any>,
|
||||
{ flow }: { flow: FlowRaw }
|
||||
) => { label: string; text: string; copyable?: boolean }[])
|
||||
| ComponentOptions
|
||||
| null;
|
||||
options: DeepPartial<Field>[] | ((options: Record<string, any>) => DeepPartial<Field>[]) | ComponentOptions | null;
|
||||
}
|
||||
|
||||
export interface OperationApiConfig<Options = Record<string, unknown>> {
|
||||
id: string;
|
||||
|
||||
handler: OperationHandler<Options>;
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* Get the value of a globally registered CSS variable
|
||||
*/
|
||||
export function cssVar(name: string) {
|
||||
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
||||
export function cssVar(name: string, element: Element = document.body) {
|
||||
return getComputedStyle(element ?? document.body)
|
||||
.getPropertyValue(name)
|
||||
.trim();
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import {
|
||||
DisplayConfig,
|
||||
LayoutConfig,
|
||||
ModuleConfig,
|
||||
PanelConfig,
|
||||
HookConfig,
|
||||
EndpointConfig,
|
||||
PanelConfig,
|
||||
OperationAppConfig,
|
||||
OperationApiConfig,
|
||||
} from '../types';
|
||||
|
||||
export function defineInterface(config: InterfaceConfig): InterfaceConfig {
|
||||
@@ -26,6 +28,10 @@ export function defineModule(config: ModuleConfig): ModuleConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function definePanel(config: PanelConfig): PanelConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function defineHook(config: HookConfig): HookConfig {
|
||||
return config;
|
||||
}
|
||||
@@ -34,6 +40,12 @@ export function defineEndpoint(config: EndpointConfig): EndpointConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function definePanel(config: PanelConfig): PanelConfig {
|
||||
export function defineOperationApp(config: OperationAppConfig): OperationAppConfig {
|
||||
return config;
|
||||
}
|
||||
|
||||
export function defineOperationApi<Options = Record<string, unknown>>(
|
||||
config: OperationApiConfig<Options>
|
||||
): OperationApiConfig<Options> {
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { API_EXTENSION_TYPES, APP_EXTENSION_TYPES, EXTENSION_PACKAGE_TYPES, EXTENSION_TYPES } from '../constants';
|
||||
import { ApiExtensionType, AppExtensionType, ExtensionPackageType, ExtensionType } from '../types';
|
||||
import {
|
||||
API_EXTENSION_TYPES,
|
||||
APP_EXTENSION_TYPES,
|
||||
EXTENSION_PACKAGE_TYPES,
|
||||
EXTENSION_TYPES,
|
||||
HYBRID_EXTENSION_TYPES,
|
||||
} from '../constants';
|
||||
import { ApiExtensionType, AppExtensionType, ExtensionPackageType, ExtensionType, HybridExtensionType } from '../types';
|
||||
|
||||
export function isExtension(type: string): type is ExtensionType {
|
||||
return (EXTENSION_TYPES as readonly string[]).includes(type);
|
||||
@@ -13,6 +19,19 @@ export function isApiExtension(type: string): type is ApiExtensionType {
|
||||
return (API_EXTENSION_TYPES as readonly string[]).includes(type);
|
||||
}
|
||||
|
||||
export function isHybridExtension(type: string): type is HybridExtensionType {
|
||||
return (HYBRID_EXTENSION_TYPES as readonly string[]).includes(type);
|
||||
}
|
||||
|
||||
export function isExtensionPackage(type: string): type is ExtensionPackageType {
|
||||
return (EXTENSION_PACKAGE_TYPES as readonly string[]).includes(type);
|
||||
}
|
||||
|
||||
export function isExtensionObject<T extends { type?: string }, E extends string>(
|
||||
obj: T,
|
||||
arr: readonly E[]
|
||||
): obj is Extract<T, { type?: E }> {
|
||||
if (!obj.type) return false;
|
||||
|
||||
return (arr as readonly string[]).includes(obj.type);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import path from 'path';
|
||||
import { AppExtensionType, Extension } from '../../types';
|
||||
import { AppExtension, AppExtensionType, Extension } from '../../types';
|
||||
|
||||
export function generateExtensionsEntry(type: AppExtensionType, extensions: Extension[]): string {
|
||||
const filteredExtensions = extensions.filter((extension) => extension.type === type);
|
||||
const filteredExtensions = extensions.filter((extension): extension is AppExtension => extension.type === type);
|
||||
|
||||
return `${filteredExtensions
|
||||
.map(
|
||||
(extension, i) =>
|
||||
`import e${i} from './${path
|
||||
.relative('.', path.resolve(extension.path, extension.entrypoint || ''))
|
||||
.relative('.', path.resolve(extension.path, extension.entrypoint))
|
||||
.split(path.sep)
|
||||
.join(path.posix.sep)}';\n`
|
||||
)
|
||||
|
||||
@@ -1,13 +1,88 @@
|
||||
import path from 'path';
|
||||
import fse from 'fs-extra';
|
||||
import { Extension, ExtensionManifestRaw, ExtensionPackageType, ExtensionType } from '../../types';
|
||||
import {
|
||||
ExtensionLocal,
|
||||
ExtensionManifestRaw,
|
||||
ExtensionPackage,
|
||||
ExtensionPackageType,
|
||||
ExtensionType,
|
||||
} from '../../types';
|
||||
import { resolvePackage } from './resolve-package';
|
||||
import { listFolders } from './list-folders';
|
||||
import { EXTENSION_NAME_REGEX, EXTENSION_PKG_KEY } from '../../constants';
|
||||
import { EXTENSION_NAME_REGEX, EXTENSION_PKG_KEY, HYBRID_EXTENSION_TYPES, PACK_EXTENSION_TYPE } from '../../constants';
|
||||
import { pluralize } from '../pluralize';
|
||||
import { validateExtensionManifest } from '../validate-extension-manifest';
|
||||
import { isExtensionObject, isHybridExtension } from '../is-extension';
|
||||
|
||||
export async function getPackageExtensions(root: string, types: readonly ExtensionPackageType[]): Promise<Extension[]> {
|
||||
async function resolvePackageExtensions(
|
||||
extensionNames: string[],
|
||||
root: string,
|
||||
types: readonly ExtensionPackageType[]
|
||||
): Promise<ExtensionPackage[]> {
|
||||
const extensions: ExtensionPackage[] = [];
|
||||
|
||||
for (const extensionName of extensionNames) {
|
||||
const extensionPath = resolvePackage(extensionName, root);
|
||||
const extensionManifest: ExtensionManifestRaw = await fse.readJSON(path.join(extensionPath, 'package.json'));
|
||||
|
||||
if (!validateExtensionManifest(extensionManifest)) {
|
||||
throw new Error(`The extension manifest of "${extensionName}" is not valid.`);
|
||||
}
|
||||
|
||||
const extensionOptions = extensionManifest[EXTENSION_PKG_KEY];
|
||||
|
||||
if (types.includes(extensionOptions.type)) {
|
||||
if (extensionOptions.type === PACK_EXTENSION_TYPE) {
|
||||
const extensionChildren = Object.keys(extensionManifest.dependencies ?? {}).filter((dep) =>
|
||||
EXTENSION_NAME_REGEX.test(dep)
|
||||
);
|
||||
|
||||
const extension: ExtensionPackage = {
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
version: extensionManifest.version,
|
||||
type: extensionOptions.type,
|
||||
host: extensionOptions.host,
|
||||
children: extensionChildren,
|
||||
local: false,
|
||||
};
|
||||
|
||||
extensions.push(extension);
|
||||
extensions.push(...(await resolvePackageExtensions(extension.children || [], extension.path, types)));
|
||||
} else if (isExtensionObject(extensionOptions, HYBRID_EXTENSION_TYPES)) {
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
version: extensionManifest.version,
|
||||
type: extensionOptions.type,
|
||||
entrypoint: {
|
||||
app: extensionOptions.path.app,
|
||||
api: extensionOptions.path.api,
|
||||
},
|
||||
host: extensionOptions.host,
|
||||
local: false,
|
||||
});
|
||||
} else {
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
version: extensionManifest.version,
|
||||
type: extensionOptions.type,
|
||||
entrypoint: extensionOptions.path,
|
||||
host: extensionOptions.host,
|
||||
local: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
|
||||
export async function getPackageExtensions(
|
||||
root: string,
|
||||
types: readonly ExtensionPackageType[]
|
||||
): Promise<ExtensionPackage[]> {
|
||||
let pkg: { dependencies?: Record<string, string> };
|
||||
|
||||
try {
|
||||
@@ -18,57 +93,11 @@ export async function getPackageExtensions(root: string, types: readonly Extensi
|
||||
|
||||
const extensionNames = Object.keys(pkg.dependencies ?? {}).filter((dep) => EXTENSION_NAME_REGEX.test(dep));
|
||||
|
||||
return listExtensionsChildren(extensionNames, root);
|
||||
|
||||
async function listExtensionsChildren(extensionNames: string[], root: string) {
|
||||
const extensions: Extension[] = [];
|
||||
|
||||
for (const extensionName of extensionNames) {
|
||||
const extensionPath = resolvePackage(extensionName, root);
|
||||
const extensionManifest: ExtensionManifestRaw = await fse.readJSON(path.join(extensionPath, 'package.json'));
|
||||
|
||||
if (!validateExtensionManifest(extensionManifest)) {
|
||||
throw new Error(`The extension manifest of "${extensionName}" is not valid.`);
|
||||
}
|
||||
|
||||
if (types.includes(extensionManifest[EXTENSION_PKG_KEY].type)) {
|
||||
if (extensionManifest[EXTENSION_PKG_KEY].type === 'pack') {
|
||||
const extensionChildren = Object.keys(extensionManifest.dependencies ?? {}).filter((dep) =>
|
||||
EXTENSION_NAME_REGEX.test(dep)
|
||||
);
|
||||
|
||||
const extension: Extension = {
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
version: extensionManifest.version,
|
||||
type: extensionManifest[EXTENSION_PKG_KEY].type,
|
||||
host: extensionManifest[EXTENSION_PKG_KEY].host,
|
||||
children: extensionChildren,
|
||||
local: false,
|
||||
};
|
||||
|
||||
extensions.push(extension);
|
||||
extensions.push(...(await listExtensionsChildren(extension.children || [], extension.path)));
|
||||
} else {
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
version: extensionManifest.version,
|
||||
type: extensionManifest[EXTENSION_PKG_KEY].type,
|
||||
entrypoint: extensionManifest[EXTENSION_PKG_KEY].path,
|
||||
host: extensionManifest[EXTENSION_PKG_KEY].host,
|
||||
local: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return extensions;
|
||||
}
|
||||
return resolvePackageExtensions(extensionNames, root, types);
|
||||
}
|
||||
|
||||
export async function getLocalExtensions(root: string, types: readonly ExtensionType[]): Promise<Extension[]> {
|
||||
const extensions: Extension[] = [];
|
||||
export async function getLocalExtensions(root: string, types: readonly ExtensionType[]): Promise<ExtensionLocal[]> {
|
||||
const extensions: ExtensionLocal[] = [];
|
||||
|
||||
for (const extensionType of types) {
|
||||
const typeDir = pluralize(extensionType);
|
||||
@@ -80,13 +109,26 @@ export async function getLocalExtensions(root: string, types: readonly Extension
|
||||
for (const extensionName of extensionNames) {
|
||||
const extensionPath = path.join(typePath, extensionName);
|
||||
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
type: extensionType,
|
||||
entrypoint: 'index.js',
|
||||
local: true,
|
||||
});
|
||||
if (!isHybridExtension(extensionType)) {
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
type: extensionType,
|
||||
entrypoint: 'index.js',
|
||||
local: true,
|
||||
});
|
||||
} else {
|
||||
extensions.push({
|
||||
path: extensionPath,
|
||||
name: extensionName,
|
||||
type: extensionType,
|
||||
entrypoint: {
|
||||
app: 'app.js',
|
||||
api: 'api.js',
|
||||
},
|
||||
local: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
throw new Error(`Extension folder "${typePath}" couldn't be opened`);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { EXTENSION_PKG_KEY } from '../constants';
|
||||
import { EXTENSION_PKG_KEY, PACK_EXTENSION_TYPE } from '../constants';
|
||||
import { ExtensionManifest, ExtensionManifestRaw } from '../types';
|
||||
import { isExtensionPackage } from './is-extension';
|
||||
import { isExtensionPackage, isHybridExtension } from './is-extension';
|
||||
|
||||
export function validateExtensionManifest(
|
||||
extensionManifest: ExtensionManifestRaw
|
||||
): extensionManifest is ExtensionManifest {
|
||||
if (extensionManifest.name === undefined || extensionManifest.version === undefined) {
|
||||
if (!extensionManifest.name || !extensionManifest.version) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extensionOptions = extensionManifest[EXTENSION_PKG_KEY];
|
||||
|
||||
if (extensionOptions === undefined) {
|
||||
if (!extensionOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extensionOptions.type === undefined) {
|
||||
if (!extensionOptions.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,16 +23,26 @@ export function validateExtensionManifest(
|
||||
return false;
|
||||
}
|
||||
|
||||
if (extensionOptions.type !== 'pack') {
|
||||
if (extensionOptions.type === PACK_EXTENSION_TYPE) {
|
||||
if (!extensionOptions.host) {
|
||||
return false;
|
||||
}
|
||||
} else if (isHybridExtension(extensionOptions.type)) {
|
||||
if (
|
||||
extensionOptions.path === undefined ||
|
||||
extensionOptions.source === undefined ||
|
||||
extensionOptions.host === undefined
|
||||
!extensionOptions.path ||
|
||||
!extensionOptions.source ||
|
||||
typeof extensionOptions.path === 'string' ||
|
||||
typeof extensionOptions.source === 'string' ||
|
||||
!extensionOptions.path.app ||
|
||||
!extensionOptions.path.api ||
|
||||
!extensionOptions.source.app ||
|
||||
!extensionOptions.source.api ||
|
||||
!extensionOptions.host
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (extensionOptions.host === undefined) {
|
||||
if (!extensionOptions.path || !extensionOptions.source || !extensionOptions.host) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user