diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d205404..e0d8b93 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -107,6 +107,15 @@ jobs: steps: - name: Github checkout uses: actions/checkout@v4 + - run: echo "previous_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo '')" >> $GITHUB_ENV + - name: Tag Release + uses: jaywcjlove/create-tag-action@main + if: env.previous_tag + id: tag_release + with: + prerelease: true + token: ${{ secrets.GITHUB_TOKEN }} + package-path: ./package.json - name: Download Distribution Binaries uses: actions/download-artifact@v4 with: @@ -117,13 +126,6 @@ jobs: id: changelog with: token: ${{ secrets.GITHUB_TOKEN }} - - name: Tag Release - uses: jaywcjlove/create-tag-action@main - id: tag_release - with: - prerelease: true - token: ${{ secrets.GITHUB_TOKEN }} - - name: Release App uses: softprops/action-gh-release@v2 if: steps.tag_release.outputs.successful diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index fe7f44b..fd8815d 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -132,12 +132,6 @@ dependencies = [ "system-deps 6.2.2", ] -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - [[package]] name = "autocfg" version = "1.3.0" @@ -1302,25 +1296,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "h2" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1464,7 +1439,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", + "h2", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -1487,7 +1462,6 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1514,6 +1488,7 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -1529,22 +1504,6 @@ dependencies = [ "tokio-native-tls", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.4.0", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" version = "0.1.6" @@ -2780,11 +2739,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", + "h2", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.29", - "hyper-tls 0.5.0", + "hyper-tls", "ipnet", "js-sys", "log", @@ -2819,23 +2778,19 @@ checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "http-body-util", "hyper 1.4.0", "hyper-rustls", - "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -2848,9 +2803,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 1.0.1", - "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -2859,6 +2812,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg 0.52.0", ] @@ -4433,6 +4387,15 @@ dependencies = [ "system-deps 6.2.2", ] +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webview2-com" version = "0.19.1" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e061893..8b1598c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1" tauri-plugin-store = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-system-info = { git = "https://github.com/HuakunShen/tauri-plugin-system-info", branch = "v1" } # use v2 branch for Tauri v2 plugin async-openai = { version = "0.23.3" } -reqwest = { version = "0.12", features = ["json"] } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } tokio = { version = "1", features = ["full"] } http = "0.2.12" log = "0.4.22" diff --git a/src/common/editor.ts b/src/common/editor.ts deleted file mode 100644 index f40c0d2..0000000 --- a/src/common/editor.ts +++ /dev/null @@ -1,478 +0,0 @@ -export type Range = { - startLineNumber: number; - startColumn: number; - endLineNumber: number; - endColumn: number; -}; -export type Decoration = { - id: number; - range: Range; - options: { isWholeLine: boolean; linesDecorationsClassName: string }; -}; - -export const executeActions = { - regexp: /^(GET|DELETE|POST|PUT)\s\w+/, - decorationClassName: 'action-execute-decoration', -}; - -export type SearchAction = { - qdsl: string; - actionPosition: Range; - qdslPosition: Range; - method: string; - index: string; - path: string; -}; - -export const searchTokensProvider = { - // Set defaultToken to invalid to see what you do not tokenize yet - defaultToken: 'invalid', - tokenPostfix: '.search', - - // keywords of elasticsearch - keywords: [ - 'GET', - 'POST', - 'PUT', - 'DELETE', - 'HEAD', - 'OPTIONS', - 'PATCH', - 'TRACE', - 'index', - 'indices', - 'type', - 'types', - 'from', - 'size', - 'explain', - 'analyze', - 'default_operator', - 'df', - 'analyzer', - 'lenient', - 'lowercase_expanded_terms', - 'analyze_wildcard', - 'all_shards', - 'allow_no_indices', - 'expand_wildcards', - 'preference', - 'routing', - 'ignore_unavailable', - 'allow_no_indices', - 'ignore_throttled', - 'search_type', - 'batched_reduce_size', - 'ccs_minimize_roundtrips', - 'max_concurrent_shard_requests', - 'pre_filter_shard_size', - 'rest_total_hits_as_int', - 'scroll', - 'search_type', - 'typed_keys', - 'wait_for_active_shards', - 'wait_for_completion', - 'requests_per_second', - 'slices', - 'timeout', - 'terminate_after', - 'stats', - 'version', - 'version_type', - 'if_seq_no', - 'if_primary_term', - 'refresh', - 'routing', - 'parent', - 'preference', - 'realtime', - 'refresh', - 'retry_on_conflict', - 'timeout', - 'version', - 'version_type', - 'if_seq_no', - 'if_primary_term', - 'pipeline', - 'wait_for_active_shards', - 'wait_for_completion', - 'requests_per_second', - 'slices', - 'timeout', - 'terminate_after', - 'stats', - 'version', - 'version_type', - 'if_seq_no', - 'if_primary_term', - 'refresh', - 'routing', - 'parent', - 'preference', - 'realtime', - 'refresh', - 'retry_on_conflict', - 'timeout', - 'version', - 'version_type', - 'if_seq_no', - 'if_primary_term', - 'pipeline', - 'wait_for_active_shards', - 'wait_for_completion', - 'requests_per_second', - 'slices', - 'timeout', - 'terminate_after', - 'stats', - 'version', - 'version_type', - '_search', - ], - - typeKeywords: ['any', 'boolean', 'number', 'object', 'string', 'undefined'], - - // we include these common regular expressions - symbols: /[=>) => { - const commands = lines.filter(({ lineContent }) => executeActions.regexp.test(lineContent)); - - return commands.map(({ lineContent, lineNumber }, index, commands) => { - const rawCmd = lineContent.split(/[/\s]+/); - const method = rawCmd[0]?.toUpperCase(); - const indexName = rawCmd[1]?.startsWith('_') ? undefined : rawCmd[1]; - const path = rawCmd.slice(indexName ? 2 : 1, rawCmd.length).join('/'); - const nexCommandLineNumber = commands[index + 1]?.lineNumber - ? commands[index + 1]?.lineNumber - 1 - : lines.length; - - const endLineNumber = - lines - .slice(lineNumber, nexCommandLineNumber) - .reverse() - .find(({ lineContent }) => lineContent.trim().endsWith('}'))?.lineNumber || lineNumber; - - const qdsl = lines - .slice(lineNumber, endLineNumber) - .map(({ lineContent }) => lineContent) - .join('\n'); - - return { - qdsl, - method, - index: indexName, - path, - actionPosition: { - startLineNumber: lineNumber, - endLineNumber: lineNumber, - startColumn: 1, - endColumn: lineContent.length, - }, - qdslPosition: qdsl - ? { - startLineNumber: lineNumber + 1, - startColumn: 1, - endLineNumber, - endColumn: lines[endLineNumber].lineContent.length, - } - : null, - } as SearchAction; - }); -}; - -export const defaultCodeSnippet = ` -// Cluster Health -GET _cluster/health - -// Cluster State -GET _cluster/state - -// Nodes Info -GET _nodes/info - -// Create Index -PUT dockit_sample_index - -// Delete Index -DELETE dockit_sample_index - - -// Get Mapping -GET dockit_sample_index/_mapping - - -// Put Mapping -PUT dockit_sample_index/_mapping -{ - "properties": { - "name": { - "type": "text" - } - } -} - -// Aliases -POST _aliases -{ - "actions": [ - { - "add": { - "index": "dockit_sample_index", - "alias": "dockit_sample_index_alias" - } - } - ] -} - -// Indexing Documents -POST dockit_sample_index/_doc/1 -{ - "name": "Elasticsearch", - "category": "Search Engine" -} - -// Searching -POST dockit_sample_index/_search -{ - "query": { - "match": { - "name": "Elasticsearch" - } - } -} - -// Count -POST dockit_sample_index/_count -{ - "query": { - "term": { - "category.keyword": "Search Engine" - } - } -} - -// Get Document -GET dockit_sample_index/_doc/1 - -// Update Document -POST dockit_sample_index/_update/1 -{ - "doc": { - "category": "Search Engine" - } -} - -// Delete Document -DELETE dockit_sample_index/_doc/1 - - -// Bulk API -POST _bulk -{"index": {"_index": "dockit_sample_index", "_id": "1"}} -{"name": "Document 1"} -{"delete": {"_index": "dockit_sample_index", "_id": "2"}} -`; - -export enum ActionType { - POST_INDEX = 'POST_INDEX', - POST_SEARCH = 'POST_SEARCH', - POST_COUNT = 'POST_COUNT', - GET_SEARCH = 'GET_SEARCH', - POST_UPDATE = 'POST_UPDATE', - DELETE_DOC = 'DELETE_DOC', - PUT_INDEX = 'PUT_INDEX', - DELETE_INDEX = 'DELETE_INDEX', - POST_BULK = 'POST_BULK', - PUT_PUT_INDEX = 'PUT_PUT_INDEX', - PUT_MAPPING = 'PUT_MAPPING', - GET_MAPPING = 'GET_MAPPING', - POST_ALIAS = 'POST_ALIAS', - GET_HEALTH = 'GET_HEALTH', - GET_STATE = 'GET_STATE', - GET_INFO = 'GET_INFO', - HEAD_INDEX = 'HEAD_INDEX', - PUT_AUTO_FOLLOW = 'PUT_AUTO_FOLLOW', - PUT_CCR_FOLLOW = 'PUT_CCR_FOLLOW', - PUT_SLM_POLICY = 'PUT_SLM_POLICY', - PUT_SECURITY_ROLE_MAPPING = 'PUT_SECURITY_ROLE_MAPPING', - PUT_ROLLUP_JOB = 'PUT_ROLLUP_JOB', - PUT_SECURITY_API_KEY = 'PUT_SECURITY_API_KEY', - PUT_INGEST_PIPELINE = 'PUT_INGEST_PIPELINE', - PUT_TRANSFORM = 'PUT_TRANSFORM', - POST_ML_INFER = 'POST_ML_INFER', - POST_MULTI_SEARCH = 'POST_MULTI_SEARCH', - POST_OPEN_INDEX = 'POST_OPEN_INDEX', - PUT_COMPONENT_TEMPLATE = 'PUT_COMPONENT_TEMPLATE', - PUT_ENRICH_POLICY = 'PUT_ENRICH_POLICY', - PUT_TEMPLATE = 'PUT_TEMPLATE', -} - -const actionRegexMap: { [key in ActionType]: RegExp } = { - POST_INDEX: /POST \/_doc\/\d+/, - POST_SEARCH: /POST \/_search/, - POST_COUNT: /POST \/_count/, - GET_SEARCH: /GET \/_doc\/\d+/, - POST_UPDATE: /POST \/_update\/\d+/, - DELETE_DOC: /DELETE \/_doc\/\d+/, - PUT_INDEX: /PUT /, - DELETE_INDEX: /DELETE /, - POST_BULK: /POST \/_bulk/, - PUT_PUT_INDEX: /PUT /, - PUT_MAPPING: /PUT \/_mapping/, - GET_MAPPING: /GET \/_mapping/, - POST_ALIAS: /POST \/_aliases/, - GET_HEALTH: /GET \/_cluster\/health/, - GET_STATE: /GET \/_cluster\/state/, - GET_INFO: /GET \/_nodes\/info/, - HEAD_INDEX: /HEAD /, - PUT_AUTO_FOLLOW: /PUT \/_ccr\/auto_follow\/\w+/, - PUT_CCR_FOLLOW: /PUT \/_ccr\/follow/, - PUT_SLM_POLICY: /PUT \/_slm\/policy\/\w+/, - PUT_SECURITY_ROLE_MAPPING: /PUT \/_security\/role_mapping\/\w+/, - PUT_ROLLUP_JOB: /PUT \/_rollup\/job\/\w+/, - PUT_SECURITY_API_KEY: /PUT \/_security\/api_key/, - PUT_INGEST_PIPELINE: /PUT \/_ingest\/pipeline\/\w+/, - PUT_TRANSFORM: /PUT \/_transform\/\w+/, - POST_ML_INFER: /POST \/_ml\/infer\/\w+/, - POST_MULTI_SEARCH: /POST \/_msearch/, - POST_OPEN_INDEX: /POST \/_open/, - PUT_COMPONENT_TEMPLATE: /PUT \/_component_template\/\w+/, - PUT_ENRICH_POLICY: /PUT \/_enrich\/policy\/\w+/, - PUT_TEMPLATE: /PUT \/_template\/\w+/, -}; - -export enum EngineType { - ELASTICSEARCH = 'ELASTICSEARCH', - OPENSEARCH = 'OPENSEARCH', -} - -export const getActionApiDoc = (engine: EngineType, version: string, action: SearchAction) => { - const { APIS } = getDocLinks(engine, version); - const matchedAction = Object.entries(actionRegexMap).find(([, regex]) => - `${action.method} /${action.path}`.match(regex), - ); - - return matchedAction ? APIS[matchedAction[0] as ActionType] : undefined; -}; - -const getDocLinks = (engine: EngineType, version: string) => { - const DOCS_LINK = `https://www.elastic.co/guide/en/elasticsearch/reference/${version}`; - const linksMap: { - [key in EngineType]: { - APIS: { - [key in ActionType]: string; - }; - }; - } = { - [EngineType.ELASTICSEARCH]: { - APIS: { - POST_INDEX: `${DOCS_LINK}/indices-create-index.html`, - POST_SEARCH: `${DOCS_LINK}/search-search.html`, - POST_COUNT: `${DOCS_LINK}/search-count.html`, - GET_SEARCH: `${DOCS_LINK}/docs-get.html`, - POST_UPDATE: `${DOCS_LINK}/docs-update.html`, - DELETE_DOC: `${DOCS_LINK}/docs-delete.html`, - PUT_INDEX: `${DOCS_LINK}/indices-create-index.html`, - DELETE_INDEX: `${DOCS_LINK}/indices-delete-index.html`, - POST_BULK: `${DOCS_LINK}/docs-bulk.html`, - PUT_PUT_INDEX: `${DOCS_LINK}/indices-create-index.html`, - PUT_MAPPING: `${DOCS_LINK}/indices-put-mapping.html`, - GET_MAPPING: `${DOCS_LINK}/indices-get-mapping.html`, - POST_ALIAS: `${DOCS_LINK}/indices-aliases.html`, - GET_HEALTH: `${DOCS_LINK}/cluster-health.html`, - GET_STATE: `${DOCS_LINK}/indices-stats.html`, - GET_INFO: '', - HEAD_INDEX: `${DOCS_LINK}/indices-exists.html`, - PUT_AUTO_FOLLOW: `${DOCS_LINK}/ccr-put-auto-follow-pattern.html`, - PUT_CCR_FOLLOW: `${DOCS_LINK}/ccr-put-follow.html`, - PUT_SLM_POLICY: `${DOCS_LINK}/slm-api-put-policy.html`, - PUT_SECURITY_ROLE_MAPPING: `${DOCS_LINK}/security-api-put-role-mapping.html`, - PUT_ROLLUP_JOB: `${DOCS_LINK}/rollup-put-job.html#rollup-put-job-api-request-body`, - PUT_SECURITY_API_KEY: `${DOCS_LINK}/security-api-create-api-key.html`, - PUT_INGEST_PIPELINE: `${DOCS_LINK}/put-pipeline-api.html`, - PUT_TRANSFORM: `${DOCS_LINK}/put-transform.html#put-transform-request-body`, - POST_ML_INFER: `${DOCS_LINK}/infer-trained-model.html`, - POST_MULTI_SEARCH: `${DOCS_LINK}/search-multi-search.html`, - POST_OPEN_INDEX: `${DOCS_LINK}/indices-open-close.html`, - PUT_COMPONENT_TEMPLATE: `${DOCS_LINK}/indices-component-template.html`, - PUT_ENRICH_POLICY: `${DOCS_LINK}/put-enrich-policy-api.html`, - PUT_TEMPLATE: `${DOCS_LINK}/indices-templates-v1.html`, - }, - }, - // @TODO docs link for OpenSearch - [EngineType.OPENSEARCH]: { APIS: {} } as { APIS: { [key in ActionType]: string } }, - }; - - return linksMap[engine]; -}; diff --git a/src/common/index.ts b/src/common/index.ts index 06425b8..b5cbff8 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -2,6 +2,5 @@ export * from './customError'; export * from './debounceThrottle'; export * from './debug'; export * from './pureObject'; -export * from './editor'; export * from './base64'; export * from './crypto'; diff --git a/src/common/monaco/environment.ts b/src/common/monaco/environment.ts new file mode 100644 index 0000000..3f1ce73 --- /dev/null +++ b/src/common/monaco/environment.ts @@ -0,0 +1,39 @@ +/** + * refer https://github.com/wobsoriano/codeplayground + * https://github.com/wobsoriano/codeplayground/blob/master/src/components/MonacoEditor.vue + */ +export const monacoEnvironment = { + // @ts-ignore + async getWorker(_, label) { + let worker; + + switch (label) { + case 'json': + // @ts-ignore + worker = await import('monaco-editor/esm/vs/language/json/json.worker?worker'); + break; + case 'css': + case 'scss': + case 'less': + // @ts-ignore + worker = await import('monaco-editor/esm/vs/language/css/css.worker?worker'); + break; + case 'html': + case 'handlebars': + case 'razor': + // @ts-ignore + worker = await import('monaco-editor/esm/vs/language/html/html.worker?worker'); + break; + case 'typescript': + case 'javascript': + // @ts-ignore + worker = await import('monaco-editor/esm/vs/language/typescript/ts.worker?worker'); + break; + default: + // @ts-ignore + worker = await import('monaco-editor/esm/vs/editor/editor.worker?worker'); + } + + return new worker.default(); + }, +}; diff --git a/src/common/monaco/index.ts b/src/common/monaco/index.ts new file mode 100644 index 0000000..1d7a8e9 --- /dev/null +++ b/src/common/monaco/index.ts @@ -0,0 +1,61 @@ +import * as monaco from 'monaco-editor'; + +import { search, executeActions } from './lexerRules.ts'; +import { monacoEnvironment } from './environment.ts'; +import { buildSearchToken } from './tokenlizer.ts'; + +self.MonacoEnvironment = monacoEnvironment; + +monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true); +monaco.languages.register({ id: search.id }); +monaco.languages.setMonarchTokensProvider( + search.id, + search.rules as monaco.languages.IMonarchLanguage, +); +monaco.languages.setLanguageConfiguration( + search.id, + search.languageConfiguration as monaco.languages.LanguageConfiguration, +); +monaco.languages.registerCompletionItemProvider(search.id, { + triggerCharacters: ['g', 'p', 'd'], + // @ts-ignore + provideCompletionItems: function (model, position) { + const textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + const matches = new Map([ + [/^ge?t?$/gi, 'GET '], + [/^put?$/gi, 'PUT '], + [/^pos?t?$/gi, 'POST '], + [/^de?l?e?t?e?$/gi, 'DELETE '], + ]); + const matchKey = Array.from(matches.keys()).find(regex => regex.test(textUntilPosition)); + if (!matchKey) { + return; + } + const word = matches.get(matchKey); + const range = { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: position.column - word!.length, + endColumn: position.column, + }; + + return { + suggestions: [ + { + label: word, + kind: monaco.languages.CompletionItemKind.Keyword, + insertText: word, + range: range, + }, + ], + }; + }, +}); +export * from './type.ts'; +export { monaco, executeActions, buildSearchToken }; +export * from './referDoc.ts'; diff --git a/src/common/monaco/lexerRules.ts b/src/common/monaco/lexerRules.ts new file mode 100644 index 0000000..e4974eb --- /dev/null +++ b/src/common/monaco/lexerRules.ts @@ -0,0 +1,305 @@ +export const xJson = { + id: 'xjson', + rules: { + defaultToken: 'invalid', + tokenPostfix: '', + escapes: /\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/, + tokenizer: { + root: [ + [ + /("(?:[^"]*_)?script"|"inline"|"source")(\s*?)(:)(\s*?)(""")/, + [ + 'variable', + 'whitespace', + 'ace.punctuation.colon', + 'whitespace', + { + token: 'punctuation.start_triple_quote', + nextEmbedded: 'painless', + next: 'my_painless', + }, + ], + ], + [ + /(:)(\s*?)(""")(sql)/, + [ + 'ace.punctuation.colon', + 'whitespace', + 'punctuation.start_triple_quote', + { + token: 'punctuation.start_triple_quote.lang_marker', + nextEmbedded: 'opensearchql', + next: 'my_sql', + }, + ], + ], + [/{/, { token: 'paren.lparen', next: '@push' }], + [/}/, { token: 'paren.rparen', next: '@pop' }], + [/[[(]/, { token: 'paren.lparen' }], + [/[\])]/, { token: 'paren.rparen' }], + [/,/, { token: 'punctuation.comma' }], + [/:/, { token: 'punctuation.colon' }], + [/\s+/, { token: 'whitespace' }], + [/["](?:(?:\\.)|(?:[^"\\]))*?["]\s*(?=:)/, { token: 'variable' }], + [/"""/, { token: 'string_literal', next: 'string_literal' }], + [/0[xX][0-9a-fA-F]+\b/, { token: 'constant.numeric' }], + [/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/, { token: 'constant.numeric' }], + [/(?:true|false)\b/, { token: 'constant.language.boolean' }], + // strings + [/"([^"\\]|\\.)*$/, 'string.invalid'], // non-teminated string + [ + /"/, + { + token: 'string.quote', + bracket: '@open', + next: '@string', + }, + ], + [/['](?:(?:\\.)|(?:[^'\\]))*?[']/, { token: 'invalid' }], + [/.+?/, { token: 'text' }], + [/\/\/.*$/, { token: 'invalid' }], + ], + + my_painless: [ + [ + /"""/, + { + token: 'punctuation.end_triple_quote', + nextEmbedded: '@pop', + next: '@pop', + }, + ], + ], + + my_sql: [ + [ + /"""/, + { + token: 'punctuation.end_triple_quote', + nextEmbedded: '@pop', + next: '@pop', + }, + ], + ], + + string: [ + [/[^\\"]+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/"/, { token: 'string.quote', bracket: '@close', next: '@pop' }], + ], + + string_literal: [ + [/"""/, { token: 'punctuation.end_triple_quote', next: '@pop' }], + [/./, { token: 'multi_string' }], + ], + }, + }, +}; +export const executeActions = { + regexp: /^(GET|DELETE|POST|PUT)\s\w+/, + decorationClassName: 'action-execute-decoration', +}; + +export const search = { + id: 'search', + rules: { + // Set defaultToken to invalid to see what you do not tokenize yet + defaultToken: 'invalid', + tokenPostfix: '.search', + + // keywords of elasticsearch + keywords: [ + 'GET', + 'POST', + 'PUT', + 'DELETE', + 'HEAD', + 'OPTIONS', + 'PATCH', + 'TRACE', + 'index', + 'indices', + 'type', + 'types', + 'from', + 'size', + 'explain', + 'analyze', + 'default_operator', + 'df', + 'analyzer', + 'lenient', + 'lowercase_expanded_terms', + 'analyze_wildcard', + 'all_shards', + 'allow_no_indices', + 'expand_wildcards', + 'preference', + 'routing', + 'ignore_unavailable', + 'allow_no_indices', + 'ignore_throttled', + 'search_type', + 'batched_reduce_size', + 'ccs_minimize_roundtrips', + 'max_concurrent_shard_requests', + 'pre_filter_shard_size', + 'rest_total_hits_as_int', + 'scroll', + 'search_type', + 'typed_keys', + 'wait_for_active_shards', + 'wait_for_completion', + 'requests_per_second', + 'slices', + 'timeout', + 'terminate_after', + 'stats', + 'version', + 'version_type', + 'if_seq_no', + 'if_primary_term', + 'refresh', + 'routing', + 'parent', + 'preference', + 'realtime', + 'refresh', + 'retry_on_conflict', + 'timeout', + 'version', + 'version_type', + 'if_seq_no', + 'if_primary_term', + 'pipeline', + 'wait_for_active_shards', + 'wait_for_completion', + 'requests_per_second', + 'slices', + 'timeout', + 'terminate_after', + 'stats', + 'version', + 'version_type', + 'if_seq_no', + 'if_primary_term', + 'refresh', + 'routing', + 'parent', + 'preference', + 'realtime', + 'refresh', + 'retry_on_conflict', + 'timeout', + 'version', + 'version_type', + 'if_seq_no', + 'if_primary_term', + 'pipeline', + 'wait_for_active_shards', + 'wait_for_completion', + 'requests_per_second', + 'slices', + 'timeout', + 'terminate_after', + 'stats', + 'version', + 'version_type', + '_search', + ], + + typeKeywords: ['any', 'boolean', 'number', 'object', 'string', 'undefined'], + + // we include these common regular expressions + symbols: /[=> { + const { APIS } = getDocLinks(engine, version); + const matchedAction = Object.entries(actionRegexMap).find(([, regex]) => + `${action.method} /${action.path}`.match(regex), + ); + + return matchedAction ? APIS[matchedAction[0] as ActionType] : undefined; +}; + +const getDocLinks = (engine: EngineType, version: string) => { + const DOCS_LINK = `https://www.elastic.co/guide/en/elasticsearch/reference/${version}`; + const linksMap: { + [key in EngineType]: { + APIS: { + [key in ActionType]: string; + }; + }; + } = { + [EngineType.ELASTICSEARCH]: { + APIS: { + POST_INDEX: `${DOCS_LINK}/indices-create-index.html`, + POST_SEARCH: `${DOCS_LINK}/search-search.html`, + POST_COUNT: `${DOCS_LINK}/search-count.html`, + GET_SEARCH: `${DOCS_LINK}/docs-get.html`, + POST_UPDATE: `${DOCS_LINK}/docs-update.html`, + DELETE_DOC: `${DOCS_LINK}/docs-delete.html`, + PUT_INDEX: `${DOCS_LINK}/indices-create-index.html`, + DELETE_INDEX: `${DOCS_LINK}/indices-delete-index.html`, + POST_BULK: `${DOCS_LINK}/docs-bulk.html`, + PUT_PUT_INDEX: `${DOCS_LINK}/indices-create-index.html`, + PUT_MAPPING: `${DOCS_LINK}/indices-put-mapping.html`, + GET_MAPPING: `${DOCS_LINK}/indices-get-mapping.html`, + POST_ALIAS: `${DOCS_LINK}/indices-aliases.html`, + GET_HEALTH: `${DOCS_LINK}/cluster-health.html`, + GET_STATE: `${DOCS_LINK}/indices-stats.html`, + GET_INFO: '', + HEAD_INDEX: `${DOCS_LINK}/indices-exists.html`, + PUT_AUTO_FOLLOW: `${DOCS_LINK}/ccr-put-auto-follow-pattern.html`, + PUT_CCR_FOLLOW: `${DOCS_LINK}/ccr-put-follow.html`, + PUT_SLM_POLICY: `${DOCS_LINK}/slm-api-put-policy.html`, + PUT_SECURITY_ROLE_MAPPING: `${DOCS_LINK}/security-api-put-role-mapping.html`, + PUT_ROLLUP_JOB: `${DOCS_LINK}/rollup-put-job.html#rollup-put-job-api-request-body`, + PUT_SECURITY_API_KEY: `${DOCS_LINK}/security-api-create-api-key.html`, + PUT_INGEST_PIPELINE: `${DOCS_LINK}/put-pipeline-api.html`, + PUT_TRANSFORM: `${DOCS_LINK}/put-transform.html#put-transform-request-body`, + POST_ML_INFER: `${DOCS_LINK}/infer-trained-model.html`, + POST_MULTI_SEARCH: `${DOCS_LINK}/search-multi-search.html`, + POST_OPEN_INDEX: `${DOCS_LINK}/indices-open-close.html`, + PUT_COMPONENT_TEMPLATE: `${DOCS_LINK}/indices-component-template.html`, + PUT_ENRICH_POLICY: `${DOCS_LINK}/put-enrich-policy-api.html`, + PUT_TEMPLATE: `${DOCS_LINK}/indices-templates-v1.html`, + }, + }, + // @TODO docs link for OpenSearch + [EngineType.OPENSEARCH]: { APIS: {} } as { APIS: { [key in ActionType]: string } }, + }; + + return linksMap[engine]; +}; diff --git a/src/common/monaco/tokenlizer.ts b/src/common/monaco/tokenlizer.ts new file mode 100644 index 0000000..ae643f9 --- /dev/null +++ b/src/common/monaco/tokenlizer.ts @@ -0,0 +1,47 @@ +import { executeActions, SearchAction } from './'; + +export const buildSearchToken = (lines: Array<{ lineNumber: number; lineContent: string }>) => { + const commands = lines.filter(({ lineContent }) => executeActions.regexp.test(lineContent)); + + return commands.map(({ lineContent, lineNumber }, index, commands) => { + const rawCmd = lineContent.split(/[/\s]+/); + const method = rawCmd[0]?.toUpperCase(); + const indexName = rawCmd[1]?.startsWith('_') ? undefined : rawCmd[1]; + const path = rawCmd.slice(indexName ? 2 : 1, rawCmd.length).join('/'); + const nexCommandLineNumber = commands[index + 1]?.lineNumber + ? commands[index + 1]?.lineNumber - 1 + : lines.length; + + const endLineNumber = + lines + .slice(lineNumber, nexCommandLineNumber) + .reverse() + .find(({ lineContent }) => lineContent.trim().endsWith('}'))?.lineNumber || lineNumber; + + const qdsl = lines + .slice(lineNumber, endLineNumber) + .map(({ lineContent }) => lineContent) + .join('\n'); + + return { + qdsl, + method, + index: indexName, + path, + actionPosition: { + startLineNumber: lineNumber, + endLineNumber: lineNumber, + startColumn: 1, + endColumn: lineContent.length, + }, + qdslPosition: qdsl + ? { + startLineNumber: lineNumber + 1, + startColumn: 1, + endLineNumber, + endColumn: lines[endLineNumber].lineContent.length, + } + : null, + } as SearchAction; + }); +}; diff --git a/src/common/monaco/type.ts b/src/common/monaco/type.ts new file mode 100644 index 0000000..7f37a98 --- /dev/null +++ b/src/common/monaco/type.ts @@ -0,0 +1,63 @@ +import { monaco } from './index.ts'; + +export type Range = { + startLineNumber: number; + startColumn: number; + endLineNumber: number; + endColumn: number; +}; +export type Decoration = { + id: number; + range: Range; + options: { isWholeLine: boolean; linesDecorationsClassName: string }; +}; + +export type SearchAction = { + qdsl: string; + actionPosition: Range; + qdslPosition: Range; + method: string; + index: string; + path: string; +}; + +export enum ActionType { + POST_INDEX = 'POST_INDEX', + POST_SEARCH = 'POST_SEARCH', + POST_COUNT = 'POST_COUNT', + GET_SEARCH = 'GET_SEARCH', + POST_UPDATE = 'POST_UPDATE', + DELETE_DOC = 'DELETE_DOC', + PUT_INDEX = 'PUT_INDEX', + DELETE_INDEX = 'DELETE_INDEX', + POST_BULK = 'POST_BULK', + PUT_PUT_INDEX = 'PUT_PUT_INDEX', + PUT_MAPPING = 'PUT_MAPPING', + GET_MAPPING = 'GET_MAPPING', + POST_ALIAS = 'POST_ALIAS', + GET_HEALTH = 'GET_HEALTH', + GET_STATE = 'GET_STATE', + GET_INFO = 'GET_INFO', + HEAD_INDEX = 'HEAD_INDEX', + PUT_AUTO_FOLLOW = 'PUT_AUTO_FOLLOW', + PUT_CCR_FOLLOW = 'PUT_CCR_FOLLOW', + PUT_SLM_POLICY = 'PUT_SLM_POLICY', + PUT_SECURITY_ROLE_MAPPING = 'PUT_SECURITY_ROLE_MAPPING', + PUT_ROLLUP_JOB = 'PUT_ROLLUP_JOB', + PUT_SECURITY_API_KEY = 'PUT_SECURITY_API_KEY', + PUT_INGEST_PIPELINE = 'PUT_INGEST_PIPELINE', + PUT_TRANSFORM = 'PUT_TRANSFORM', + POST_ML_INFER = 'POST_ML_INFER', + POST_MULTI_SEARCH = 'POST_MULTI_SEARCH', + POST_OPEN_INDEX = 'POST_OPEN_INDEX', + PUT_COMPONENT_TEMPLATE = 'PUT_COMPONENT_TEMPLATE', + PUT_ENRICH_POLICY = 'PUT_ENRICH_POLICY', + PUT_TEMPLATE = 'PUT_TEMPLATE', +} + +export enum EngineType { + ELASTICSEARCH = 'ELASTICSEARCH', + OPENSEARCH = 'OPENSEARCH', +} + +export type Monaco = typeof monaco.editor.create; diff --git a/src/datasources/fetchApi.ts b/src/datasources/fetchApi.ts index f22d05b..6e167af 100644 --- a/src/datasources/fetchApi.ts +++ b/src/datasources/fetchApi.ts @@ -8,7 +8,7 @@ type FetchApiOptions = { [key: string]: string | number | undefined; }; agent: { ssl: boolean } | undefined; - payload: unknown; + payload?: string; }; const handleFetch = (result: { data: unknown; status: number; details: string | undefined }) => { @@ -43,7 +43,7 @@ const fetchWrapper = async ({ method: string; path?: string; queryParameters?: string; - payload?: unknown; + payload?: string; username?: string; password?: string; host: string; @@ -80,7 +80,7 @@ const fetchRequest = async ( const { status, message, data } = JSON.parse( await invoke('fetch_api', { url, - options: { method, headers, body: payload ? JSON.stringify(payload) : undefined, agent }, + options: { method, headers, body: payload ?? undefined, agent }, }), ) as { status: number; message: string; data: unknown }; @@ -117,7 +117,7 @@ const loadHttpClient = (con: { queryParameters, ssl: con.sslCertVerification, }), - post: async (path: string, queryParameters?: string, payload?: unknown) => + post: async (path: string, queryParameters?: string, payload?: string) => fetchWrapper({ ...con, method: 'POST', @@ -126,7 +126,7 @@ const loadHttpClient = (con: { payload, ssl: con.sslCertVerification, }), - put: async (path: string, queryParameters?: string, payload?: unknown) => + put: async (path: string, queryParameters?: string, payload?: string) => fetchWrapper({ ...con, method: 'PUT', @@ -136,7 +136,7 @@ const loadHttpClient = (con: { ssl: con.sslCertVerification, }), - delete: async (path: string, queryParameters?: string, payload?: unknown) => + delete: async (path: string, queryParameters?: string, payload?: string) => fetchWrapper({ ...con, method: 'DELETE', diff --git a/src/store/connectionStore.ts b/src/store/connectionStore.ts index e4defcf..9b7871c 100644 --- a/src/store/connectionStore.ts +++ b/src/store/connectionStore.ts @@ -136,7 +136,7 @@ export const useConnectionStore = defineStore('connectionStore', { method: string; path: string; index?: string; - qdsl?: { [key: string]: unknown }; + qdsl?: string; }) { if (!this.established) throw new Error('no connection established'); const client = loadHttpClient(this.established); @@ -158,14 +158,13 @@ export const useConnectionStore = defineStore('connectionStore', { } const reqPath = buildPath(index, path); - const body = qdsl ?? undefined; const dispatch: { [method: string]: () => Promise } = { - POST: async () => client.post(reqPath, undefined, body), - PUT: async () => client.put(reqPath, undefined, body), - DELETE: async () => client.delete(reqPath, undefined, body), + POST: async () => client.post(reqPath, undefined, qdsl), + PUT: async () => client.put(reqPath, undefined, qdsl), + DELETE: async () => client.delete(reqPath, undefined, qdsl), GET: async () => - body ? client.post(reqPath, undefined, body) : client.get(reqPath, 'format=json'), + qdsl ? client.post(reqPath, undefined, qdsl) : client.get(reqPath, 'format=json'), }; return dispatch[method](); }, diff --git a/src/views/editor/index.vue b/src/views/editor/index.vue index 2f626ca..27c072f 100644 --- a/src/views/editor/index.vue +++ b/src/views/editor/index.vue @@ -11,24 +11,24 @@