add discover and console module

This commit is contained in:
medcl 2021-06-07 10:57:29 +08:00
parent 7f4ef3e399
commit 77fc87c3dc
1797 changed files with 161137 additions and 911 deletions

2
Jenkinsfile vendored
View File

@ -71,7 +71,7 @@ pipeline {
stage('Start Backend Docker') {
steps {
sh 'cd /home/deploy/search-center/docker && docker-compose -f docker-compose.dev.yml up -d'
sh 'cd /home/deploy/search-center/docker && docker-compose -f docker-compose.dev.yml up --remove-orphans -d'
}
}

480
Makefile Normal file → Executable file
View File

@ -1,233 +1,247 @@
SHELL=/bin/bash
# APP info
APP_NAME := search-center
APP_VERSION := 1.0.0_SNAPSHOT
APP_CONFIG := $(APP_NAME).yml
APP_EOLDate := "2021-12-31 10:10:10"
APP_STATIC_FOLDER := .public
APP_STATIC_PACKAGE := public
APP_UI_FOLDER := ui
APP_PLUGIN_FOLDER := plugin
# Get release version from environment
ifneq "$(VERSION)" ""
APP_VERSION := $(VERSION)
endif
ifneq "$(EOL)" ""
APP_EOLDate := $(EOL)
endif
# Ensure GOPATH is set before running build process.
ifeq "$(GOPATH)" ""
GOPATH := ~/go
#$(error Please set the environment variable GOPATH before running `make`)
endif
PATH := $(PATH):$(GOPATH)/bin
# Go environment
CURDIR := $(shell pwd)
OLDGOPATH:= $(GOPATH)
CMD_DIR := $(CURDIR)/cmd
OUTPUT_DIR := $(CURDIR)/bin
# INFINI framework
INFINI_BASE_FOLDER := $(OLDGOPATH)/src/infini.sh/
FRAMEWORK_FOLDER := $(INFINI_BASE_FOLDER)/framework/
FRAMEWORK_REPO := ssh://git@git.infini.ltd:64221/infini/framework.git
FRAMEWORK_BRANCH := master
FRAMEWORK_VENDOR_FOLDER := $(CURDIR)/../vendor/
FRAMEWORK_VENDOR_REPO := ssh://git@git.infini.ltd:64221/infini/framework-vendor.git
FRAMEWORK_VENDOR_BRANCH := master
NEWGOPATH:= $(CURDIR):$(FRAMEWORK_VENDOR_FOLDER):$(GOPATH)
GO := GO15VENDOREXPERIMENT="1" GO111MODULE=off go
GOBUILD := GOPATH=$(NEWGOPATH) CGO_ENABLED=0 GRPC_GO_REQUIRE_HANDSHAKE=off $(GO) build -ldflags='-s -w' -gcflags "-m" --work
GOBUILDNCGO := GOPATH=$(NEWGOPATH) CGO_ENABLED=1 $(GO) build -ldflags -s
GOTEST := GOPATH=$(NEWGOPATH) CGO_ENABLED=1 $(GO) test -ldflags -s
ARCH := "`uname -s`"
LINUX := "Linux"
MAC := "Darwin"
GO_FILES=$(find . -iname '*.go' | grep -v /vendor/)
PKGS=$(go list ./... | grep -v /vendor/)
FRAMEWORK_OFFLINE_BUILD := ""
ifneq "$(OFFLINE_BUILD)" ""
FRAMEWORK_OFFLINE_BUILD := $(OFFLINE_BUILD)
endif
.PHONY: all build update test clean
default: build
env:
@echo OLDGOPATH$(OLDGOPATH)
@echo GOPATH$(GOPATH)
@echo NEWGOPATH$(NEWGOPATH)
@echo INFINI_BASE_FOLDER$(INFINI_BASE_FOLDER)
@echo FRAMEWORK_FOLDER$(FRAMEWORK_FOLDER)
@echo FRAMEWORK_VENDOR_FOLDER$(FRAMEWORK_VENDOR_FOLDER)
build: config
$(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)
@$(MAKE) restore-generated-file
build-cmd: config
@$(MAKE) restore-generated-file
@for f in $(shell ls ${CMD_DIR}); do (cd $(CMD_DIR)/$${f} && $(GOBUILD) -o $(OUTPUT_DIR)/$${f}); done
# used to build the binary for gdb debugging
build-race: clean config update-vfs
$(GOBUILD) -gcflags "-m -N -l" -race -o $(OUTPUT_DIR)/$(APP_NAME)
@$(MAKE) restore-generated-file
tar: build
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/$(APP_NAME).tar.gz $(APP_NAME) $(APP_CONFIG)
cross-build: clean config update-vfs
$(GO) test
GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows64.exe
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-darwin64
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux64
@$(MAKE) restore-generated-file
build-win:
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows64.exe
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOOS=windows GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows32.exe
build-linux:
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux64
GOOS=linux GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux32
build-arm:
GOOS=linux GOARCH=arm64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-arm64
GOOS=linux GOARCH=arm GOARM=5 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-armv5
GOOS=linux GOARCH=arm GOARM=6 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-armv6
GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-armv7
build-darwin:
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-darwin64
#GOOS=darwin GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-darwin32
build-bsd:
GOOS=freebsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-freebsd64
GOOS=freebsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-freebsd32
GOOS=netbsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-netbsd64
GOOS=netbsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-netbsd32
GOOS=openbsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-openbsd64
GOOS=openbsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-openbsd32
all: clean config update-vfs cross-build restore-generated-file
all-platform: clean config update-vfs cross-build-all-platform restore-generated-file
cross-build-all-platform: clean config build-bsd build-linux build-darwin build-win restore-generated-file
format:
go fmt $$(go list ./... | grep -v /vendor/)
clean_data:
rm -rif dist
rm -rif data
rm -rif log
clean: clean_data
rm -rif $(OUTPUT_DIR)
mkdir $(OUTPUT_DIR)
init:
@echo building $(APP_NAME) $(APP_VERSION)
@echo $(CURDIR)
@mkdir -p $(INFINI_BASE_FOLDER)
@echo "framework path: " $(FRAMEWORK_FOLDER)
@if [ ! -d $(FRAMEWORK_FOLDER) ]; then echo "framework does not exist";(cd $(INFINI_BASE_FOLDER)&&git clone -b $(FRAMEWORK_BRANCH) $(FRAMEWORK_REPO) framework ) fi
@if [ ! -d $(FRAMEWORK_VENDOR_FOLDER) ]; then echo "framework vendor does not exist";(cd $(INFINI_BASE_FOLDER)&&git clone -b $(FRAMEWORK_VENDOR_BRANCH) $(FRAMEWORK_VENDOR_REPO) vendor) fi
@if [ "" == $(FRAMEWORK_OFFLINE_BUILD) ]; then (cd $(FRAMEWORK_FOLDER) && git pull origin $(FRAMEWORK_BRANCH)); fi;
@if [ "" == $(FRAMEWORK_OFFLINE_BUILD) ]; then (cd $(FRAMEWORK_VENDOR_FOLDER) && git pull origin $(FRAMEWORK_VENDOR_BRANCH)); fi;
update-generated-file:
@echo "update generated info"
@echo -e "package config\n\nconst LastCommitLog = \""`git log -1 --pretty=format:"%h, %ad, %an, %s"` "\"\nconst BuildDate = \"`date "+%Y-%m-%d %H:%M:%S"`\"" > config/generated.go
@echo -e "\nconst EOLDate = \"$(APP_EOLDate)\"" >> config/generated.go
@echo -e "\nconst Version = \"$(APP_VERSION)\"" >> config/generated.go
restore-generated-file:
@echo "restore generated info"
@echo -e "package config\n\nconst LastCommitLog = \"N/A\"\nconst BuildDate = \"N/A\"" > config/generated.go
@echo -e "\nconst EOLDate = \"N/A\"" >> config/generated.go
@echo -e "\nconst Version = \"0.0.1-SNAPSHOT\"" >> config/generated.go
update-vfs:
cd $(FRAMEWORK_FOLDER) && cd cmd/vfs && $(GO) build && cp vfs ~/
@if [ -d $(APP_STATIC_FOLDER) ]; then echo "generate static files";(cd $(APP_STATIC_FOLDER) && ~/vfs -ignore="static.go|.DS_Store" -o static.go -pkg $(APP_STATIC_PACKAGE) . ) fi
config: init update-vfs update-generated-file
@echo "update configs"
@# $(GO) env
@mkdir -p $(OUTPUT_DIR)
@cp $(APP_CONFIG) $(OUTPUT_DIR)/$(APP_CONFIG)
dist: cross-build package
dist-major-platform: all package
dist-all-platform: all-platform package-all-platform
package:
@echo "Packaging"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/darwin64.tar.gz darwin64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux64.tar.gz linux64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows64.tar.gz windows64 $(APP_CONFIG)
package-all-platform: package-darwin-platform package-linux-platform package-windows-platform
@echo "Packaging all"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/freebsd64.tar.gz $(APP_NAME)-freebsd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/freebsd32.tar.gz $(APP_NAME)-freebsd32 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/netbsd64.tar.gz $(APP_NAME)-netbsd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/netbsd32.tar.gz $(APP_NAME)-netbsd32 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/openbsd64.tar.gz $(APP_NAME)-openbsd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/openbsd32.tar.gz $(APP_NAME)-openbsd32 $(APP_CONFIG)
package-darwin-platform:
@echo "Packaging Darwin"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/darwin64.tar.gz $(APP_NAME)-darwin64 $(APP_CONFIG)
#cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/darwin32.tar.gz $(APP_NAME)-darwin32 $(APP_CONFIG)
package-linux-platform:
@echo "Packaging Linux"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux64.tar.gz $(APP_NAME)-linux64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux32.tar.gz $(APP_NAME)-linux32 $(APP_CONFIG)
package-linux-arm-platform:
@echo "Packaging Linux (ARM)"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/arm64.tar.gz $(APP_NAME)-arm64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/armv5.tar.gz $(APP_NAME)-armv5 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/armv6.tar.gz $(APP_NAME)-armv6 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/armv7.tar.gz $(APP_NAME)-armv7 $(APP_CONFIG)
package-windows-platform:
@echo "Packaging Windows"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows64.tar.gz $(APP_NAME)-windows64.exe $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows32.tar.gz $(APP_NAME)-windows32.exe $(APP_CONFIG)
test:
go get -u github.com/kardianos/govendor
go get github.com/stretchr/testify/assert
govendor test +local
go test -timeout 60s ./...
#GORACE="halt_on_error=1" go test ./... -race -timeout 120s --ignore ./vendor
#go test -bench=. -benchmem
SHELL=/bin/bash
# APP info
APP_NAME := search-center
APP_VERSION := 1.0.0_SNAPSHOT
APP_CONFIG := $(APP_NAME).yml
APP_EOLDate := "2021-12-31 10:10:10"
APP_STATIC_FOLDER := .public
APP_STATIC_PACKAGE := public
APP_UI_FOLDER := ui
APP_PLUGIN_FOLDER := plugin
# Get release version from environment
ifneq "$(VERSION)" ""
APP_VERSION := $(VERSION)
endif
ifneq "$(EOL)" ""
APP_EOLDate := $(EOL)
endif
# Ensure GOPATH is set before running build process.
ifeq "$(GOPATH)" ""
GOPATH := ~/go
#$(error Please set the environment variable GOPATH before running `make`)
endif
PATH := $(PATH):$(GOPATH)/bin
# Go environment
CURDIR := $(shell pwd)
OLDGOPATH:= $(GOPATH)
CMD_DIR := $(CURDIR)/cmd
OUTPUT_DIR := $(CURDIR)/bin
# INFINI framework
INFINI_BASE_FOLDER := $(OLDGOPATH)/src/infini.sh/
FRAMEWORK_FOLDER := $(INFINI_BASE_FOLDER)/framework/
FRAMEWORK_REPO := ssh://git@git.infini.ltd:64221/infini/framework.git
FRAMEWORK_BRANCH := master
FRAMEWORK_VENDOR_FOLDER := $(CURDIR)/../vendor/
FRAMEWORK_VENDOR_REPO := ssh://git@git.infini.ltd:64221/infini/framework-vendor.git
FRAMEWORK_VENDOR_BRANCH := master
NEWGOPATH:= $(CURDIR):$(FRAMEWORK_VENDOR_FOLDER):$(GOPATH)
GO := GO15VENDOREXPERIMENT="1" GO111MODULE=off go
GOBUILD := GOPATH=$(NEWGOPATH) CGO_ENABLED=0 GRPC_GO_REQUIRE_HANDSHAKE=off $(GO) build -ldflags='-s -w' -gcflags "-m" --work
GOBUILDNCGO := GOPATH=$(NEWGOPATH) CGO_ENABLED=1 $(GO) build -ldflags -s
GOTEST := GOPATH=$(NEWGOPATH) CGO_ENABLED=1 $(GO) test -ldflags -s
ARCH := "`uname -s`"
LINUX := "Linux"
MAC := "Darwin"
GO_FILES=$(find . -iname '*.go' | grep -v /vendor/)
PKGS=$(go list ./... | grep -v /vendor/)
FRAMEWORK_OFFLINE_BUILD := ""
ifneq "$(OFFLINE_BUILD)" ""
FRAMEWORK_OFFLINE_BUILD := $(OFFLINE_BUILD)
endif
.PHONY: all build update test clean
default: build-race
env:
@echo OLDGOPATH$(OLDGOPATH)
@echo GOPATH$(GOPATH)
@echo NEWGOPATH$(NEWGOPATH)
@echo INFINI_BASE_FOLDER$(INFINI_BASE_FOLDER)
@echo FRAMEWORK_FOLDER$(FRAMEWORK_FOLDER)
@echo FRAMEWORK_VENDOR_FOLDER$(FRAMEWORK_VENDOR_FOLDER)
build: config
$(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)
@$(MAKE) restore-generated-file
build-cmd: config
@for f in $(shell ls ${CMD_DIR}); do (cd $(CMD_DIR)/$${f} && $(GOBUILD) -o $(OUTPUT_DIR)/$${f}); done
@$(MAKE) restore-generated-file
# used to build the binary for gdb debugging
build-race: clean config update-vfs
$(GOBUILDNCGO) -gcflags "-m -N -l" -race -o $(OUTPUT_DIR)/$(APP_NAME)
@$(MAKE) restore-generated-file
tar: build
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/$(APP_NAME).tar.gz $(APP_NAME) $(APP_CONFIG)
cross-build: clean config update-vfs
$(GO) test
GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows-amd64.exe
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-mac-amd64
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-amd64
@$(MAKE) restore-generated-file
build-win: config
CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows-amd64.exe
CC=i686-w64-mingw32-gcc CXX=i686-w64-mingw32-g++ GOOS=windows GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-windows-386.exe
@$(MAKE) restore-generated-file
build-linux: config
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-amd64
GOOS=linux GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-386
GOOS=linux GOARCH=mips $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-mips
GOOS=linux GOARCH=mipsle $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-mipsle
GOOS=linux GOARCH=mips64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-mips64
GOOS=linux GOARCH=mips64le $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-mips64le
@$(MAKE) restore-generated-file
build-arm: config
GOOS=linux GOARCH=arm64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-arm64
GOOS=linux GOARCH=arm GOARM=5 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-armv5
GOOS=linux GOARCH=arm GOARM=6 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-armv6
GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-linux-armv7
@$(MAKE) restore-generated-file
build-darwin: config
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-mac-amd64
# GOOS=darwin GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-mac-386
# GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-mac-arm64
@$(MAKE) restore-generated-file
build-bsd: config
GOOS=freebsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-freebsd-amd64
GOOS=freebsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-freebsd-386
GOOS=netbsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-netbsd-amd64
GOOS=netbsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-netbsd-386
GOOS=openbsd GOARCH=amd64 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-openbsd-amd64
GOOS=openbsd GOARCH=386 $(GOBUILD) -o $(OUTPUT_DIR)/$(APP_NAME)-openbsd-386
@$(MAKE) restore-generated-file
all: clean config update-vfs cross-build restore-generated-file
all-platform: clean config update-vfs cross-build-all-platform restore-generated-file
cross-build-all-platform: clean config build-bsd build-linux build-darwin build-win restore-generated-file
format:
go fmt $$(go list ./... | grep -v /vendor/)
clean_data:
rm -rif dist
rm -rif data
rm -rif log
clean: clean_data
rm -rif $(OUTPUT_DIR)
mkdir $(OUTPUT_DIR)
init:
@echo building $(APP_NAME) $(APP_VERSION)
@echo $(CURDIR)
@mkdir -p $(INFINI_BASE_FOLDER)
@echo "framework path: " $(FRAMEWORK_FOLDER)
@if [ ! -d $(FRAMEWORK_FOLDER) ]; then echo "framework does not exist";(cd $(INFINI_BASE_FOLDER)&&git clone -b $(FRAMEWORK_BRANCH) $(FRAMEWORK_REPO) framework ) fi
@if [ ! -d $(FRAMEWORK_VENDOR_FOLDER) ]; then echo "framework vendor does not exist";(cd $(INFINI_BASE_FOLDER)&&git clone -b $(FRAMEWORK_VENDOR_BRANCH) $(FRAMEWORK_VENDOR_REPO) vendor) fi
@if [ "" == $(FRAMEWORK_OFFLINE_BUILD) ]; then (cd $(FRAMEWORK_FOLDER) && git pull origin $(FRAMEWORK_BRANCH)); fi;
@if [ "" == $(FRAMEWORK_OFFLINE_BUILD) ]; then (cd $(FRAMEWORK_VENDOR_FOLDER) && git pull origin $(FRAMEWORK_VENDOR_BRANCH)); fi;
update-generated-file:
@echo "update generated info"
@echo -e "package config\n\nconst LastCommitLog = \""`git log -1 --pretty=format:"%h, %ad, %an, %s"` "\"\nconst BuildDate = \"`date "+%Y-%m-%d %H:%M:%S"`\"" > config/generated.go
@echo -e "\nconst EOLDate = \"$(APP_EOLDate)\"" >> config/generated.go
@echo -e "\nconst Version = \"$(APP_VERSION)\"" >> config/generated.go
restore-generated-file:
@echo "restore generated info"
@echo -e "package config\n\nconst LastCommitLog = \"N/A\"\nconst BuildDate = \"N/A\"" > config/generated.go
@echo -e "\nconst EOLDate = \"N/A\"" >> config/generated.go
@echo -e "\nconst Version = \"0.0.1-SNAPSHOT\"" >> config/generated.go
update-vfs:
cd $(FRAMEWORK_FOLDER) && cd cmd/vfs && $(GO) build && cp vfs ~/
@if [ -d $(APP_STATIC_FOLDER) ]; then echo "generate static files";(cd $(APP_STATIC_FOLDER) && ~/vfs -ignore="static.go|.DS_Store" -o static.go -pkg $(APP_STATIC_PACKAGE) . ) fi
config: init update-vfs update-generated-file
@echo "update configs"
@# $(GO) env
@mkdir -p $(OUTPUT_DIR)
@cp $(APP_CONFIG) $(OUTPUT_DIR)/$(APP_CONFIG)
dist: cross-build package
dist-major-platform: all package
dist-all-platform: all-platform package-all-platform
package:
@echo "Packaging"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/mac-amd64.tar.gz mac-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-amd64.tar.gz linux-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows-amd64.tar.gz windows-amd64 $(APP_CONFIG)
package-all-platform: package-darwin-platform package-linux-platform package-windows-platform
@echo "Packaging all"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/freebsd-amd64.tar.gz $(APP_NAME)-freebsd-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/freebsd-386.tar.gz $(APP_NAME)-freebsd-386 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/netbsd-amd64.tar.gz $(APP_NAME)-netbsd-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/netbsd-386.tar.gz $(APP_NAME)-netbsd-386 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/openbsd-amd64.tar.gz $(APP_NAME)-openbsd-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/openbsd-386.tar.gz $(APP_NAME)-openbsd-386 $(APP_CONFIG)
package-darwin-platform:
@echo "Packaging Darwin"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/mac-amd64.tar.gz $(APP_NAME)-mac-amd64 $(APP_CONFIG)
# cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/mac-386.tar.gz $(APP_NAME)-mac-386 $(APP_CONFIG)
# cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/mac-arm64.tar.gz $(APP_NAME)-mac-arm64 $(APP_CONFIG)
package-linux-platform:
@echo "Packaging Linux"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-amd64.tar.gz $(APP_NAME)-linux-amd64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-mips.tar.gz $(APP_NAME)-linux-mips $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-mipsle.tar.gz $(APP_NAME)-linux-mipsle $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-mips64.tar.gz $(APP_NAME)-linux-mips64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-mips64le.tar.gz $(APP_NAME)-linux-mips64le $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-386.tar.gz $(APP_NAME)-linux-386 $(APP_CONFIG)
package-linux-arm-platform:
@echo "Packaging Linux (ARM)"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-arm64.tar.gz $(APP_NAME)-linux-arm64 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-armv5.tar.gz $(APP_NAME)-linux-armv5 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-armv6.tar.gz $(APP_NAME)-linux-armv6 $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/linux-armv7.tar.gz $(APP_NAME)-linux-armv7 $(APP_CONFIG)
package-windows-platform:
@echo "Packaging Windows"
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows-amd64.tar.gz $(APP_NAME)-windows-amd64.exe $(APP_CONFIG)
cd $(OUTPUT_DIR) && tar cfz $(OUTPUT_DIR)/windows-386.tar.gz $(APP_NAME)-windows-386.exe $(APP_CONFIG)
test:
go get -u github.com/kardianos/govendor
go get github.com/stretchr/testify/assert
govendor test +local
go test -timeout 60s ./...
#GORACE="halt_on_error=1" go test ./... -race -timeout 120s --ignore ./vendor
#go test -bench=. -benchmem

View File

@ -58,6 +58,7 @@ npm install -g cnpm --registry=https://registry.npm.taobao.org
### 下载项目依赖包
```
cnpm install
cnpm install -g cross-env
```
### 启动开发模式

View File

@ -32,7 +32,7 @@ func main() {
terminalFooter += (" / // |/ // __// // |/ // / / //_ _// \\ \n")
terminalFooter += (" / // || // _/ / // || // / / /_ / / / o | \n")
terminalFooter += ("/_//_/|_//_/ /_//_/|_//_/() /___//_/ /__,' \n\n")
terminalFooter += (2020 INFINI.LTD, All Rights Reserved.\n")
terminalFooter += (INFINI.LTD, All Rights Reserved.\n")
app := framework.NewApp("search-center", "the easiest way to operate your own search center.",
config.Version, config.LastCommitLog, config.BuildDate,config.EOLDate, terminalHeader, terminalFooter)

View File

@ -17,8 +17,7 @@ web:
binding: 0.0.0.0:9000
skip_occupied_port: true
modules:
- name: elastic
elastic:
elasticsearch: default
enabled: true
store:

View File

@ -67,13 +67,13 @@ export default {
// pathRewrite: { '^/server': '' },
// },
// },
// proxy: {
// '/_search-center/': {
// target: 'http://localhost:9000',
// changeOrigin: true,
// // pathRewrite: { '^/server': '' },
// },
// },
proxy: {
'/elasticsearch/': {
target: 'http://localhost:9000',
changeOrigin: true,
// pathRewrite: { '^/server': '' },
},
},
ignoreMomentLocale: true,
lessLoaderOptions: {
javascriptEnabled: true,
@ -85,7 +85,8 @@ export default {
if (
context.resourcePath.includes('node_modules') ||
context.resourcePath.includes('ant.design.pro.less') ||
context.resourcePath.includes('global.less')
context.resourcePath.includes('global.less') ||
context.resourcePath.includes('.scss')
) {
return localName;
}
@ -124,4 +125,5 @@ export default {
// // htmlSuffix: true,
// dynamicRoot: true,
// },
sass: { },
};

View File

@ -25,51 +25,84 @@ export default [
icon: 'cluster',
routes: [
// { path: '/', redirect: '/platform/gateway' },
{
path: '/cluster/overview/',
name: 'overview',
component: './Cluster/Overview',
}, {
// {
// path: '/cluster/overview/',
// name: 'overview',
// component: './Cluster/Overview',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
{
path: '/cluster/overview/:cluster_id',
name: 'overview',
component: './Cluster/Overview',
hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
},
{
path: '/cluster/monitoring/:cluster_id',
name: 'cluster',
component: './Cluster/ClusterMonitor',
hideInMenu: true,
routes:[
{ path: '/', redirect: '/' },
],
}, {
path: '/cluster/metrics/',
name: 'monitoring',
component: './Cluster/Metrics',
routes:[
{ path: '/', redirect: '/' },
],
}, {
path: '/cluster/metrics/:cluster_id',
name: 'monitoring',
component: './Cluster/Metrics',
hideInMenu: true,
}, {
path: '/cluster/logs/',
name: 'logging',
component: './Cluster/SearchMonitor',
},{
path: '/cluster/settings/',
name: 'settings',
component: './Cluster/Settings/Base',
routes: [
{
path: '/cluster/settings',
redirect: '/cluster/settings/repository',
},
{
path: '/cluster/settings/repository',
component: './Cluster/Settings/Repository',
}
]
},
// {
// path: '/cluster/logs/',
// name: 'logging',
// component: './Cluster/SearchMonitor',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },{
// path: '/cluster/settings/',
// name: 'settings',
// component: './Cluster/Settings/Base',
// routes: [
// {
// path: '/cluster/settings',
// redirect: '/cluster/settings/repository',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/cluster/settings/repository',
// component: './Cluster/Settings/Repository',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// },
]
},
//devtools
{
routes:[
{ path: '/', redirect: '/' },
],
path: '/dev_tool',
name: 'devtool',
icon: 'code',
component: './DevTool/Console',
},
//data
{
@ -96,215 +129,307 @@ export default [
// },
// ]
// },
// {
// path: '/data/overview',
// name: 'overview',
// component: './DataManagement/IndexSummary',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }, {
// path: '/data/index',
// name: 'index',
// component: './DataManagement/Index',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },{
// path: '/data/document',
// name: 'document',
// component: './DataManagement/Document',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }, {
// path: '/data/template',
// name: 'template',
// component: './DataManagement/IndexTemplate',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/data/lifecycle',
// name: 'lifecycle',
// component: './DataManagement/IndexLifeCycle',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
{
path: '/data/overview',
name: 'overview',
component: './DataManagement/IndexSummary',
}, {
path: '/data/index',
name: 'index',
component: './DataManagement/Index',
},{
path: '/data/document',
name: 'document',
component: './DataManagement/Document',
}, {
path: '/data/template',
name: 'template',
component: './DataManagement/IndexTemplate',
routes:[
{ path: '/', redirect: '/' },
],
path: '/data/discover',
name: 'discover',
component: './DataManagement/Discover',
},
{
path: '/data/lifecycle',
name: 'lifecycle',
component: './DataManagement/IndexLifeCycle',
routes:[
{ path: '/', redirect: '/' },
],
path: '/data/views/',
name: 'indexPatterns',
component: './DataManagement/IndexPatterns',
},
]
},
//search
{
path: '/search',
name: 'search',
icon: 'search',
routes: [
{
path: '/search/overview',
name: 'overview',
component: './SearchManage/template/Template',
},
{
path: '/search/template',
name: 'template',
component: './SearchManage/template/Template',
routes: [
{
path: '/search/template',
redirect: '/search/template/template',
},
{
path: '/search/template/template',
component: './SearchManage/template/SearchTemplate',
},
{
path: '/search/template/:cluster_id',
component: './SearchManage/template/SearchTemplate',
},
{
path: '/search/template/history',
component: './SearchManage/template/History',
},
]
}, {
path: '/search/alias',
name: 'alias',
component: './SearchManage/alias/Alias',
routes: [
{
path: '/search/alias',
redirect: '/search/alias/index',
},
{
path: '/search/alias/index',
component: './SearchManage/alias/AliasManage',
},
{
path: '/search/alias/rule',
component: './SearchManage/alias/Rule',
}
]
}, {
path: '/search/dict',
name: 'dict',
component: './SearchManage/dict/Dict',
routes: [
{
path: '/search/dict',
redirect: '/search/dict/professional',
},
{
path: '/search/dict/professional',
component: './SearchManage/dict/Pro',
},
{
path: '/search/dict/common',
component: './SearchManage/dict/Common',
}
]
}, {
path: '/search/analyzer',
name: 'analyzer',
component: './SearchManage/analyzer/Analyzer',
routes: [
{
path: '/search/analyzer',
redirect: '/search/analyzer/manage',
},
{
path: '/search/analyzer/manage',
component: './SearchManage/analyzer/Manage',
},
{
path: '/search/analyzer/test',
component: './SearchManage/analyzer/AnalyzerTest',
}
]
}//, {
// path: '/search/nlp',
// name: 'nlp',
// component: './SearchManage/nlp/NLP',
// routes: [
// {
// path: '/search/nlp',
// redirect: '/search/nlp/query',
// },
// {
// path: '/search/nlp/query',
// component: './SearchManage/nlp/Query',
// },
// {
// path: '/search/nlp/intention',
// component: './SearchManage/nlp/Intention',
// },
// {
// path: '/search/nlp/knowledge',
// component: './SearchManage/nlp/Knowledge',
// },
// {
// path: '/search/nlp/text',
// component: './SearchManage/nlp/Text',
// }
//]
//},
]
},
//sync
{
path: '/sync',
name: 'synchronize',
icon: 'sync',
routes: [
{
path: '/sync/overview',
name: 'overview',
component: './Synchronize/Pipes',
},
{
path: '/sync/pipeline',
name: 'pipeline',
component: './Synchronize/Pipes',
routes: [
{
path: '/sync/pipeline',
redirect: '/sync/pipeline/logstash',
},
{
path: '/sync/pipeline/ingestpipeline',
component: './Synchronize/IngestPipeline',
}, {
path: '/sync/pipeline/logstash',
component: './Synchronize/LogstashConfig',
}]
},{
path: '/sync/rebuild',
name: 'rebuild',
component: './Synchronize/RebuildList',
},
{
path: '/sync/rebuild/new',
component: './Synchronize/Rebuild',
hideInMenu: true,
},{
path: '/sync/inout',
name: 'inout',
component: './Synchronize/Import',
}
]
},
//backup
{
path: '/backup',
name: 'backup',
icon: 'cloud',
routes: [
{
path: '/backup/overview',
name: 'overview',
component: './SearchManage/template/Template',
},
{
path: '/backup/bakandrestore',
name: 'index',
component: './Backup/BakAndRestore',
},{
path: '/backup/lifecycle',
name: 'lifecycle',
component: './Backup/BakCycle',
}
]
},
//
//
// //search
// {
// path: '/search',
// name: 'search',
// icon: 'search',
// routes: [
// {
// path: '/search/overview',
// name: 'overview',
// component: './SearchManage/template/Template',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/template',
// name: 'template',
// component: './SearchManage/template/Template',
// routes: [
// {
// path: '/search/template',
// redirect: '/search/template/template',
// },
// {
// path: '/search/template/template',
// component: './SearchManage/template/SearchTemplate',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/template/:cluster_id',
// component: './SearchManage/template/SearchTemplate',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/template/history',
// component: './SearchManage/template/History',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// ]
// }, {
// path: '/search/alias',
// name: 'alias',
// component: './SearchManage/alias/Alias',
// routes: [
// {
// path: '/search/alias',
// redirect: '/search/alias/index',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/alias/index',
// component: './SearchManage/alias/AliasManage',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/alias/rule',
// component: './SearchManage/alias/Rule',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// }, {
// path: '/search/dict',
// name: 'dict',
// component: './SearchManage/dict/Dict',
// routes: [
// {
// path: '/search/dict',
// redirect: '/search/dict/professional',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/dict/professional',
// component: './SearchManage/dict/Pro',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/dict/common',
// component: './SearchManage/dict/Common',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// }, {
// path: '/search/analyzer',
// name: 'analyzer',
// component: './SearchManage/analyzer/Analyzer',
// routes: [
// {
// path: '/search/analyzer',
// redirect: '/search/analyzer/manage',
// },
// {
// path: '/search/analyzer/manage',
// component: './SearchManage/analyzer/Manage',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/search/analyzer/test',
// component: './SearchManage/analyzer/AnalyzerTest',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// }//, {
// // path: '/search/nlp',
// // name: 'nlp',
// // component: './SearchManage/nlp/NLP',
// // routes: [
// // {
// // path: '/search/nlp',
// // redirect: '/search/nlp/query',
// // },
// // {
// // path: '/search/nlp/query',
// // component: './SearchManage/nlp/Query',
// // },
// // {
// // path: '/search/nlp/intention',
// // component: './SearchManage/nlp/Intention',
// // },
// // {
// // path: '/search/nlp/knowledge',
// // component: './SearchManage/nlp/Knowledge',
// // },
// // {
// // path: '/search/nlp/text',
// // component: './SearchManage/nlp/Text',
// // }
// //]
// //},
// ]
// },
//
// //sync
// {
// path: '/sync',
// name: 'synchronize',
// icon: 'sync',
// routes: [
// {
// path: '/sync/overview',
// name: 'overview',
// component: './Synchronize/Pipes',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/sync/pipeline',
// name: 'pipeline',
// component: './Synchronize/Pipes',
// routes: [
// {
// path: '/sync/pipeline',
// redirect: '/sync/pipeline/logstash',
// },
// {
// path: '/sync/pipeline/ingestpipeline',
// component: './Synchronize/IngestPipeline',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }, {
// path: '/sync/pipeline/logstash',
// component: './Synchronize/LogstashConfig',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }]
// },{
// path: '/sync/rebuild',
// name: 'rebuild',
// component: './Synchronize/RebuildList',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/sync/rebuild/new',
// component: './Synchronize/Rebuild',
// hideInMenu: true,
// },{
// path: '/sync/inout',
// name: 'inout',
// component: './Synchronize/Import',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// },
//
// //backup
// {
// path: '/backup',
// name: 'backup',
// icon: 'cloud',
// routes: [
// {
// path: '/backup/overview',
// name: 'overview',
// component: './SearchManage/template/Template',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },
// {
// path: '/backup/bakandrestore',
// name: 'index',
// component: './Backup/BakAndRestore',
// routes:[
// { path: '/', redirect: '/' },
// ],
// },{
// path: '/backup/lifecycle',
// name: 'lifecycle',
// component: './Backup/BakCycle',
// routes:[
// { path: '/', redirect: '/' },
// ],
// }
// ]
// },
//settings
{
@ -323,77 +448,78 @@ export default [
component: './System/Cluster/Form',
hideInMenu: true
},
{
path: '/system/settings',
name: 'settings',
component: './System/Settings/Base',
hideChildrenInMenu: true,
routes: [
{
path: '/system/settings',
redirect: '/system/settings/global',
},
{
path: '/system/settings/global',
component: './System/Settings/Global',
}, {
path: '/system/settings/gateway',
component: './System/Settings/Gateway',
},
]
},
{
path: '/system/security',
name: 'security',
component: './System/Security/Base',
hideChildrenInMenu: true,
routes: [
{
path: '/system/security',
redirect: '/system/security/general',
},
{
path: '/system/security/general',
component: './System/Security/General',
}, {
path: '/system/security/sso',
component: './System/Security/SSO',
}, {
path: '/system/security/roles',
component: './System/Security/Roles',
}, {
path: '/system/security/users',
component: './System/Security/Users',
}, {
path: '/system/security/certs',
component: './System/Security/Certs',
},
]
}, {
path: '/system/logs',
name: 'logs',
component: './System/Logs/Base',
hideChildrenInMenu: true,
routes: [
{
path: '/system/logs',
redirect: '/system/logs/overview',
},
{
path: '/system/logs/overview',
component: './System/Logs/Overview',
}, {
path: '/system/logs/audit',
component: './System/Logs/Audit',
}, {
path: '/system/logs/query',
component: './System/Logs/Audit',
}, {
path: '/system/logs/slow',
component: './System/Logs/Audit',
},
]
},
// {
// path: '/system/settings',
// name: 'settings',
// component: './System/Settings/Base',
// hideChildrenInMenu: true,
// routes: [
// {
// path: '/system/settings',
// redirect: '/system/settings/global',
// },
// {
// path: '/system/settings/global',
// component: './System/Settings/Global',
// }, {
// path: '/system/settings/gateway',
// component: './System/Settings/Gateway',
// },
// ]
// },
// {
// path: '/system/security',
// name: 'security',
// component: './System/Security/Base',
// hideChildrenInMenu: true,
// routes: [
// {
// path: '/system/security',
// redirect: '/system/security/general',
// },
// {
// path: '/system/security/general',
// component: './System/Security/General',
// }, {
// path: '/system/security/sso',
// component: './System/Security/SSO',
// }, {
// path: '/system/security/roles',
// component: './System/Security/Roles',
// }, {
// path: '/system/security/users',
// component: './System/Security/Users',
// }, {
// path: '/system/security/certs',
// component: './System/Security/Certs',
// },
// ]
// },
// {
// path: '/system/logs',
// name: 'logs',
// component: './System/Logs/Base',
// hideChildrenInMenu: true,
// routes: [
// {
// path: '/system/logs',
// redirect: '/system/logs/overview',
// },
// {
// path: '/system/logs/overview',
// component: './System/Logs/Overview',
// }, {
// path: '/system/logs/audit',
// component: './System/Logs/Audit',
// }, {
// path: '/system/logs/query',
// component: './System/Logs/Audit',
// }, {
// path: '/system/logs/slow',
// component: './System/Logs/Audit',
// },
// ]
// },
]
},

View File

@ -1,9 +1,3 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import moment from 'moment';
const d = moment.duration;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,66 @@
const savedObjectsResult = {"page":1,"per_page":10000,"total":4,"saved_objects":[{"type":"index-pattern","id":"c7fbafd0-34a9-11eb-925f-9db57376c4ce","attributes":{"title":".monitoring-es-7-mb-*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-02T14:34:38.010Z","version":"WzgyNCw3XQ==","namespaces":["default"],"score":0},{"type":"index-pattern","id":"861ea7f0-3a9b-11eb-9b55-45d33507027a","attributes":{"title":"mock_log*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2020-12-10T04:09:09.044Z","version":"WzE3NTgsMTBd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1a28c950-0f6b-11eb-9512-2d0c0eda237d","attributes":{"title":"gateway_requests*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-05-22T11:04:23.811Z","version":"WzkxMTgsNDhd","namespaces":["default"],"score":0},{"type":"index-pattern","id":"1ccce5c0-bb9a-11eb-957b-939add21a246","attributes":{"title":"test-custom*"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"},"updated_at":"2021-06-03T14:51:14.139Z","version":"WzEwMTEzLDQ4XQ==","namespaces":["default"],"score":0}]};
const resolveIndexResult1 = {"indices":[{"name":".apm-agent-configuration","attributes":["open"]},{"name":".apm-custom-link","attributes":["open"]},{"name":".async-search","attributes":["open"]},{"name":".infini-search-center_cluster","attributes":["open"]},{"name":".infini-search-center_dict","attributes":["open"]},{"name":".infini-search-center_monitoring","attributes":["open"]},{"name":".infini-search-center_reindex","attributes":["open"]},{"name":".infini-search-center_searchtemplate","attributes":["open"]},{"name":".infini-search-center_searchtemplatehistory","attributes":["open"]},{"name":".kibana-event-log-7.10.0-000004","aliases":[".kibana-event-log-7.10.0"],"attributes":["open"]},{"name":".kibana-event-log-7.10.0-000005","aliases":[".kibana-event-log-7.10.0"],"attributes":["open"]},{"name":".kibana-event-log-7.10.0-000006","aliases":[".kibana-event-log-7.10.0"],"attributes":["open"]},{"name":".kibana-event-log-7.10.0-000007","aliases":[".kibana-event-log-7.10.0"],"attributes":["open"]},{"name":".kibana_1","aliases":[".kibana"],"attributes":["open"]},{"name":".kibana_2","attributes":["open"]},{"name":".kibana_task_manager_1","aliases":[".kibana_task_manager"],"attributes":["open"]},{"name":".tasks","attributes":["open"]},{"name":"cluster","attributes":["open"]},{"name":"dict","attributes":["open"]},{"name":"gateway_requests","attributes":["open"]},{"name":"infini-dict","attributes":["open"]},{"name":"infini-reindex","attributes":["open"]},{"name":"metricbeat-7.10.0-2021.02.03-000001","aliases":["metricbeat-7.10.0"],"attributes":["open"]},{"name":"metricbeat-7.10.0-2021.03.06-000002","aliases":["metricbeat-7.10.0"],"attributes":["open"]},{"name":"metricbeat-7.10.0-2021.04.07-000003","aliases":["metricbeat-7.10.0"],"attributes":["open"]},{"name":"metricbeat-7.10.0-2021.05.07-000004","aliases":["metricbeat-7.10.0"],"attributes":["open"]},{"name":"metricbeat-7.10.0-2021.06.06-000005","aliases":["metricbeat-7.10.0"],"attributes":["open"]},{"name":"mock_log","attributes":["open"]},{"name":"nginx_mock_log","attributes":["open"]},{"name":"reindex","attributes":["open"]},{"name":"test-custom","aliases":["custom"],"attributes":["open"]},{"name":"test-custom1","aliases":["custom"],"attributes":["open"]},{"name":"test-custom8","aliases":["custom"],"attributes":["open"]},{"name":"test-custom9","aliases":["custom"],"attributes":["open"]}],"aliases":[{"name":".kibana","indices":[".kibana_1"]},{"name":".kibana-event-log-7.10.0","indices":[".kibana-event-log-7.10.0-000004",".kibana-event-log-7.10.0-000005",".kibana-event-log-7.10.0-000006",".kibana-event-log-7.10.0-000007"]},{"name":".kibana_task_manager","indices":[".kibana_task_manager_1"]},{"name":"custom","indices":["test-custom","test-custom1","test-custom8","test-custom9"]},{"name":"metricbeat-7.10.0","indices":["metricbeat-7.10.0-2021.02.03-000001","metricbeat-7.10.0-2021.03.06-000002","metricbeat-7.10.0-2021.04.07-000003","metricbeat-7.10.0-2021.05.07-000004","metricbeat-7.10.0-2021.06.06-000005"]}],"data_streams":[]};
const resolveIndexResult2 = {"indices":[],"aliases":[],"data_streams":[]};
export default {
'GET /elasticsearch/:clusterID/saved_objects/_find': (req, res) =>{
return res.json(savedObjectsResult);
},
'GET /elasticsearch/:clusterID/internal/index-pattern-management/resolve_index/:pattern': (req, res)=>{
const {pattern} = req.params;
if(pattern == '*')
return res.json(resolveIndexResult1);
else if(pattern == '*:*'){
return res.json(resolveIndexResult2);
}else{
const result = {...resolveIndexResult1};
result.aliases = result.aliases.filter(alias=>alias.name.startsWith(pattern.replace('*', '')))
result.indices = result.indices.filter(index=>index.name.startsWith(pattern.replace('*', '')))
return res.json(result);
}
},
'POST /elasticsearch/:clusterID/saved_objects/_bulk_get': (req, res) => {
if(req.body && req.body.length > 0 ){
let mockObj =
{
"attributes": {
"fields": "[{\"count\":0,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"address\"}}},{\"count\":0,\"conflictDescriptions\":{\"text\":[\"test-custom1\"],\"long\":[\"test-custom\",\"test-custom8\",\"test-custom9\"]},\"name\":\"age\",\"type\":\"conflict\",\"esTypes\":[\"text\",\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"age.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"age\"}}},{\"count\":0,\"name\":\"created_at\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":0,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"email\"}}},{\"count\":0,\"name\":\"hobbies\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"hobbies.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"hobbies\"}}},{\"count\":0,\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"id\"}}},{\"count\":0,\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"name\"}}}]",
"timeFieldName": "created_at",
"title": "test*",
"fieldFormatMap": `{"age":{"id":"bytes","params":{"parsedUrl":{"origin":"http://localhost:9000","pathname":"/","basePath":""}}}}`,
},
"id": "1ccce5c0-bb9a-11eb-957b-939add21a246",
"migrationVersion": {
"index-pattern": "7.6.0"
},
"namespaces": [
"default"
],
"score": 0,
"type": "index-pattern",
"updated_at": "2021-06-27T10:13:23.639105+08:00",
// "version":"WzEwMTEzLDQ4XQ=="
}//({"id":"1ccce5c0-bb9a-11eb-957b-939add21a246","type":"index-pattern","namespaces":["default"],"updated_at":"2021-06-03T14:51:14.139Z","version":"WzEwMTEzLDQ4XQ==","attributes":{"title":"test-custom*","timeFieldName":"created_at","fields":"[{\"count\":4,\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_score\",\"type\":\"number\",\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":4,\"name\":\"address\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"address.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"address\"}}},{\"count\":0,\"conflictDescriptions\":{\"text\":[\"test-custom1\"],\"long\":[\"test-custom\",\"test-custom8\",\"test-custom9\"]},\"name\":\"age\",\"type\":\"conflict\",\"esTypes\":[\"text\",\"long\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"count\":0,\"name\":\"age.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"age\"}}},{\"count\":0,\"name\":\"created_at\",\"type\":\"date\",\"esTypes\":[\"date\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"count\":3,\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"email.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"email\"}}},{\"count\":0,\"name\":\"hobbies\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"hobbies.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"hobbies\"}}},{\"count\":0,\"name\":\"id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"id\"}}},{\"count\":0,\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"count\":0,\"name\":\"name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"name\"}}}]"},"references":[],"migrationVersion":{"index-pattern":"7.6.0"}});
let savedObjects = [];
req.body.forEach((reqObj)=>{
savedObjects.push({
...mockObj,
id: reqObj.id,
})
})
return res.json({
saved_objects: savedObjects,
})
}
return res.json({"saved_objects":[{"id":"telemetry","type":"telemetry","namespaces":[],"updated_at":"2020-11-23T11:30:51.234Z","version":"WzgsMV0=","attributes":{"userHasSeenNotice":true},"references":[]}]});
},
'GET /elasticsearch/:clusterID/index_patterns/_fields_for_wildcard': (req, res)=>{
return res.json({"fields":[{"name":"_id","type":"string","esTypes":["_id"],"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"address","type":"string","esTypes":["text"],"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"address.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"address"}}},{"name":"age","type":"conflict","esTypes":["text","long"],"searchable":true,"aggregatable":true,"readFromDocValues":false,"conflictDescriptions":{"text":["test-custom1"],"long":["test-custom","test-custom8","test-custom9"]}},{"name":"age.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"age"}}},{"name":"created_at","type":"date","esTypes":["date"],"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"email","type":"string","esTypes":["text"],"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"email.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"email"}}},{"name":"hobbies","type":"string","esTypes":["text"],"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"hobbies.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"hobbies"}}},{"name":"id","type":"string","esTypes":["text"],"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"id.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"id"}}},{"name":"name","type":"string","esTypes":["text"],"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"name.keyword","type":"string","esTypes":["keyword"],"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"name"}}}]})
},
'GET elasticsearch/:clusterID/setting/defaultIndex': (req, res)=>{
return res.json('');
}
}

View File

@ -12,6 +12,15 @@ export default {
// curl -XPOST http://localhost:8000/elasticsearch/uuid/_proxy\?path=%2F_search&method=GET?pretty -d '{ "size": 1 }'
'POST /elasticsearch/:id/_proxy': function(req, res){
const {path} = req.query;
switch(path){
case '_mapping':
return res.send({"test-custom9":{"mappings":{"properties":{"address":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"age":{"type":"long"},"created_at":{"type":"date"},"email":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"hobbies":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"id":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"name":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}},"test-custom1":{"mappings":{"properties":{"address":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"age":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"created_at":{"type":"date"},"email":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"hobbies":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"id":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"name":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}},"test-custom":{"mappings":{"properties":{"address":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"age":{"type":"long"},"created_at":{"type":"date"},"email":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"hobbies":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"id":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"name":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}},"test-custom8":{"mappings":{"properties":{"address":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"age":{"type":"long"},"created_at":{"type":"date"},"email":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"hobbies":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"id":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"name":{"type":"text","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}});
case '_aliases':
return res.send({"test-custom8":{"aliases":{"custom":{"filter":{"match":{"name":"test"}},"index_routing":"1","search_routing":"1"}}},"test-custom9":{"aliases":{"custom":{"filter":{"match":{"name":"test"}},"index_routing":"1","search_routing":"1"}}},"test-custom1":{"aliases":{"custom":{"filter":{"match":{"name":"test"}},"index_routing":"1","search_routing":"1"}}},"test-custom":{"aliases":{"custom":{"filter":{"match":{"name":"test"}},"index_routing":"1","search_routing":"1","is_write_index":true}}}});
case 'template':
return res.send({"search-center":{"order":0,"index_patterns":["infini-*"],"settings":{"index":{"max_result_window":"10000000","number_of_shards":"1"}},"mappings":{"dynamic_templates":[{"strings":{"mapping":{"ignore_above":256,"type":"keyword"},"match_mapping_type":"string"}}]},"aliases":{}}});
}
res.send({
"took" : 1055,
"timed_out" : false,
@ -138,23 +147,23 @@ export default {
},
//加载常用命令
'GET /elasticsearch/:id/command/:id': function(req, res){
res.send({
"_id": "c0oc4kkgq9s8qss2uk50",
"_source": {
"created" : "2021-02-02T13:23:16.799Z",
"request" : {
"method" : "POST",
"path" : "/myindex/_search",
"body" : "{ \"query\": { \"match\": { \"name\": \"medcl\" } } }"
},
"status":200,
"title":"一个常用查询的例子",
"tag":["example","search"]
},
"found": true
});
},
// 'GET /elasticsearch/:id/command/:id': function(req, res){
// res.send({
// "_id": "c0oc4kkgq9s8qss2uk50",
// "_source": {
// "created" : "2021-02-02T13:23:16.799Z",
// "request" : {
// "method" : "POST",
// "path" : "/myindex/_search",
// "body" : "{ \"query\": { \"match\": { \"name\": \"medcl\" } } }"
// },
// "status":200,
// "title":"一个常用查询的例子",
// "tag":["example","search"]
// },
// "found": true
// });
// },
//删除常用命令
'DELETE /elasticsearch/:id/command/:id': function(req, res){
@ -194,7 +203,23 @@ export default {
"path" : "/myindex/_search",
"body" : "{ \"query\": { \"match\": { \"name\": \"medcl\" } } }"
},
"title":"一个常用查询的例子",
"title":"command1",
"tag":["example","search"]
}
},
{
"_index" : "gateway-command-7.9.2-000001",
"_type" : "_doc",
"_id" : "VLvqYncBwyX1iJ4H4cBA",
"_score" : 1.0,
"_source" : {
"created" : "2021-02-02T13:23:16.799Z",
"request" : {
"method" : "GET",
"path" : "/myindex/_search",
"body" : "{ \"query\": { \"match\": { \"name\": \"medcl\" } } }"
},
"title":"command2",
"tag":["example","search"]
}
}

View File

@ -9,20 +9,28 @@
"@antv/g2-brush": "^0.0.2",
"@babel/runtime": "^7.1.2",
"@elastic/charts": "^25.0.1",
"@elastic/datemath": "^5.0.3",
"@elastic/eui": "^34.4.0",
"@elastic/numeral": "^2.5.1",
"@hapi/boom": "^9.1.3",
"@monaco-editor/react": "^3.7.4",
"@svgdotjs/svg.js": "^3.0.16",
"antd": "^3.26.18",
"antd-table-infinity": "^1.1.6",
"bizcharts": "^3.2.2",
"bizcharts-plugin-slider": "^2.0.3",
"brace": "^0.11.1",
"classnames": "^2.2.6",
"cluster": "^0.7.7",
"console": "^0.7.2",
"deep-freeze-strict": "^1.1.1",
"dns": "^0.2.2",
"dva": "^2.4.0",
"enquire-js": "^0.2.1",
"fp-ts": "^2.10.5",
"hash.js": "^1.1.5",
"honeycomb-grid": "^3.1.7",
"jquery": "^3.6.0",
"lodash": "^4.17.10",
"lodash-decorators": "^6.0.0",
"luxon": "^1.26.0",
@ -30,6 +38,7 @@
"module": "^1.2.5",
"moment": "^2.29.1",
"moment-timezone": "^0.5.32",
"nano-css": "^5.3.1",
"node-ssh": "^8.0.0",
"numeral": "^2.0.6",
"nzh": "^1.0.3",
@ -37,8 +46,10 @@
"path-to-regexp": "^2.4.0",
"prop-types": "^15.5.10",
"qs": "^6.5.2",
"raw-loader": "^4.0.2",
"rc-animate": "^2.4.4",
"react": "^16.5.1",
"react-calendar-heatmap": "^1.8.1",
"react-container-query": "^0.11.0",
"react-copy-to-clipboard": "^5.0.1",
"react-document-title": "^2.0.3",
@ -46,12 +57,19 @@
"react-fittext": "^1.0.0",
"react-grid-layout": "^1.2.0",
"react-highlight-words": "^0.16.0",
"react-infinite-scroll-component": "^6.1.0",
"react-infinite-scroller": "^1.2.4",
"react-json-prettify": "^0.2.0",
"react-json-view": "^1.19.1",
"react-router-dom": "^4.3.1",
"react-sparklines": "^1.7.0",
"repl": "^0.1.3"
"react-use": "^17.2.4",
"readline": "^1.3.0",
"repl": "^0.1.3",
"reqwest": "^2.0.5",
"rison-node": "^2.1.1",
"rxjs": "^7.2.0",
"sass-loader": "^8.0.2",
"use-query-params": "^1.2.3"
},
"devDependencies": {
"antd-pro-merge-less": "^0.1.0",
@ -62,6 +80,7 @@
"babel-plugin-dva-hmr": "^0.4.1",
"babel-plugin-import": "^1.6.3",
"babel-plugin-transform-decorators-legacy": "^1.3.4",
"cross-env": "^7.0.3",
"egg": "^2.4.1",
"egg-bin": "^4.3.7",
"egg-ci": "^1.8.0",
@ -84,7 +103,11 @@
"mockjs": "^1.0.1-beta3",
"moment-duration-format": "^2.3.2",
"node-fetch": "^2.6.1",
"raw-loader": "^4.0.2",
"redbox-react": "^1.5.0",
"sass": "^1.35.1",
"sass-loader": "^10.1.1",
"ts-loader": "^8.0.2",
"umi": "^2.1.2",
"umi-plugin-ga": "^1.1.3",
"umi-plugin-react": "^1.1.1"
@ -94,12 +117,13 @@
},
"scripts": {
"dev": "umi dev",
"build": "umi build",
"build": "NODE_OPTIONS=--max_old_space_size=4096 umi build",
"autod": "autod",
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up -d",
"docker:stop-dev": "docker-compose -f ./docker/docker-compose.dev.yml down",
"docker:build": "docker-compose -f ./docker/docker-compose.build.yml up",
"docker:start-mysql": "docker-compose -f ./docker/docker-compose-mysql.dev.yml up -d"
"docker:dev": "docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans -d",
"docker:stop-dev": "docker-compose -f ./docker/docker-compose.dev.yml down",
"docker:build": "docker-compose -f ./docker/docker-compose.build.yml up --remove-orphans",
"docker:start-mysql": "docker-compose -f ./docker/docker-compose-mysql.dev.yml up --remove-orphans -d",
"postinstall": "rm -rf ./node_modules/_ts-loader@6.0.3@ts-loader"
},
"ci": {
"version": "8",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 112 KiB

BIN
web/src/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

1
web/src/assets/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -74,7 +74,7 @@ class DropdownSelect extends React.Component{
hasMore={!this.state.loading && this.state.hasMore}
useWindow={false}
>
<List
{/* <List
grid={{
gutter: 16,
sm: 4,
@ -97,7 +97,15 @@ class DropdownSelect extends React.Component{
<Spin />
</div>
)}
</List>
</List> */}
<div className={styles.dslist}>
{(this.props.data || []).map((item)=>{
return <div className={styles.item}><Button key={item[labelField]} onClick={() => {
this.handleItemClick(item)
}}
className={_.isEqual(item, value) ? styles.btnitem + " " + styles.selected : styles.btnitem}>{item[labelField]}</Button></div>
})}
</div>
</InfiniteScroll>
</div>
{!this.state.loading && this.state.hasMore && (

View File

@ -26,7 +26,8 @@
}
.btnitem{
border-radius: 0px;
width: 100px;
margin-right: 8px;
margin-bottom: 8px;
}
.selected{
color: #1890FF;
@ -50,4 +51,9 @@
.dropmenu{
width: 100%;
}
}
.dslist{
display: inline-block;
width: 100%;
}

View File

@ -77,7 +77,7 @@ export default class GlobalHeaderRight extends PureComponent {
}
return (
<div className={className}>
<HeaderSearch
{/* <HeaderSearch
className={`${styles.action} ${styles.search}`}
placeholder={formatMessage({ id: 'component.globalHeader.search' })}
dataSource={[
@ -91,11 +91,14 @@ export default class GlobalHeaderRight extends PureComponent {
onPressEnter={value => {
console.log('enter', value); // eslint-disable-line
}}
/>
/> */}
<a className={styles.action}> <Icon type="code" /></a>
<a className={styles.action} onClick={()=>{
const {history, selectedCluster} = this.props;
history.push(`/dev_tool/elasticsearch/${selectedCluster.id}/`);
}}> <Icon type="code" /></a>
<NoticeIcon
{/* <NoticeIcon
className={styles.action}
count={currentUser.notifyCount}
onItemClick={(item, tabProps) => {
@ -131,8 +134,8 @@ export default class GlobalHeaderRight extends PureComponent {
emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
/>
</NoticeIcon>
{currentUser.name ? (
</NoticeIcon> */}
{/* {currentUser.name ? (
<Dropdown overlay={menu}>
<span className={`${styles.action} ${styles.account}`}>
<Avatar
@ -146,7 +149,7 @@ export default class GlobalHeaderRight extends PureComponent {
</Dropdown>
) : (
<Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
)}
)} */}
<SelectLang className={styles.action} />
</div>
);

View File

@ -57,19 +57,30 @@ export default class GlobalHeader extends PureComponent {
labelField="name"
visible={clusterVisible}
onChange={(item)=>{
this.props.handleSaveGlobalState({
selectedCluster: item
const rel = this.props.handleSaveGlobalState({
selectedCluster: item,
selectedClusterID: item.id,
}).then(()=>{
const {dispatch,history} = this.props;
dispatch({
type:'global/rewriteURL',
payload:{
history,
pathname: history.location.pathname,
}
})
});
const path1=this.props.location.pathname
// const path1=this.props.location.pathname
if (path1[path1.length-1] !=='/'){
const currentPath=path.dirname(path1);
router.replace(currentPath+'/'+item.id);
}else{
router.replace(path1+item.id);
}
// if (path1[path1.length-1] !=='/'){
// const currentPath=path.dirname(path1);
// router.replace(currentPath+'/'+item.id);
// }else{
// router.replace(path1+item.id);
// }
//location.reload()
}}
size={56}
fetchData={

View File

@ -0,0 +1,6 @@
.header{
margin-bottom: 10px;
}
.searchbox{
margin-bottom: 10px;
}

View File

@ -0,0 +1,135 @@
import {List, Avatar, Button, Skeleton, Divider,Row,Col} from 'antd';
import { Icon,Input } from 'antd';
const { Search } = Input;
import { Badge } from 'antd';
import style from './LoadMoreList.css'
import reqwest from 'reqwest';
const count = 3;
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat&noinfo`;
class LoadMoreList extends React.Component {
state = {
initLoading: true,
loading: false,
data: [],
list: [],
};
componentDidMount() {
this.getData(res => {
this.setState({
initLoading: false,
data: res.results,
list: res.results,
});
});
}
getData = callback => {
reqwest({
url: fakeDataUrl,
type: 'json',
method: 'get',
contentType: 'application/json',
success: res => {
callback(res);
},
});
};
onLoadMore = () => {
this.setState({
loading: true,
list: this.state.data.concat([...new Array(count)].map(() => ({ loading: true, name: {} }))),
});
this.getData(res => {
const data = this.state.data.concat(res.results);
this.setState(
{
data,
list: data,
loading: false,
},
() => {
// Resetting window's offsetTop so as to display react-virtualized demo underfloor.
// In real scene, you can using public method of react-virtualized:
// https://stackoverflow.com/questions/46700726/how-to-use-public-method-updateposition-of-react-virtualized
window.dispatchEvent(new Event('resize'));
},
);
});
};
render() {
const { initLoading, loading, list } = this.state;
const loadMore =
!initLoading && !loading ? (
<div
style={{
textAlign: 'center',
marginTop: 12,
height: 32,
lineHeight: '32px',
}}
>
<Button onClick={this.onLoadMore}>Loading more</Button>
</div>
) : null;
return (
<dvi>
<div className={style.header}>
<Row type="flex" justify="space-between" align="bottom">
<Col >
<a href="#" className="head-example" >Elasticsearch 集群</a>
&nbsp;
<Badge
count={4}
style={{ backgroundColor: '#fff', color: '#999', boxShadow: '0 0 0 1px #d9d9d9 inset' }}
/>
</Col>
<Col >
<a href={'#'}>
<Icon type="plus" />
</a>
</Col>
</Row>
</div>
<Search className={style.searchbox} placeholder="input search text" onSearch={value => console.log(value)} enterButton />
<List
className="demo-loadmore-list"
loading={initLoading}
itemLayout="horizontal"
loadMore={loadMore}
dataSource={list}
renderItem={item => (
<List.Item
actions={[<a key="list-loadmore-edit"><Icon type="setting" /></a>]}
>
<Skeleton avatar title={false} loading={item.loading} active>
<List.Item.Meta
avatar={
<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />
}
title={<a href="https://ant.design">{item.name.last}</a>}
/>
<div>
<Icon type="check-circle" theme="twoTone" twoToneColor="#52c41a" title={'Green'} />
</div>
</Skeleton>
</List.Item>
)}
/>
</dvi>
);
}
}
export default LoadMoreList;

View File

@ -88,8 +88,27 @@ export default class SiderMenu extends PureComponent {
});
};
renderIcon(collapsed,icon,logo){
if (collapsed){
return (<div className={styles.icon} id="logo">
<Link to="/">
<img src={icon} alt="logo" />
{/*<h1>{formatMessage({ id: 'app.setting.appname' })}</h1>*/}
</Link>
</div>);
}
return (<div className={styles.logo} id="logo">
<Link to="/">
<img src={logo} alt="logo" />
</Link>
</div>);
}
render() {
const { logo, collapsed, onCollapse, fixSiderbar, theme } = this.props;
const { icon,logo, collapsed, onCollapse, fixSiderbar, theme } = this.props;
const { openKeys } = this.state;
const defaultProps = collapsed ? {} : { openKeys };
@ -109,12 +128,7 @@ export default class SiderMenu extends PureComponent {
theme={theme}
className={siderClassName}
>
<div className={styles.logo} id="logo">
<Link to="/">
<img src={logo} alt="logo" />
<h1>{formatMessage({ id: 'app.setting.appname' })}</h1>
</Link>
</div>
{this.renderIcon(collapsed,icon,logo)}
<BaseMenu
{...this.props}
mode="inline"

View File

@ -2,6 +2,21 @@
@nav-header-height: 64px;
.icon {
height: @nav-header-height;
position: relative;
line-height: @nav-header-height;
padding-left: (@menu-collapsed-width - 32px) / 2;
transition: all 0.3s;
background: #ffffff;
img {
display: inline-block;
vertical-align: middle;
max-height: 32px;
max-width:32px;
}
}
.logo {
height: @nav-header-height;
position: relative;
@ -13,8 +28,9 @@
img {
display: inline-block;
vertical-align: middle;
max-height: 32px;
max-width:32px;
//max-height: 32px;
//max-width:32px;
max-width: 200px;
}
h1 {
color: #000000;
@ -88,4 +104,10 @@
transition: opacity 0.3s @ease-in-out, width 0.3s @ease-in-out;
opacity: 1;
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: #939ea0;
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected a {
color: #FFFFFF;
}
}

View File

@ -0,0 +1,84 @@
// @ts-ignore
import React, { useState, useCallback } from 'react';
import { Modal, Form, Input, Tag } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
interface ITagGeneratorProps {
value?: Array<string>,
onChange?: (val: Array<string>) => void;
}
const TagGenerator = ({ value = [], onChange }: ITagGeneratorProps) => {
const [inputVisible, setInputVisible] = useState<boolean>(false);
const [inputValue, setInputValue] = useState('');
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
const showInput = () => {
setInputVisible(true);
};
const handleInputConfirm = useCallback((e) => {
onChange([...(value || []), e.target.value]);
setInputVisible(false);
setInputValue('');
}, [value]);
const handleRemove = useCallback((index) => {
const newValue = [...value];
newValue.splice(index, 1);
onChange(newValue);
}, [value]);
return (
<div>
{value.map((tag, index) => (
<Tag key={index} closable style={{ padding: '4px 6px', fontSize: 16, margin: '0 10px 10px 0' }} onClose={() => handleRemove(index)}>{tag}</Tag>
))}
{inputVisible && <Input value={inputValue} onChange={handleInputChange} style={{ width: 100 }} onBlur={handleInputConfirm} onPressEnter={handleInputConfirm} />}
{!inputVisible && (
<Tag onClick={showInput} style={{ padding: '4px 6px', fontSize: 16 }}>
<PlusOutlined />
</Tag>
)}
</div>
)
};
interface ICommonCommandModalProps {
onClose: () => void;
onConfirm: (params: Record<string, any>) => void;
form: any;
}
const CommonCommandModal = Form.create()((props: ICommonCommandModalProps) => {
const { form } = props;
const handleConfirm = async () => {
try {
const values = await form.validateFields();
props.onConfirm(values);
} catch (e) {
}
};
return (
<Modal title="保存常用命令" visible={true} onCancel={props.onClose} onOk={handleConfirm} cancelText="取消" okText="确认">
<Form layout="vertical">
<Form.Item label="标题">
{form.getFieldDecorator('title', {
rules: [{ required: true, message: '请输入标题' }]
})(<Input />)}
</Form.Item>
<Form.Item label="标签">
{form.getFieldDecorator('tag')( <TagGenerator />)}
</Form.Item>
</Form>
</Modal>
);
});
export default CommonCommandModal;

View File

@ -0,0 +1,35 @@
.Console {
position: relative;
width: 100%;
height: calc(100vh);
display: flex;
.ConsoleInput_wrapper {
flex: 1;
min-width: 0;
}
.ConsoleOutput_wrapper {
width: 50%;
}
.resizer {
position: relative;
z-index: 999;
width: 14px;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #fafbfd;
padding: 0;
margin: 0;
outline: none;
border: none;
cursor: col-resize;
&:hover {
background-color: #cce1f0;
}
}
}

View File

@ -0,0 +1,104 @@
// @ts-ignore
import React, { useRef, useMemo } from 'react';
import ConsoleInput from './ConsoleInput';
import ConsoleOutput from './ConsoleOutput';
import { Panel } from './Panel';
import PanelsContainer from './PanelContainer';
import { PanelContextProvider } from '../contexts/panel_context';
import { PanelRegistry } from '../contexts/panel_context/registry';
import './Console.scss';
import { RequestContextProvider, useRequestReadContext } from '../contexts/request_context';
import {EditorContextProvider} from '../contexts/editor_context/editor_context';
import { ServicesContextProvider } from '../contexts/services_context';
import { createHistory, History, createStorage, createSettings } from '../services';
import { create } from '../storage/local_storage_object_client';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import {RequestStatusBar} from './request_status_bar';
interface props {
selectedCluster: any;
}
const INITIAL_PANEL_WIDTH = 50;
const PANEL_MIN_WIDTH = '300px';
const ConsoleWrapper = ({
selectedCluster
}:props) => {
const {
requestInFlight: requestInProgress,
lastResult: { data: requestData, error: requestError },
} = useRequestReadContext();
const lastDatum = requestData?.[requestData.length - 1] ?? requestError;
return (
<div>
<EuiFlexGroup className="consoleContainer"
style={{height:30, background:'#fff'}}
gutterSize="none"
direction="column">
<EuiFlexItem className="conApp__tabsExtension">
<RequestStatusBar
requestInProgress={requestInProgress}
// selectedCluster={selectedCluster}
requestResult={
lastDatum
? {
method: lastDatum.request.method.toUpperCase(),
endpoint: lastDatum.request.path,
statusCode: lastDatum.response.statusCode,
statusText: lastDatum.response.statusText,
timeElapsedMs: lastDatum.response.timeMs,
}
: undefined
}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div className="Console">
<PanelsContainer resizerClassName="resizer">
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }} initialWidth={INITIAL_PANEL_WIDTH}>
<ConsoleInput clusterID={selectedCluster.id} />
</Panel>
<Panel style={{ height: '100%', position: 'relative', minWidth: PANEL_MIN_WIDTH }} initialWidth={INITIAL_PANEL_WIDTH}>
<ConsoleOutput clusterID={selectedCluster.id} />
</Panel>
</PanelsContainer>
</div>
</div>
);
};
const Console = (params:props) => {
const registryRef = useRef(new PanelRegistry());
// const [consoleInputKey] = useMemo(()=>{
// return [selectedCluster.id + '-console-input'];
// },[selectedCluster])
const {storage, history, objectStorageClient, settings} = useMemo(()=>{
const storage = createStorage({
engine: window.localStorage,
prefix: 'sense:',
});
const history: History = createHistory({ storage });
const objectStorageClient = create(storage);
const settings = createSettings({storage});
return {storage, history, objectStorageClient, settings};
}, [])
return ( <PanelContextProvider registry={registryRef.current}>
<RequestContextProvider>
<EditorContextProvider>
<ServicesContextProvider value={{ services: { history, storage, objectStorageClient, settings} }}>
<ConsoleWrapper {...params} />
</ServicesContextProvider>
</EditorContextProvider>
</RequestContextProvider>
</PanelContextProvider>
);
}
export default Console;

View File

@ -0,0 +1,101 @@
.conApp {
display: flex;
}
.conApp__editor {
width: 100%;
display: flex;
flex: 0 0 auto;
// Required on IE11 to render ace editor correctly after first input.
position: relative;
&__spinner {
width: 100%;
}
}
.conApp__output {
display: flex;
flex: 1 1 1px;
}
.conApp__editorContent,
.conApp__outputContent {
height: 100%;
flex: 1 1 1px;
font-size: 14px;
}
.conApp__editorActions {
position: absolute;
z-index: 1000;
top: 0;
right: 8px;
line-height: 1;
min-width: 20px;
display: flex;
align-items: center;
button {
line-height: inherit;
cursor: pointer;
// padding: 3px;
}
}
.conApp__editorActionButton {
padding: 0 4px;
appearance: none;
border: none;
background: none;
}
.conApp__editorActionButton--success {
color: #017D73;
}
.conApp__resizer {
// Give the aria selection border priority when the divider is selected on IE11 and Chrome
z-index: 1000;
}
// SASSTODO: This component seems to not be used anymore?
// Possibly replaced by the Ace version
.conApp__autoComplete {
position: absolute;
left: -1000px;
visibility: hidden;
/* by pass any other element in ace and resize bar, but not modal popups */
z-index: 1002;
margin-top: 22px;
}
.conApp__settingsModal {
min-width: 460px;
}
.conApp__requestProgressBarContainer {
position: relative;
z-index: 2000;
}
.conApp__tabsExtension {
border-bottom: 1px solid #cccccc;
}
.ace_method {
color: #c80a68;
}
.ace_url, .ace_start_triple_quote, .ace_end_triple_quote {
color: #00756c;
}
.conApp {
.euiButtonIcon--primary:not([class*='isDisabled']){
box-shadow: none !important;
background-color: transparent !important;
}
}

View File

@ -0,0 +1,210 @@
// @ts-ignore
import React, { useRef, useEffect, CSSProperties, useMemo } from 'react';
import ace from 'brace';
import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiToolTip } from '@elastic/eui';
import { SenseEditor } from '../entities/sense_editor';
import { LegacyCoreEditor } from '../modules/legacy_core_editor/legacy_core_editor';
import ConsoleMenu from './ConsoleMenu';
// import { RequestContextProvider } from '../contexts/request_context';
import { getDocumentation, autoIndent } from '../entities/console_menu_actions';
import './ConsoleInput.scss';
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es';
import { useSetInputEditor } from '../hooks/use_set_input_editor';
import '@elastic/eui/dist/eui_theme_light.css';
import { instance as registry } from '../contexts/editor_context/editor_registry';
import 'antd/dist/antd.css';
import {retrieveAutoCompleteInfo} from '../modules/mappings/mappings';
import {useSaveCurrentTextObject} from '../hooks/use_save_current_text_object';
import {useEditorReadContext} from '../contexts/editor_context/editor_context';
import {useDataInit} from '../hooks/use_data_init';
import { useServicesContext } from '../contexts';
import {applyCurrentSettings} from './apply_editor_settings';
import { subscribeResizeChecker } from './subscribe_console_resize_checker';
const abs: CSSProperties = {
position: 'absolute',
top: '0',
left: '0',
bottom: '0',
right: '0',
};
// interface IConsoleInputProps {
// onExecuteCommand?: () => void;
// onQueryHistoryCommands: () => void;
// onLoadCommonCommands: () => void;
// onPatchCommonCommand: (id: string, params: ICommonCommandParams) => void;
// onDeleteCommonCommand: (id: string) => void;
// }
const SendRequestButton = (props: any) => {
const sendCurrentRequestToES = useSendCurrentRequestToES();
const saveCurrentTextObject = useSaveCurrentTextObject();
const {sendCurrentRequestToESRef, saveCurrentTextObjectRef} = props;
useEffect(()=>{
sendCurrentRequestToESRef.current = sendCurrentRequestToES
saveCurrentTextObjectRef.current = saveCurrentTextObject
}, [sendCurrentRequestToESRef, saveCurrentTextObjectRef])
return (
<EuiToolTip
content={'点击发送请求'}
>
<button
data-test-subj="sendRequestButton"
aria-label={'Click to send request'}
className="conApp__editorActionButton conApp__editorActionButton--success"
onClick={sendCurrentRequestToES}
>
<EuiIcon type="play" />
</button>
</EuiToolTip>
);
};
interface ConsoleInputProps {
clusterID: string,
initialText: string,
}
const DEFAULT_INPUT_VALUE = `GET _search
{
"query": {
"match_all": {}
}
}`;
const ConsoleInputUI = ({clusterID, initialText}:ConsoleInputProps) => {
const editorRef = useRef<HTMLDivElement | null>(null);
const editorActionsRef = useRef<HTMLDivElement | null>(null);
const editorInstanceRef = useRef<SenseEditor | null>(null);
const setInputEditor = useSetInputEditor();
const consoleMenuRef = useRef<ConsoleMenu | null>(null);
const aceEditorRef = useRef<ace.Editor | null>(null);
const sendCurrentRequestToESRef = useRef(()=>{});
const saveCurrentTextObjectRef = useRef((content:string)=>{});
const {services:{settings}} = useServicesContext();
useEffect(() => {
const aceEditor = ace.edit(editorRef.current!);
aceEditorRef.current = aceEditor;
const legacyCoreEditor = new LegacyCoreEditor(aceEditor, editorActionsRef.current as HTMLElement);
aceEditor.commands.addCommand({
name: 'exec_request',
bindKey: 'ctrl+enter',
exec: ()=>{
sendCurrentRequestToESRef.current();
}
})
const senseEditor = new SenseEditor(legacyCoreEditor);
// senseEditor.highlightCurrentRequestsAndUpdateActionBar();
editorInstanceRef.current = senseEditor;
setInputEditor(senseEditor);
senseEditor.update(initialText || DEFAULT_INPUT_VALUE);
applyCurrentSettings(senseEditor!.getCoreEditor(), {fontSize:14, wrapMode: true,});
function setupAutosave() {
let timer: number;
const saveDelay = 500;
senseEditor.getCoreEditor().on('change', () => {
if (timer) {
clearTimeout(timer);
}
timer = window.setTimeout(saveCurrentState, saveDelay);
});
}
function saveCurrentState() {
try {
const content = senseEditor.getCoreEditor().getValue();
saveCurrentTextObjectRef.current(content);
} catch (e) {
console.log(e)
// Ignoring saving error
}
}
const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, senseEditor);
setupAutosave();
return () => {
unsubscribeResizer();
if (editorInstanceRef.current) {
editorInstanceRef.current.getCoreEditor().destroy();
}
}
}, []);
useEffect(()=>{
retrieveAutoCompleteInfo(settings, settings.getAutocomplete());
},[clusterID])
const handleSaveAsCommonCommand = async () => {
const editor = registry.getInputEditor();
const requests = await editor.getRequestsInRange();
const formattedRequest = requests.map(request => ({
method: request.method,
path: request.url,
body: (request.data || [])[0],
}));
return formattedRequest;
};
return (
<div style={abs} data-test-subj="console-application" className="conApp">
<div className="conApp__editor">
<ul className="conApp__autoComplete" id="autocomplete" />
<EuiFlexGroup
className="conApp__editorActions"
id="ConAppEditorActions"
gutterSize="none"
responsive={false}
ref={editorActionsRef}
>
<EuiFlexItem>
<SendRequestButton sendCurrentRequestToESRef={sendCurrentRequestToESRef} saveCurrentTextObjectRef={saveCurrentTextObjectRef}/>
</EuiFlexItem>
<EuiFlexItem>
<ConsoleMenu
getCurl={() => {
return editorInstanceRef.current!.getRequestsAsCURL('');
}}
getDocumentation={() => {
return getDocumentation(editorInstanceRef.current!, '');
}}
autoIndent={(event) => {
autoIndent(editorInstanceRef.current!, event);
}}
saveAsCommonCommand={handleSaveAsCommonCommand}
ref={consoleMenuRef}
/>
</EuiFlexItem>
</EuiFlexGroup>
<div
ref={editorRef}
id="ConAppEditor"
className="conApp__editorContent"
data-test-subj="request-editor"
onClick={()=>{consoleMenuRef.current?.closePopover(); aceEditorRef.current?.focus()}}
/>
</div>
</div>
);
};
const ConsoleInput = ({clusterID}:ConsoleInputProps)=>{
const { done, error, retry } = useDataInit();
const { currentTextObject } = useEditorReadContext();
return done ? <ConsoleInputUI clusterID={clusterID} initialText={currentTextObject?.text}/>: <></>
}
export default ConsoleInput;

View File

@ -0,0 +1,205 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import React, { Component } from 'react';
import { EuiIcon, EuiContextMenuPanel, EuiContextMenuItem, EuiPopover } from '@elastic/eui';
import { ESRequestParams } from '../entities/es_request';
import {notification} from 'antd';
import CommonCommandModal from './CommonCommandModal';
import {saveCommonCommand} from '../modules/es';
import {pushCommand} from '../modules/mappings/mappings';
interface Props {
getCurl: () => Promise<string>;
getDocumentation: () => Promise<string | null>;
autoIndent: (ev: React.MouseEvent) => void;
saveAsCommonCommand: () => Promise<ESRequestParams>;
}
interface State {
isPopoverOpen: boolean;
curlCode: string;
modalVisible: boolean;
}
export default class ConsoleMenu extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
curlCode: '',
isPopoverOpen: false,
modalVisible: false,
};
}
mouseEnter = () => {
if (this.state.isPopoverOpen) return;
this.props.getCurl().then((text) => {
this.setState({ curlCode: text });
});
};
async copyAsCurl() {
try {
await this.copyText(this.state.curlCode);
notification.open({
message: 'Request copied as cURL',
placement: 'bottomRight'
});
} catch (e) {
notification.error({
message: 'Could not copy request as cURL',
placement: 'bottomRight'
});
}
}
async copyText(text: string) {
if (window.navigator?.clipboard) {
await window.navigator.clipboard.writeText(text);
return;
}
throw new Error('Could not copy to clipboard!');
}
onButtonClick = () => {
this.setState((prevState) => ({
isPopoverOpen: !prevState.isPopoverOpen,
}));
};
closePopover = () => {
this.setState({
isPopoverOpen: false,
});
};
openDocs = async () => {
this.closePopover();
const documentation = await this.props.getDocumentation();
if (!documentation) {
return;
}
window.open(documentation, '_blank');
};
autoIndent = (event: React.MouseEvent) => {
this.closePopover();
this.props.autoIndent(event);
};
saveAsCommonCommand = () => {
this.setState({
isPopoverOpen: false,
modalVisible: true
});
};
handleClose = () => {
this.setState({ modalVisible: false });
};
handleConfirm = async (params: Record<string, any>) => {
const requests = await this.props.saveAsCommonCommand();
const reqBody = {
...params,
requests,
};
const result = await (await saveCommonCommand(reqBody))?.json();
if(result.error){
notification.error({
message: result.error
});
}else{
this.handleClose();
notification.success({
message:'保存成功'
});
pushCommand(result);
}
};
render() {
const button = (
<button
className="euiButtonIcon--primary"
onClick={this.onButtonClick}
>
<EuiIcon type="wrench" />
</button>
);
const items = [
<EuiContextMenuItem
key="Copy as cURL"
id="ConCopyAsCurl"
disabled={!window.navigator?.clipboard}
onClick={() => {
this.closePopover();
this.copyAsCurl();
}}
>
curl命令
</EuiContextMenuItem>,
<EuiContextMenuItem
key="Auto indent"
onClick={this.autoIndent}
>
</EuiContextMenuItem>,
<EuiContextMenuItem
key="Save as common command"
onClick={this.saveAsCommonCommand}
>
</EuiContextMenuItem>,
];
return (
<span onMouseEnter={this.mouseEnter}>
<EuiPopover
id="contextMenu"
button={button}
isOpen={this.state.isPopoverOpen}
closePopover={this.closePopover}
panelPaddingSize="none"
anchorPosition="downLeft"
>
<EuiContextMenuPanel items={items} />
</EuiPopover>
{this.state.modalVisible && <CommonCommandModal onClose={this.handleClose} onConfirm={this.handleConfirm} />}
</span>
);
}
}

View File

@ -0,0 +1,99 @@
// @ts-ignore
import React, { useRef, useEffect } from 'react';
import 'brace/mode/text';
import 'brace/mode/json';
import 'brace/mode/yaml';
import { CustomAceEditor, createReadOnlyAceEditor } from '../modules/legacy_core_editor/create_readonly';
import { useRequestReadContext } from '../contexts/request_context';
import './ConsoleInput.scss';
import {applyCurrentSettings} from './apply_editor_settings';
import { subscribeResizeChecker } from './subscribe_console_resize_checker';
const isJSONContentType = (contentType?: string) =>
Boolean(contentType && contentType.indexOf('application/json') >= 0);
function modeForContentType(contentType?: string) {
if (!contentType) {
return 'ace/mode/text';
}
if (isJSONContentType(contentType)) {
return 'ace/mode/json';
} else if (contentType.indexOf('application/yaml') >= 0) {
return 'ace/mode/yaml';
}
return 'ace/mode/text';
}
interface props {
clusterID: string;
}
function ConsoleOutput({clusterID}: props) {
const editorRef = useRef<null | HTMLDivElement>(null);
const editorInstanceRef = useRef<null | CustomAceEditor>(null);
const inputId = 'ConAppOutputTextarea';
const {
lastResult: { data, error },
} = useRequestReadContext();
useEffect(()=>{
editorInstanceRef.current?.setValue('');
},[clusterID])
useEffect(() => {
editorInstanceRef.current = createReadOnlyAceEditor(editorRef.current!);
const textarea = editorRef.current!.querySelector('textarea')!;
textarea.setAttribute('id', inputId);
textarea.setAttribute('readonly', 'true');
applyCurrentSettings(editorInstanceRef.current!, {fontSize:14, wrapMode: true,})
const unsubscribeResizer = subscribeResizeChecker(editorRef.current!, editorInstanceRef.current!);
return () => {
unsubscribeResizer();
editorInstanceRef.current!.destroy();
};
}, []);
useEffect(() => {
const editor = editorInstanceRef.current!;
if (data) {
//const mode = modeForContentType(data[0].response.contentType);
editor.update(
data
.map((result) => {
const { value, contentType } = result.response;
if (isJSONContentType(contentType)) {
let comment = '';
let strValue = value as string;
if(strValue[0]=='#'){
const idx = strValue.indexOf('\n');
comment = strValue.slice(0, idx);
strValue = strValue.slice(idx)
return comment + '\n' + JSON.stringify(JSON.parse(strValue), null, 2);
}
return JSON.stringify(JSON.parse(strValue), null, 2);
}
return value;
})
.join('\n'),
// mode
);
} else if (error) {
const mode = modeForContentType(error.response.contentType);
editor.update(error.response.value as string, mode);
} else {
editor.update('');
}
}, [data, error]);
return (
<div ref={editorRef} className="conApp__outputContent" data-test-subj="response-editor">
<div id="ConAppOutput" />
</div>
);
}
export default ConsoleOutput;

View File

@ -0,0 +1,71 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import React, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
import { usePanelContext } from '../contexts/panel_context';
export interface Props {
children: ReactNode[] | ReactNode;
className?: string;
/**
* initial width of the panel in percents
*/
initialWidth?: number;
style?: CSSProperties;
}
export function Panel({ children, className, initialWidth = 100, style = {} }: Props) {
const [width, setWidth] = useState(`${initialWidth}%`);
const { registry } = usePanelContext();
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
registry.registerPanel({
width: initialWidth,
setWidth(value) {
setWidth(value + '%');
this.width = value;
},
getWidth() {
return divRef.current!.getBoundingClientRect().width;
},
});
}, [initialWidth, registry]);
return (
<div className={className} ref={divRef} style={{ ...style, width, display: 'flex' }}>
{children}
</div>
);
}

View File

@ -0,0 +1,167 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import React, { Children, ReactNode, useRef, useState, useCallback, useEffect } from 'react';
import { keys } from '@elastic/eui';
import { PanelContextProvider } from '../contexts/panel_context';
import { Resizer, ResizerMouseEvent, ResizerKeyDownEvent } from './Resizer';
import { PanelRegistry } from '../contexts/panel_context/registry';
export interface Props {
children: ReactNode;
className?: string;
resizerClassName?: string;
onPanelWidthChange?: (arrayOfPanelWidths: number[]) => any;
}
interface State {
isDragging: boolean;
currentResizerPos: number;
}
const initialState: State = { isDragging: false, currentResizerPos: -1 };
const pxToPercent = (proportion: number, whole: number) => (proportion / whole) * 100;
const PanelsContainer = ({
children,
className,
onPanelWidthChange,
resizerClassName,
}: Props) => {
const childrenArray = Children.toArray(children);
const [firstChild, secondChild] = childrenArray;
const registryRef = useRef(new PanelRegistry());
const containerRef = useRef<HTMLDivElement>(null);
const [state, setState] = useState<State>(initialState);
const getContainerWidth = () => {
return containerRef.current!.getBoundingClientRect().width;
};
const handleMouseDown = useCallback(
(event: ResizerMouseEvent) => {
setState({
...state,
isDragging: true,
currentResizerPos: event.clientX,
});
},
[state]
);
const handleKeyDown = useCallback(
(ev: ResizerKeyDownEvent) => {
const { key } = ev;
if (key === keys.ARROW_LEFT || key === keys.ARROW_RIGHT) {
ev.preventDefault();
const { current: registry } = registryRef;
const [left, right] = registry.getPanels();
const leftPercent = left.width - (key === keys.ARROW_LEFT ? 1 : -1);
const rightPercent = right.width - (key === keys.ARROW_RIGHT ? 1 : -1);
left.setWidth(leftPercent);
right.setWidth(rightPercent);
if (onPanelWidthChange) {
onPanelWidthChange([leftPercent, rightPercent]);
}
}
},
[onPanelWidthChange]
);
useEffect(() => {
if (process.env.NODE_ENV !== 'production') {
// For now we only support bi-split
if (childrenArray.length > 2) {
// eslint-disable-next-line no-console
console.warn(
'[Split Panels Container] Detected more than two children; ignoring additional children.'
);
}
}
}, [childrenArray.length]);
const childrenWithResizer = [
firstChild,
<Resizer
key={'resizer'}
className={resizerClassName}
onKeyDown={handleKeyDown}
onMouseDown={handleMouseDown}
/>,
secondChild,
];
return (
<PanelContextProvider registry={registryRef.current}>
<div
className={className}
ref={containerRef}
style={{ display: 'flex', height: '100%', width: '100%' }}
onMouseMove={(event) => {
if (state.isDragging) {
const { clientX: x } = event;
const { current: registry } = registryRef;
const [left, right] = registry.getPanels();
const delta = x - state.currentResizerPos;
const containerWidth = getContainerWidth();
const leftPercent = pxToPercent(left.getWidth() + delta, containerWidth);
const rightPercent = pxToPercent(right.getWidth() - delta, containerWidth);
left.setWidth(leftPercent);
right.setWidth(rightPercent);
if (onPanelWidthChange) {
onPanelWidthChange([leftPercent, rightPercent]);
}
setState({ ...state, currentResizerPos: x });
}
}}
onMouseUp={() => {
setState(initialState);
}}
>
{childrenWithResizer}
</div>
</PanelContextProvider>
);
};
export default PanelsContainer;

View File

@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import React from 'react';
import { EuiIcon } from '@elastic/eui';
export type ResizerMouseEvent = React.MouseEvent<HTMLButtonElement, MouseEvent>;
export type ResizerKeyDownEvent = React.KeyboardEvent<HTMLButtonElement>;
export interface Props {
onKeyDown: (eve: ResizerKeyDownEvent) => void;
onMouseDown: (eve: ResizerMouseEvent) => void;
className?: string;
}
export function Resizer(props: Props) {
return (
<button
{...props}
>
<EuiIcon type="grabHorizontal" />
</button>
);
}

View File

@ -0,0 +1,25 @@
// @ts-ignore
import React from 'react';
import { EuiIcon, EuiToolTip } from '@elastic/eui/es';
import { useSendCurrentRequestToES } from '../hooks/use_send_current_request_to_es';
const SendRequestButton = () => {
const sendCurrentRequestToES = useSendCurrentRequestToES();
return (
<EuiToolTip
content={'Click to send request'}
>
<button
data-test-subj="sendRequestButton"
aria-label={'Click to send request'}
className="conApp__editorActionButton conApp__editorActionButton--success"
onClick={sendCurrentRequestToES}
>
<EuiIcon type="play" />
</button>
</EuiToolTip>
);
};
export default SendRequestButton;

View File

@ -0,0 +1,37 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { DevToolsSettings } from '../services';
import { CoreEditor } from '../entities/core_editor';
import { CustomAceEditor } from '../modules/legacy_core_editor/create_readonly';
export function applyCurrentSettings(
editor: CoreEditor | CustomAceEditor,
settings: DevToolsSettings
) {
if ((editor as any).setStyles) {
(editor as CoreEditor).setStyles({
wrapLines: settings.wrapMode,
fontSize: settings.fontSize + 'px',
});
} else {
(editor as CustomAceEditor).getSession().setUseWrapMode(settings.wrapMode);
(editor as CustomAceEditor).container.style.fontSize = settings.fontSize + 'px';
}
}

View File

@ -0,0 +1,20 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { RequestStatusBar } from './request_status_bar';

View File

@ -0,0 +1,128 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { FunctionComponent } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiBadge, EuiText, EuiToolTip } from '@elastic/eui';
export interface Props {
requestInProgress: boolean;
requestResult?: {
// Status code of the request, e.g., 200
statusCode: number;
// Status text of the request, e.g., OK
statusText: string;
// Method of the request, e.g., GET
method: string;
// The path of endpoint that was called, e.g., /_search
endpoint: string;
// The time, in milliseconds, that the last request took
timeElapsedMs: number;
};
}
const mapStatusCodeToBadgeColor = (statusCode: number) => {
if (statusCode <= 199) {
return 'default';
}
if (statusCode <= 299) {
return 'secondary';
}
if (statusCode <= 399) {
return 'primary';
}
if (statusCode <= 499) {
return 'warning';
}
return 'danger';
};
export const RequestStatusBar: FunctionComponent<Props> = ({
requestInProgress,
requestResult,
}) => {
let content: React.ReactNode = null;
if (requestInProgress) {
content = (
<EuiFlexItem grow={false}>
<EuiBadge color="hollow">
Request in progress
</EuiBadge>
</EuiFlexItem>
);
} else if (requestResult) {
const { endpoint, method, statusCode, statusText, timeElapsedMs } = requestResult;
content = (
<>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={
<EuiText size="s">{`${method} ${
endpoint.startsWith('/') ? endpoint : '/' + endpoint
}`}</EuiText>
}
>
<EuiBadge color={mapStatusCodeToBadgeColor(statusCode)}>
{/* Use &nbsp; to ensure that no matter the width we don't allow line breaks */}
{statusCode}&nbsp;-&nbsp;{statusText}
</EuiBadge>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="top"
content={
<EuiText size="s">
Time Elapsed
</EuiText>
}
>
<EuiText size="s">
<EuiBadge color="default">
{timeElapsedMs}&nbsp;{'ms'}
</EuiBadge>
</EuiText>
</EuiToolTip>
</EuiFlexItem>
</>
);
}
return (
<EuiFlexGroup
justifyContent="flexEnd"
alignItems="center"
direction="row"
gutterSize="s"
responsive={false}
>
{content}
</EuiFlexGroup>
);
};

View File

@ -0,0 +1,38 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { ResizeChecker } from '../../kibana_utils/public/resize_checker';
export function subscribeResizeChecker(el: HTMLElement, ...editors: any[]) {
const checker = new ResizeChecker(el);
checker.on('resize', () =>
editors.forEach((e) => {
if (e.getCoreEditor) {
e.getCoreEditor().resize();
} else {
e.resize();
}
if (e.updateActionsBar) {
e.updateActionsBar();
}
})
);
return () => checker.destroy();
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { Context, useContext } from 'react';
export const createUseContext = <T>(Ctx: Context<T>, name: string) => {
return () => {
const ctx = useContext(Ctx);
if (!ctx) {
throw new Error(`${name} should be used inside of ${name}Provider!`);
}
return ctx;
};
};

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import React, { createContext, Dispatch, useReducer } from 'react';
import * as editor from '../../stores/editor';
import { createUseContext } from '../create_use_context';
const EditorReadContext = createContext<editor.Store>(editor.initialValue);
const EditorActionContext = createContext<Dispatch<editor.Action>>(() => {});
export interface EditorContextArgs {
children: JSX.Element;
}
export function EditorContextProvider({ children }: EditorContextArgs) {
const [state, dispatch] = useReducer(editor.reducer, editor.initialValue, (value) => ({
...value,
}));
return (
<EditorReadContext.Provider value={state}>
<EditorActionContext.Provider value={dispatch}>{children}</EditorActionContext.Provider>
</EditorReadContext.Provider>
);
}
export const useEditorReadContext = createUseContext(EditorReadContext, 'EditorReadContext');
export const useEditorActionContext = createUseContext(EditorActionContext, 'EditorActionContext');

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { SenseEditor } from '../../entities/sense_editor';
export class EditorRegistry {
private inputEditor: SenseEditor | undefined;
setInputEditor(inputEditor: SenseEditor) {
this.inputEditor = inputEditor;
}
getInputEditor() {
return this.inputEditor!;
}
}
// Create a single instance of this and use as private state.
export const instance = new EditorRegistry();

View File

@ -0,0 +1,37 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export {
EditorContextProvider,
useEditorReadContext,
useEditorActionContext,
} from './editor_context';

View File

@ -0,0 +1,2 @@
export * from './services_context';
export * from './editor_context';

View File

@ -0,0 +1,54 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import React, { createContext, useContext } from 'react';
import { PanelRegistry } from './registry';
const PanelContext = createContext({ registry: new PanelRegistry() });
interface ContextProps {
children: any;
registry: PanelRegistry;
}
export function PanelContextProvider({ children, registry }: ContextProps) {
return <PanelContext.Provider value={{ registry }}>{children}</PanelContext.Provider>;
}
export const usePanelContext = () => {
const context = useContext(PanelContext);
if (context === undefined) {
throw new Error('usePanelContext must be used within a <PanelContextProvider />');
}
return context;
};

View File

@ -0,0 +1,49 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export interface PanelController {
setWidth: (percent: number) => void;
getWidth: () => number;
width: number;
}
export class PanelRegistry {
private panels: PanelController[] = [];
registerPanel(panel: PanelController) {
this.panels.push(panel);
}
getPanels() {
return this.panels;
}
}

View File

@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import React, { createContext, useReducer, Dispatch } from 'react';
import { createUseContext } from './create_use_context';
import * as store from '../stores/request';
const RequestReadContext = createContext<store.Store>(store.initialValue);
const RequestActionContext = createContext<Dispatch<store.Actions>>(() => {});
export function RequestContextProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(store.reducer, store.initialValue);
return (
<RequestReadContext.Provider value={state}>
<RequestActionContext.Provider value={dispatch}>{children}</RequestActionContext.Provider>
</RequestReadContext.Provider>
);
}
export const useRequestReadContext = createUseContext(RequestReadContext, 'RequestReadContext');
export const useRequestActionContext = createUseContext(
RequestActionContext,
'RequestActionContext'
);

View File

@ -0,0 +1,66 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import React, { createContext, useContext, useEffect } from 'react';
import { History, Storage, Settings } from '../services';
import { ObjectStorageClient } from '../entities/storage';
interface ContextServices {
history: History;
storage: Storage;
objectStorageClient: ObjectStorageClient;
settings: Settings;
}
export interface ContextValue {
services: ContextServices;
}
interface ContextProps {
value: ContextValue;
children: JSX.Element;
}
const ServicesContext = createContext<ContextValue | null>(null);
export function ServicesContextProvider({ children, value }: ContextProps) {
return <ServicesContext.Provider value={value}>{children}</ServicesContext.Provider>;
}
export const useServicesContext = () => {
const context = useContext(ServicesContext);
if (context == null) {
throw new Error('useServicesContext must be used inside the ServicesContextProvider.');
}
return context!;
};

View File

@ -0,0 +1,76 @@
import { IEditSession, TokenInfo as BraceTokenInfo } from 'brace';
import { TokensProvider } from './tokens_provider';
import { Token } from './token';
import { Position } from './position';
interface TokenInfo extends BraceTokenInfo {
type: string;
}
const toToken = (lineNumber: number, column: number, token: TokenInfo): Token => ({
type: token.type,
value: token.value,
position: {
lineNumber,
column,
},
});
const toTokens = (lineNumber: number, tokens: TokenInfo[]): Token[] => {
let acc = '';
return tokens.map((token) => {
const column = acc.length + 1;
acc += token.value;
return toToken(lineNumber, column, token);
});
};
const extractTokenFromAceTokenRow = (
lineNumber: number,
column: number,
aceTokens: TokenInfo[]
) => {
let acc = '';
for (const token of aceTokens) {
const start = acc.length + 1;
acc += token.value;
const end = acc.length;
if (column < start) continue;
if (column > end + 1) continue;
return toToken(lineNumber, start, token);
}
return null;
};
export class AceTokensProvider implements TokensProvider {
constructor(private readonly session: IEditSession) {}
getTokens(lineNumber: number): Token[] | null {
if (lineNumber < 1) return null;
// Important: must use a .session.getLength because this is a cached value.
// Calculating line length here will lead to performance issues because this function
// may be called inside of tight loops.
const lineCount = this.session.getLength();
if (lineNumber > lineCount) {
return null;
}
const tokens = (this.session.getTokens(lineNumber - 1) as unknown) as TokenInfo[];
if (!tokens || !tokens.length) {
// We are inside of the document but have no tokens for this line. Return an empty
// array to represent this empty line.
return [];
}
return toTokens(lineNumber, tokens);
}
getTokenAt(pos: Position): Token | null {
const tokens = (this.session.getTokens(pos.lineNumber - 1) as unknown) as TokenInfo[];
if (tokens) {
return extractTokenFromAceTokenRow(pos.lineNumber, pos.column, tokens);
}
return null;
}
}

View File

@ -0,0 +1,55 @@
import { CoreEditor } from './core_editor';
import { Range } from './position';
import { Token } from './token';
export interface ResultTerm {
context?: AutoCompleteContext;
insertValue?: string;
name?: string;
value?: string;
}
export interface AutoCompleteContext {
autoCompleteSet?: null | ResultTerm[];
endpoint?: null | {
paramsAutocomplete: {
getTopLevelComponents: (method?: string | null) => unknown;
};
bodyAutocompleteRootComponents: unknown;
id?: string;
documentation?: string;
};
urlPath?: null | unknown;
urlParamsTokenPath?: Array<Record<string, string>> | null;
method?: string | null;
token?: Token;
activeScheme?: unknown;
replacingToken?: boolean;
rangeToReplace?: Range;
autoCompleteType?: null | string;
editor?: CoreEditor;
/**
* The tokenized user input that prompted the current autocomplete at the cursor. This can be out of sync with
* the input that is currently being displayed in the editor.
*/
createdWithToken?: Token | null;
/**
* The tokenized user input that is currently being displayed at the cursor in the editor when the user accepted
* the autocomplete suggestion.
*/
updatedForToken?: Token | null;
addTemplate?: unknown;
prefixToAdd?: string;
suffixToAdd?: string;
textBoxPosition?: { lineNumber: number; column: number };
urlTokenPath?: string[];
otherTokenValues?: string;
requestStartRow?: number | null;
bodyTokenPath?: string[] | null;
endpointComponentResolver?: unknown;
globalComponentResolver?: unknown;
documentation?: string;
}

View File

@ -0,0 +1,61 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { SenseEditor } from './sense_editor';
import { getEndpointFromPosition} from '../modules/autocomplete/get_endpoint_from_position';
export async function autoIndent(editor: SenseEditor, event: React.MouseEvent) {
event.preventDefault();
await editor.autoIndent();
editor.getCoreEditor().getContainer().focus();
}
export function getDocumentation(
editor: SenseEditor,
docLinkVersion: string
): Promise<string | null> {
return editor.getRequestsInRange().then((requests) => {
if (!requests || requests.length === 0) {
return null;
}
const position = requests[0].range.end;
position.column = position.column - 1;
const endpoint = getEndpointFromPosition(editor.getCoreEditor(), position, editor.parser);
if (endpoint && endpoint.documentation && endpoint.documentation.indexOf('http') !== -1) {
return endpoint.documentation
.replace('/master/', `/${docLinkVersion}/`)
.replace('/current/', `/${docLinkVersion}/`);
} else {
return null;
}
});
}

View File

@ -0,0 +1,261 @@
import { TokensProvider } from './tokens_provider';
import { Token } from './token';
type MarkerRef = any;
export type EditorEvent =
| 'tokenizerUpdate'
| 'changeCursor'
| 'changeScrollTop'
| 'change'
| 'changeSelection';
export type AutoCompleterFunction = (
pos: Position,
prefix: string,
callback: (...args: unknown[]) => void
) => void;
export interface Position {
/**
* The line number, not zero-indexed.
*
* E.g., if given line number 1, this would refer to the first line visible.
*/
lineNumber: number;
/**
* The column number, not zero-indexed.
*
* E.g., if given column number 1, this would refer to the first character of a column.
*/
column: number;
}
export interface Range {
/**
* The start point of the range.
*/
start: Position;
/**
* The end point of the range.
*/
end: Position;
}
/**
* Enumeration of the different states the current position can be in.
*
* Current implementation uses low-level binary operations OR ('|') and AND ('&') to, respectively:
*
* - Create a combination of acceptable states.
* - Extract the states from the acceptable combination.
*
* E.g.
* ```ts
* const acceptableStates = LINE_MODE.REQUEST_START | LINE_MODE.IN_REQUEST; // binary '110'
*
* // Is MULTI_DOC_CUR_DOC_END ('1000') acceptable?
* Boolean(acceptableStates & LINE_MODE.MULTI_DOC_CUR_DOC_END) // false
*
* // Is REQUEST_START ('10') acceptable?
* Boolean(acceptableStates & LINE_MODE.REQUEST_START) // true
* ```
*
* This implementation will probably be changed to something more accessible in future but is documented
* here for reference.
*/
export enum LINE_MODE {
REQUEST_START = 2,
IN_REQUEST = 4,
MULTI_DOC_CUR_DOC_END = 8,
REQUEST_END = 16,
BETWEEN_REQUESTS = 32,
UNKNOWN = 64,
}
/**
* The CoreEditor is a component separate from the Editor implementation that provides Console
* app specific business logic. The CoreEditor is an interface to the lower-level editor implementation
* being used which is usually vendor code such as Ace or Monaco.
*/
export interface CoreEditor {
/**
* Get the current position of the cursor.
*/
getCurrentPosition(): Position;
/**
* Get the contents of the editor.
*/
getValue(): string;
/**
* Sets the contents of the editor.
*
* Returns a promise so that callers can wait for re-tokenizing to complete.
*/
setValue(value: string, forceRetokenize: boolean): Promise<void>;
/**
* Get the contents of the editor at a specific line.
*/
getLineValue(lineNumber: number): string;
/**
* Insert a string value at the current cursor position.
*/
insert(value: string): void;
/**
* Insert a string value at the indicated position.
*/
insert(pos: Position, value: string): void;
/**
* Replace a range of text.
*/
replace(rangeToReplace: Range, value: string): void;
/**
* Clear the selected range.
*/
clearSelection(): void;
/**
* Returns the {@link Range} for currently selected text
*/
getSelectionRange(): Range;
/**
* Move the cursor to the indicated position.
*/
moveCursorToPosition(pos: Position): void;
/**
* Get the token at the indicated position. The token considered "at" the position is the
* one directly preceding the position.
*
* Returns null if there is no such token.
*/
getTokenAt(pos: Position): Token | null;
/**
* Get an iterable token provider.
*/
getTokenProvider(): TokensProvider;
/**
* Get the contents of the editor between two points.
*/
getValueInRange(range: Range): string;
/**
* Get the lexer state at the end of a specific line.
*/
getLineState(lineNumber: number): string;
/**
* Get line content between and including the start and end lines provided.
*/
getLines(startLine: number, endLine: number): string[];
/**
* Replace a range in the current buffer with the provided value.
*/
replaceRange(range: Range, value: string): void;
/**
* Return the current line count in the buffer.
*
* @remark
* This function should be usable in a tight loop and must make used of a cached
* line count.
*/
getLineCount(): number;
/**
* A legacy mechanism which gives consumers of this interface a chance to wait for
* latest tokenization to complete.
*/
waitForLatestTokens(): Promise<void>;
/**
* Mark a range in the current buffer
*/
addMarker(range: Range): MarkerRef;
/**
* Mark a range in the current buffer
*/
removeMarker(ref: MarkerRef): void;
/**
* Get a number that represents the current wrap limit on a line
*/
getWrapLimit(): number;
/**
* Register a listener for predefined editor events
*/
on(event: EditorEvent, listener: () => void): void;
/**
* Unregister a listener for predefined editor events
*/
off(event: EditorEvent, listener: () => void): void;
/**
* Execute a predefined editor command.
*/
execCommand(cmd: string): void;
/**
* Returns a boolean value indicating whether or not the completer UI is currently showing in
* the editor
*/
isCompleterActive(): boolean;
/**
* Get the HTML container element for this editor instance
*/
getContainer(): HTMLDivElement;
/**
* Because the core editor should not know about requests, but can know about ranges we still
* have this backdoor to update UI in response to request range changes, for example, as the user
* moves the cursor around
*/
legacyUpdateUI(opts: unknown): void;
/**
* A method to for the editor to resize, useful when, for instance, window size changes.
*/
resize(): void;
/**
* Expose a way to set styles on the editor
*/
setStyles(styles: { wrapLines: boolean; fontSize: string }): void;
/**
* Register a keyboard shortcut and provide a function to be called.
*/
registerKeyboardShortcut(opts: {
keys: string | { win?: string; mac?: string };
fn: () => void;
name: string;
}): void;
/**
* Register a completions function that will be called when the editor
* detects a change
*/
registerAutocompleter(autocompleter: AutoCompleterFunction): void;
/**
* Release any resources in use by the editor.
*/
destroy(): void;
}

View File

@ -0,0 +1,36 @@
export interface ESRequest {
method: string;
endpoint: string;
data?: string;
}
export interface ESRequestParams {
method: string,
path: string,
body?: string,
}
export type BaseResponseType =
| 'application/json'
| 'text/csv'
| 'text/tab-separated-values'
| 'text/plain'
| 'application/yaml'
| 'unknown';
export interface EsRequestArgs {
requests: Array<{ url: string; method: string; data: string[] }>;
}
export interface ESResponseObject<V = unknown> {
statusCode: number;
statusText: string;
timeMs: number;
contentType: BaseResponseType;
value: V;
}
export interface ESRequestResult<V = unknown> {
request: { data: string; method: string; path: string };
response: ESResponseObject<V>;
}

View File

@ -0,0 +1,424 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import ace from 'brace';
import { Editor as IAceEditor, IEditSession as IAceEditSession } from 'brace';
import $ from 'jquery';
import { CoreEditor, AutoCompleterFunction, EditorEvent } from './core_editor';
import { Position, Range } from './position';
import { Token } from './token';
import { TokensProvider } from './tokens_provider';
import { AceTokensProvider } from './ace_tokens_providers';
// @ts-ignore
import * as InputMode from './mode/input';
const _AceRange = ace.acequire('ace/range').Range;
const rangeToAceRange = ({ start, end }: Range) =>
new _AceRange(start.lineNumber - 1, start.column - 1, end.lineNumber - 1, end.column - 1);
export class LegacyCoreEditor implements CoreEditor {
private _aceOnPaste: Function;
$actions: JQuery<HTMLElement>;
resize: () => void;
constructor(private readonly editor: IAceEditor, actions: HTMLElement) {
this.$actions = $(actions);
this.editor.setShowPrintMargin(false);
const session = this.editor.getSession();
session.setMode(new InputMode.Mode());
((session as unknown) as { setFoldStyle: (style: string) => void }).setFoldStyle(
'markbeginend'
);
session.setTabSize(2);
session.setUseWrapMode(true);
// Intercept ace on paste handler.
this._aceOnPaste = this.editor.onPaste;
this.editor.onPaste = this.DO_NOT_USE_onPaste.bind(this);
this.editor.setOptions({
enableBasicAutocompletion: true,
});
this.editor.$blockScrolling = Infinity;
this.hideActionsBar();
this.editor.focus();
}
// dirty check for tokenizer state, uses a lot less cycles
// than listening for tokenizerUpdate
waitForLatestTokens(): Promise<void> {
return new Promise<void>((resolve) => {
const session = this.editor.getSession();
const checkInterval = 25;
const check = () => {
// If the bgTokenizer doesn't exist, we can assume that the underlying editor has been
// torn down, e.g. by closing the History tab, and we don't need to do anything further.
if (session.bgTokenizer) {
// Wait until the bgTokenizer is done running before executing the callback.
if (((session.bgTokenizer as unknown) as { running: boolean }).running) {
setTimeout(check, checkInterval);
} else {
resolve();
}
}
};
setTimeout(check, 0);
});
}
getLineState(lineNumber: number) {
const session = this.editor.getSession();
return session.getState(lineNumber - 1);
}
getValueInRange(range: Range): string {
return this.editor.getSession().getTextRange(rangeToAceRange(range));
}
getTokenProvider(): TokensProvider {
return new AceTokensProvider(this.editor.getSession());
}
getValue(): string {
return this.editor.getValue();
}
async setValue(text: string, forceRetokenize: boolean): Promise<void> {
const session = this.editor.getSession();
session.setValue(text);
if (forceRetokenize) {
await this.forceRetokenize();
}
}
getLineValue(lineNumber: number): string {
const session = this.editor.getSession();
return session.getLine(lineNumber - 1);
}
getCurrentPosition(): Position {
const cursorPosition = this.editor.getCursorPosition();
return {
lineNumber: cursorPosition.row + 1,
column: cursorPosition.column + 1,
};
}
clearSelection(): void {
this.editor.clearSelection();
}
getTokenAt(pos: Position): Token | null {
const provider = this.getTokenProvider();
return provider.getTokenAt(pos);
}
insert(valueOrPos: string | Position, value?: string): void {
if (typeof valueOrPos === 'string') {
this.editor.insert(valueOrPos);
return;
}
const document = this.editor.getSession().getDocument();
document.insert(
{
column: valueOrPos.column - 1,
row: valueOrPos.lineNumber - 1,
},
value || ''
);
}
moveCursorToPosition(pos: Position): void {
this.editor.moveCursorToPosition({ row: pos.lineNumber - 1, column: pos.column - 1 });
}
replace(range: Range, value: string): void {
const session = this.editor.getSession();
session.replace(rangeToAceRange(range), value);
}
getLines(startLine: number, endLine: number): string[] {
const session = this.editor.getSession();
return session.getLines(startLine - 1, endLine - 1);
}
replaceRange(range: Range, value: string) {
const pos = this.editor.getCursorPosition();
this.editor.getSession().replace(rangeToAceRange(range), value);
const maxRow = Math.max(range.start.lineNumber - 1 + value.split('\n').length - 1, 1);
pos.row = Math.min(pos.row, maxRow);
this.editor.moveCursorToPosition(pos);
// ACE UPGRADE - check if needed - at the moment the above may trigger a selection.
this.editor.clearSelection();
}
getSelectionRange() {
const result = this.editor.getSelectionRange();
return {
start: {
lineNumber: result.start.row + 1,
column: result.start.column + 1,
},
end: {
lineNumber: result.end.row + 1,
column: result.end.column + 1,
},
};
}
getLineCount() {
// Only use this function to return line count as it uses
// a cache.
return this.editor.getSession().getLength();
}
addMarker(range: Range) {
return this.editor
.getSession()
.addMarker(rangeToAceRange(range), 'ace_snippet-marker', 'fullLine', false);
}
removeMarker(ref: number) {
this.editor.getSession().removeMarker(ref);
}
getWrapLimit(): number {
return this.editor.getSession().getWrapLimit();
}
on(event: EditorEvent, listener: () => void) {
if (event === 'changeCursor') {
this.editor.getSession().selection.on(event, listener);
} else if (event === 'changeSelection') {
this.editor.on(event, listener);
} else {
this.editor.getSession().on(event, listener);
}
}
off(event: EditorEvent, listener: () => void) {
if (event === 'changeSelection') {
this.editor.off(event, listener);
}
}
isCompleterActive() {
return Boolean(
((this.editor as unknown) as { completer: { activated: unknown } }).completer &&
((this.editor as unknown) as { completer: { activated: unknown } }).completer.activated
);
}
private forceRetokenize() {
const session = this.editor.getSession();
return new Promise<void>((resolve) => {
// force update of tokens, but not on this thread to allow for ace rendering.
setTimeout(function () {
let i;
for (i = 0; i < session.getLength(); i++) {
session.getTokens(i);
}
resolve();
});
});
}
// eslint-disable-next-line @typescript-eslint/naming-convention
private DO_NOT_USE_onPaste(text: string) {
this._aceOnPaste.call(this.editor, text);
}
private setActionsBar = (value: number | null, topOrBottom: 'top' | 'bottom' = 'top') => {
if (value === null) {
this.$actions.css('visibility', 'hidden');
} else {
if (topOrBottom === 'top') {
this.$actions.css({
bottom: 'auto',
top: value,
visibility: 'visible',
});
} else {
this.$actions.css({
top: 'auto',
bottom: value,
visibility: 'visible',
});
}
}
};
private hideActionsBar = () => {
this.setActionsBar(null);
};
execCommand(cmd: string) {
this.editor.execCommand(cmd);
}
getContainer(): HTMLDivElement {
return this.editor.container as HTMLDivElement;
}
setStyles(styles: { wrapLines: boolean; fontSize: string }) {
this.editor.getSession().setUseWrapMode(styles.wrapLines);
this.editor.container.style.fontSize = styles.fontSize;
}
registerKeyboardShortcut(opts: { keys: string; fn: () => void; name: string }): void {
this.editor.commands.addCommand({
exec: opts.fn,
name: opts.name,
bindKey: opts.keys,
});
}
legacyUpdateUI(range: Range) {
if (!this.$actions) {
return;
}
if (range) {
// elements are positioned relative to the editor's container
// pageY is relative to page, so subtract the offset
// from pageY to get the new top value
const offsetFromPage = $(this.editor.container).offset()!.top;
const startLine = range.start.lineNumber;
const startColumn = range.start.column;
const firstLine = this.getLineValue(startLine);
const maxLineLength = this.getWrapLimit() - 5;
const isWrapping = firstLine.length > maxLineLength;
const totalOffset = offsetFromPage - (window.pageYOffset || 0);
const getScreenCoords = (line: number) =>
this.editor.renderer.textToScreenCoordinates(line - 1, startColumn).pageY - totalOffset;
const topOfReq = getScreenCoords(startLine);
if (topOfReq >= 0) {
const { bottom: maxBottom } = this.editor.container.getBoundingClientRect();
if (topOfReq > maxBottom - totalOffset) {
this.setActionsBar(0, 'bottom');
return;
}
let offset = 0;
if (isWrapping) {
// Try get the line height of the text area in pixels.
const textArea = $(this.editor.container.querySelector('textArea')!);
const hasRoomOnNextLine = this.getLineValue(startLine).length < maxLineLength;
if (textArea && hasRoomOnNextLine) {
// Line height + the number of wraps we have on a line.
offset += this.getLineValue(startLine).length * textArea.height()!;
} else {
if (startLine > 1) {
this.setActionsBar(getScreenCoords(startLine - 1));
return;
}
this.setActionsBar(getScreenCoords(startLine + 1));
return;
}
}
this.setActionsBar(topOfReq + offset);
return;
}
const bottomOfReq =
this.editor.renderer.textToScreenCoordinates(range.end.lineNumber, range.end.column).pageY -
offsetFromPage;
if (bottomOfReq >= 0) {
this.setActionsBar(0);
return;
}
}
}
registerAutocompleter(autocompleter: AutoCompleterFunction): void {
// Hook into Ace
// disable standard context based autocompletion.
// @ts-ignore
ace.define(
'ace/autocomplete/text_completer',
['require', 'exports', 'module'],
function (
require: unknown,
exports: {
getCompletions: (
innerEditor: unknown,
session: unknown,
pos: unknown,
prefix: unknown,
callback: (e: null | Error, values: string[]) => void
) => void;
}
) {
exports.getCompletions = function (innerEditor, session, pos, prefix, callback) {
callback(null, []);
};
}
);
const langTools = ace.acequire('ace/ext/language_tools');
langTools.setCompleters([
{
identifierRegexps: [
/[a-zA-Z_0-9\.\$\-\u00A2-\uFFFF]/, // adds support for dot character
],
getCompletions: (
// eslint-disable-next-line @typescript-eslint/naming-convention
DO_NOT_USE_1: IAceEditor,
// eslint-disable-next-line @typescript-eslint/naming-convention
DO_NOT_USE_2: IAceEditSession,
pos: { row: number; column: number },
prefix: string,
callback: (...args: unknown[]) => void
) => {
const position: Position = {
lineNumber: pos.row + 1,
column: pos.column + 1,
};
autocompleter(position, prefix, callback);
},
},
]);
}
destroy() {
this.editor.destroy();
}
}

View File

@ -0,0 +1,27 @@
export interface Position {
/**
* The line number, not zero-indexed.
*
* E.g., if given line number 1, this would refer to the first line visible.
*/
lineNumber: number;
/**
* The column number, not zero-indexed.
*
* E.g., if given column number 1, this would refer to the first character of a column.
*/
column: number;
}
export interface Range {
/**
* The start point of the range.
*/
start: Position;
/**
* The end point of the range.
*/
end: Position;
}

View File

@ -0,0 +1,137 @@
import { Token } from './token';
import { TokenIterator } from './token_iterator';
import { CoreEditor } from './core_editor';
export const MODE = {
REQUEST_START: 2,
IN_REQUEST: 4,
MULTI_DOC_CUR_DOC_END: 8,
REQUEST_END: 16,
BETWEEN_REQUESTS: 32,
};
// eslint-disable-next-line import/no-default-export
export default class RowParser {
constructor(private readonly editor: CoreEditor) {}
MODE = MODE;
getRowParseMode(lineNumber = this.editor.getCurrentPosition().lineNumber) {
const linesCount = this.editor.getLineCount();
if (lineNumber > linesCount || lineNumber < 1) {
return MODE.BETWEEN_REQUESTS;
}
const mode = this.editor.getLineState(lineNumber);
if (!mode) {
return MODE.BETWEEN_REQUESTS;
} // shouldn't really happen
// If another "start" mode is added here because we want to allow for new language highlighting
// please see https://github.com/elastic/kibana/pull/51446 for a discussion on why
// should consider a different approach.
if (mode !== 'start' && mode !== 'start-sql') {
return MODE.IN_REQUEST;
}
let line = (this.editor.getLineValue(lineNumber) || '').trim();
if (!line || line[0] === '#') {
return MODE.BETWEEN_REQUESTS;
} // empty line or a comment waiting for a new req to start
if (line.indexOf('}', line.length - 1) >= 0) {
// check for a multi doc request (must start a new json doc immediately after this one end.
lineNumber++;
if (lineNumber < linesCount + 1) {
line = (this.editor.getLineValue(lineNumber) || '').trim();
if (line.indexOf('{') === 0) {
// next line is another doc in a multi doc
// eslint-disable-next-line no-bitwise
return MODE.MULTI_DOC_CUR_DOC_END | MODE.IN_REQUEST;
}
}
// eslint-disable-next-line no-bitwise
return MODE.REQUEST_END | MODE.MULTI_DOC_CUR_DOC_END; // end of request
}
// check for single line requests
lineNumber++;
if (lineNumber >= linesCount + 1) {
// eslint-disable-next-line no-bitwise
return MODE.REQUEST_START | MODE.REQUEST_END;
}
line = (this.editor.getLineValue(lineNumber) || '').trim();
if (line.indexOf('{') !== 0) {
// next line is another request
// eslint-disable-next-line no-bitwise
return MODE.REQUEST_START | MODE.REQUEST_END;
}
return MODE.REQUEST_START;
}
rowPredicate(lineNumber: number | undefined, editor: CoreEditor, value: number) {
const mode = this.getRowParseMode(lineNumber);
// eslint-disable-next-line no-bitwise
return (mode & value) > 0;
}
isEndRequestRow(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
return this.rowPredicate(row, editor, MODE.REQUEST_END);
}
isRequestEdge(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
// eslint-disable-next-line no-bitwise
return this.rowPredicate(row, editor, MODE.REQUEST_END | MODE.REQUEST_START);
}
isStartRequestRow(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
return this.rowPredicate(row, editor, MODE.REQUEST_START);
}
isInBetweenRequestsRow(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
return this.rowPredicate(row, editor, MODE.BETWEEN_REQUESTS);
}
isInRequestsRow(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
return this.rowPredicate(row, editor, MODE.IN_REQUEST);
}
isMultiDocDocEndRow(row?: number, _e?: CoreEditor) {
const editor = _e || this.editor;
return this.rowPredicate(row, editor, MODE.MULTI_DOC_CUR_DOC_END);
}
isEmptyToken(tokenOrTokenIter: TokenIterator | Token | null) {
const token =
tokenOrTokenIter && (tokenOrTokenIter as TokenIterator).getCurrentToken
? (tokenOrTokenIter as TokenIterator).getCurrentToken()
: tokenOrTokenIter;
return !token || (token as Token).type === 'whitespace';
}
isUrlOrMethodToken(tokenOrTokenIter: TokenIterator | Token) {
const t = (tokenOrTokenIter as TokenIterator)?.getCurrentToken() || (tokenOrTokenIter as Token);
return t && t.type && (t.type === 'method' || t.type.indexOf('url') === 0);
}
nextNonEmptyToken(tokenIter: TokenIterator) {
let t = tokenIter.stepForward();
while (t && this.isEmptyToken(t)) {
t = tokenIter.stepForward();
}
return t;
}
prevNonEmptyToken(tokenIter: TokenIterator) {
let t = tokenIter.stepBackward();
// empty rows return null token.
while ((t || tokenIter.getCurrentPosition().lineNumber > 1) && this.isEmptyToken(t))
t = tokenIter.stepBackward();
return t;
}
}

View File

@ -0,0 +1,488 @@
import _ from 'lodash';
import { collapseLiteralStrings } from '../utils/xjson';
import RowParser from './row_parser';
import { CoreEditor } from './core_editor';
import { Position, Range } from './position';
import { createTokenIterator } from '../factories/token_iterator';
import createAutocompleter from '../modules/autocomplete/autocomplete';
import * as utils from '../utils/autocomplete';
import * as es from '../modules/es';
export class SenseEditor {
currentReqRange: (Range & { markerRef: unknown }) | null;
parser: RowParser;
private readonly autocomplete: ReturnType<typeof createAutocompleter>;
constructor(private readonly coreEditor: CoreEditor) {
this.currentReqRange = null;
this.parser = new RowParser(this.coreEditor);
this.autocomplete = createAutocompleter({
coreEditor,
parser: this.parser,
});
this.coreEditor.registerAutocompleter(this.autocomplete.getCompletions);
this.coreEditor.on(
'tokenizerUpdate',
this.highlightCurrentRequestsAndUpdateActionBar.bind(this)
);
this.coreEditor.on('changeCursor', this.highlightCurrentRequestsAndUpdateActionBar.bind(this));
this.coreEditor.on('changeScrollTop', this.updateActionsBar.bind(this));
}
prevRequestStart = (rowOrPos?: number | Position): Position => {
let curRow: number;
if (rowOrPos == null) {
curRow = this.coreEditor.getCurrentPosition().lineNumber;
} else if (_.isObject(rowOrPos)) {
curRow = (rowOrPos as Position).lineNumber;
} else {
curRow = rowOrPos as number;
}
while (curRow > 0 && !this.parser.isStartRequestRow(curRow, this.coreEditor)) curRow--;
return {
lineNumber: curRow,
column: 1,
};
};
nextRequestStart = (rowOrPos?: number | Position) => {
let curRow: number;
if (rowOrPos == null) {
curRow = this.coreEditor.getCurrentPosition().lineNumber;
} else if (_.isObject(rowOrPos)) {
curRow = (rowOrPos as Position).lineNumber;
} else {
curRow = rowOrPos as number;
}
const maxLines = this.coreEditor.getLineCount();
for (; curRow < maxLines - 1; curRow++) {
if (this.parser.isStartRequestRow(curRow, this.coreEditor)) {
break;
}
}
return {
row: curRow,
column: 0,
};
};
autoIndent = _.debounce(async () => {
await this.coreEditor.waitForLatestTokens();
const reqRange = await this.getRequestRange();
if (!reqRange) {
return;
}
const parsedReq = await this.getRequest();
if (!parsedReq) {
return;
}
if (parsedReq.data && parsedReq.data.length > 0) {
let indent = parsedReq.data.length === 1; // unindent multi docs by default
let formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent);
if (!formattedData.changed) {
// toggle.
indent = !indent;
formattedData = utils.formatRequestBodyDoc(parsedReq.data, indent);
}
parsedReq.data = formattedData.data;
this.replaceRequestRange(parsedReq, reqRange);
}
}, 25);
update = async (data: string, reTokenizeAll = false) => {
return this.coreEditor.setValue(data, reTokenizeAll);
};
replaceRequestRange = (
newRequest: { method: string; url: string; data: string | string[] },
requestRange: Range
) => {
const text = utils.textFromRequest(newRequest);
if (requestRange) {
this.coreEditor.replaceRange(requestRange, text);
} else {
// just insert where we are
this.coreEditor.insert(this.coreEditor.getCurrentPosition(), text);
}
};
getRequestRange = async (lineNumber?: number): Promise<Range | null> => {
await this.coreEditor.waitForLatestTokens();
if (this.parser.isInBetweenRequestsRow(lineNumber)) {
return null;
}
const reqStart = this.prevRequestStart(lineNumber);
const reqEnd = this.nextRequestEnd(reqStart);
return {
start: {
...reqStart,
},
end: {
...reqEnd,
},
};
};
expandRangeToRequestEdges = async (
range = this.coreEditor.getSelectionRange()
): Promise<Range | null> => {
await this.coreEditor.waitForLatestTokens();
let startLineNumber = range.start.lineNumber;
let endLineNumber = range.end.lineNumber;
const maxLine = Math.max(1, this.coreEditor.getLineCount());
if (this.parser.isInBetweenRequestsRow(startLineNumber)) {
/* Do nothing... */
} else {
for (; startLineNumber >= 1; startLineNumber--) {
if (this.parser.isStartRequestRow(startLineNumber)) {
break;
}
}
}
if (startLineNumber < 1 || startLineNumber > endLineNumber) {
return null;
}
// move end row to the previous request end if between requests, otherwise walk forward
if (this.parser.isInBetweenRequestsRow(endLineNumber)) {
for (; endLineNumber >= startLineNumber; endLineNumber--) {
if (this.parser.isEndRequestRow(endLineNumber)) {
break;
}
}
} else {
for (; endLineNumber <= maxLine; endLineNumber++) {
if (this.parser.isEndRequestRow(endLineNumber)) {
break;
}
}
}
if (endLineNumber < startLineNumber || endLineNumber > maxLine) {
return null;
}
const endColumn =
(this.coreEditor.getLineValue(endLineNumber) || '').replace(/\s+$/, '').length + 1;
return {
start: {
lineNumber: startLineNumber,
column: 1,
},
end: {
lineNumber: endLineNumber,
column: endColumn,
},
};
};
getRequestInRange = async (range?: Range) => {
await this.coreEditor.waitForLatestTokens();
if (!range) {
return null;
}
const request: {
method: string;
data: string[];
url: string;
range: Range;
} = {
method: '',
data: [],
url: '',
range,
};
const pos = range.start;
const tokenIter = createTokenIterator({ editor: this.coreEditor, position: pos });
let t = tokenIter.getCurrentToken();
if (this.parser.isEmptyToken(t)) {
// if the row starts with some spaces, skip them.
t = this.parser.nextNonEmptyToken(tokenIter);
}
if (t == null) {
return null;
}
request.method = t.value;
t = this.parser.nextNonEmptyToken(tokenIter);
if (!t || t.type === 'method') {
return null;
}
request.url = '';
while (t && t.type && t.type.indexOf('url') === 0) {
request.url += t.value;
t = tokenIter.stepForward();
}
if (this.parser.isEmptyToken(t)) {
// if the url row ends with some spaces, skip them.
t = this.parser.nextNonEmptyToken(tokenIter);
}
let bodyStartLineNumber = (t ? 0 : 1) + tokenIter.getCurrentPosition().lineNumber; // artificially increase end of docs.
let dataEndPos: Position;
while (
bodyStartLineNumber < range.end.lineNumber ||
(bodyStartLineNumber === range.end.lineNumber && 1 < range.end.column)
) {
dataEndPos = this.nextDataDocEnd({
lineNumber: bodyStartLineNumber,
column: 1,
});
const bodyRange: Range = {
start: {
lineNumber: bodyStartLineNumber,
column: 1,
},
end: dataEndPos,
};
const data = this.coreEditor.getValueInRange(bodyRange)!;
request.data.push(data.trim());
bodyStartLineNumber = dataEndPos.lineNumber + 1;
}
return request;
};
getRequestsInRange = async (
range = this.coreEditor.getSelectionRange(),
includeNonRequestBlocks = false
): Promise<any[]> => {
await this.coreEditor.waitForLatestTokens();
if (!range) {
return [];
}
const expandedRange = await this.expandRangeToRequestEdges(range);
if (!expandedRange) {
return [];
}
const requests: unknown[] = [];
let rangeStartCursor = expandedRange.start.lineNumber;
const endLineNumber = expandedRange.end.lineNumber;
// move to the next request start (during the second iterations this may not be exactly on a request
let currentLineNumber = expandedRange.start.lineNumber;
const flushNonRequestBlock = () => {
if (includeNonRequestBlocks) {
const nonRequestPrefixBlock = this.coreEditor
.getLines(rangeStartCursor, currentLineNumber - 1)
.join('\n');
if (nonRequestPrefixBlock) {
requests.push(nonRequestPrefixBlock);
}
}
};
while (currentLineNumber <= endLineNumber) {
if (this.parser.isStartRequestRow(currentLineNumber)) {
flushNonRequestBlock();
const request = await this.getRequest(currentLineNumber);
if (!request) {
// Something has probably gone wrong.
return requests;
} else {
requests.push(request);
rangeStartCursor = currentLineNumber = request.range.end.lineNumber + 1;
}
} else {
++currentLineNumber;
}
}
flushNonRequestBlock();
return requests;
};
getRequest = async (row?: number) => {
await this.coreEditor.waitForLatestTokens();
if (this.parser.isInBetweenRequestsRow(row)) {
return null;
}
const range = await this.getRequestRange(row);
return this.getRequestInRange(range!);
};
moveToPreviousRequestEdge = async () => {
await this.coreEditor.waitForLatestTokens();
const pos = this.coreEditor.getCurrentPosition();
for (
pos.lineNumber--;
pos.lineNumber > 1 && !this.parser.isRequestEdge(pos.lineNumber);
pos.lineNumber--
) {
// loop for side effects
}
this.coreEditor.moveCursorToPosition({
lineNumber: pos.lineNumber,
column: 1,
});
};
moveToNextRequestEdge = async (moveOnlyIfNotOnEdge: boolean) => {
await this.coreEditor.waitForLatestTokens();
const pos = this.coreEditor.getCurrentPosition();
const maxRow = this.coreEditor.getLineCount();
if (!moveOnlyIfNotOnEdge) {
pos.lineNumber++;
}
for (
;
pos.lineNumber < maxRow && !this.parser.isRequestEdge(pos.lineNumber);
pos.lineNumber++
) {
// loop for side effects
}
this.coreEditor.moveCursorToPosition({
lineNumber: pos.lineNumber,
column: 1,
});
};
nextRequestEnd = (pos: Position): Position => {
pos = pos || this.coreEditor.getCurrentPosition();
const maxLines = this.coreEditor.getLineCount();
let curLineNumber = pos.lineNumber;
for (; curLineNumber <= maxLines; ++curLineNumber) {
const curRowMode = this.parser.getRowParseMode(curLineNumber);
// eslint-disable-next-line no-bitwise
if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) {
break;
}
// eslint-disable-next-line no-bitwise
if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) {
break;
}
}
const column =
(this.coreEditor.getLineValue(curLineNumber) || '').replace(/\s+$/, '').length + 1;
return {
lineNumber: curLineNumber,
column,
};
};
nextDataDocEnd = (pos: Position): Position => {
pos = pos || this.coreEditor.getCurrentPosition();
let curLineNumber = pos.lineNumber;
const maxLines = this.coreEditor.getLineCount();
for (; curLineNumber < maxLines; curLineNumber++) {
const curRowMode = this.parser.getRowParseMode(curLineNumber);
// eslint-disable-next-line no-bitwise
if ((curRowMode & this.parser.MODE.REQUEST_END) > 0) {
break;
}
// eslint-disable-next-line no-bitwise
if ((curRowMode & this.parser.MODE.MULTI_DOC_CUR_DOC_END) > 0) {
break;
}
// eslint-disable-next-line no-bitwise
if (curLineNumber !== pos.lineNumber && (curRowMode & this.parser.MODE.REQUEST_START) > 0) {
break;
}
}
const column =
(this.coreEditor.getLineValue(curLineNumber) || '').length +
1; /* Range goes to 1 after last char */
return {
lineNumber: curLineNumber,
column,
};
};
highlightCurrentRequestsAndUpdateActionBar = _.debounce(async () => {
await this.coreEditor.waitForLatestTokens();
const expandedRange = await this.expandRangeToRequestEdges();
if (expandedRange === null && this.currentReqRange === null) {
return;
}
if (
expandedRange !== null &&
this.currentReqRange !== null &&
expandedRange.start.lineNumber === this.currentReqRange.start.lineNumber &&
expandedRange.end.lineNumber === this.currentReqRange.end.lineNumber
) {
// same request, now see if we are on the first line and update the action bar
const cursorLineNumber = this.coreEditor.getCurrentPosition().lineNumber;
if (cursorLineNumber === this.currentReqRange.start.lineNumber) {
this.updateActionsBar();
}
return; // nothing to do..
}
if (this.currentReqRange) {
this.coreEditor.removeMarker(this.currentReqRange.markerRef);
}
this.currentReqRange = expandedRange as any;
if (this.currentReqRange) {
this.currentReqRange.markerRef = this.coreEditor.addMarker(this.currentReqRange);
}
this.updateActionsBar();
}, 25);
getRequestsAsCURL = async (elasticsearchBaseUrl: string, range?: Range): Promise<string> => {
const requests = await this.getRequestsInRange(range, true);
const result = _.map(requests, (req) => {
if (typeof req === 'string') {
// no request block
return req;
}
const esPath = req.url;
const esMethod = req.method;
const esData = req.data;
// this is the first url defined in elasticsearch.hosts
const url = es.constructESUrl(elasticsearchBaseUrl, esPath);
let ret = 'curl -X' + esMethod + ' "' + url + '"';
if (esData && esData.length) {
ret += " -H 'Content-Type: application/json' -d'\n";
const dataAsString = collapseLiteralStrings(esData.join('\n'));
// We escape single quoted strings that that are wrapped in single quoted strings
ret += dataAsString.replace(/'/g, "'\\''");
if (esData.length > 1) {
ret += '\n';
} // end with a new line
ret += "'";
}
return ret;
});
return result.join('\n');
};
updateActionsBar = () => this.coreEditor.legacyUpdateUI(this.currentReqRange);
getCoreEditor() {
return this.coreEditor;
}
}

View File

@ -0,0 +1,30 @@
import { TextObject } from './text_object';
export interface IdObject {
id: string;
}
export interface ObjectStorage<O extends IdObject> {
/**
* Creates a new object in the underlying persistance layer.
*
* @remarks Does not accept an ID, a new ID is generated and returned with the newly created object.
*/
create(obj: Omit<O, 'id'>): Promise<O>;
/**
* This method should update specific object in the persistance layer.
*/
update(obj: O): Promise<void>;
/**
* A function that will return all of the objects in the persistance layer.
*
* @remarks Unless an error is thrown this function should always return an array (empty if there are not objects present).
*/
findAll(): Promise<O[]>;
}
export interface ObjectStorageClient {
text: ObjectStorage<TextObject>;
}

View File

@ -0,0 +1,61 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export const textObjectTypeName = 'text-object';
/**
* Describes the shape of persisted objects that contain information about the current text in the
* text editor.
*/
export interface TextObject {
/**
* An ID that uniquely identifies this object.
*/
id: string;
/**
* UNIX timestamp of when the object was created.
*/
createdAt: number;
/**
* UNIX timestamp of when the object was last updated.
*/
updatedAt: number;
/**
* Text value input by the user.
*
* Used to re-populate a text editor buffer.
*/
text: string;
}

View File

@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { Position } from './position';
export interface Token {
/**
* The value of the token.
*
* Can be an empty string.
*/
value: string;
/**
* The type of the token. E.g., "whitespace". All of the types are
* enumerated by the token lexer.
*/
type: string;
/**
* The position of the first character of the token.
*/
position: Position;
}

View File

@ -0,0 +1,179 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { Token } from './token';
import { Position } from './position';
import { TokensProvider } from './tokens_provider';
function isColumnInTokenRange(column: number, token: Token) {
if (column < token.position.column) {
return false;
}
return column <= token.position.column + token.value.length;
}
export class TokenIterator {
private currentTokenIdx = -1;
private currentPosition: Position = { lineNumber: -1, column: -1 };
private tokensLineCache: Token[];
constructor(private readonly provider: TokensProvider, startPosition: Position) {
this.tokensLineCache = this.provider.getTokens(startPosition.lineNumber) || [];
const tokenIdx = this.tokensLineCache.findIndex((token) =>
isColumnInTokenRange(startPosition.column, token)
);
if (tokenIdx > -1) {
this.updatePosition({
tokenIdx,
position: this.tokensLineCache[tokenIdx].position,
});
} else {
this.updatePosition({ tokenIdx: -1, position: startPosition });
}
}
private updateLineTokens(tokens: Token[]) {
this.tokensLineCache = tokens;
}
private updatePosition(info: { tokenIdx: number; position: Position }) {
this.currentTokenIdx = info.tokenIdx;
this.currentPosition = { ...info.position };
}
private step(direction: 1 | -1): Token | null {
const nextIdx = this.currentTokenIdx + direction;
let nextToken = this.tokensLineCache[nextIdx];
// Check current row
if (nextToken) {
this.updatePosition({
tokenIdx: nextIdx,
position: nextToken.position,
});
return nextToken;
}
// Check next line
const nextLineNumber = this.currentPosition.lineNumber + direction;
const nextLineTokens = this.provider.getTokens(nextLineNumber);
if (nextLineTokens) {
this.updateLineTokens(nextLineTokens);
let idx: number;
if (direction > 0) {
nextToken = nextLineTokens[0];
idx = 0;
} else {
nextToken = nextLineTokens[nextLineTokens.length - 1];
idx = nextToken ? nextLineTokens.length - 1 : 0;
}
const nextPosition = nextToken
? nextToken.position
: { column: 1, lineNumber: nextLineNumber };
this.updatePosition({ tokenIdx: idx, position: nextPosition });
return nextToken || null;
}
// We have reached the beginning or the end
return null;
}
/**
* Report the token under the iterator's internal cursor.
*/
public getCurrentToken(): Token | null {
return this.tokensLineCache[this.currentTokenIdx] || null;
}
/**
* Return the current position in the document.
*
* This will correspond to the position of a token.
*
* Note: this method may not be that useful given {@link getCurrentToken}.
*/
public getCurrentPosition(): Position {
return this.currentPosition;
}
/**
* Go to the previous token in the document.
*
* Stepping to the previous token can return null under the following conditions:
*
* 1. We are at the beginning of the document.
* 2. The preceding line is empty - no tokens.
* 3. We are in an empty document - not text, so no tokens.
*/
public stepBackward(): Token | null {
return this.step(-1);
}
/**
* See documentation for {@link stepBackward}.
*
* Steps forward.
*/
public stepForward(): Token | null {
return this.step(1);
}
/**
* Get the line number of the current token.
*
* Can be considered a convenience method for:
*
* ```ts
* it.getCurrentToken().lineNumber;
* ```
*/
public getCurrentTokenLineNumber(): number | null {
const currentToken = this.getCurrentToken();
if (currentToken) {
return currentToken.position.lineNumber;
}
return null;
}
/**
* See documentation for {@link getCurrentTokenLineNumber}.
*
* Substitutes `column` for `lineNumber`.
*/
public getCurrentTokenColumn(): number | null {
const currentToken = this.getCurrentToken();
if (currentToken) {
return currentToken.position.column;
}
return null;
}
}

View File

@ -0,0 +1,22 @@
import { Position } from './position';
import { Token } from './token';
export interface TokensProvider {
/**
* Special values in this interface are `null` and an empty array `[]`.
* - `null` means that we are outside of the document range (i.e., have requested tokens for a non-existent line).
* - An empty array means that we are on an empty line.
*/
getTokens(lineNumber: number): Token[] | null;
/**
* Get the token at the specified position.
*
* The token "at" the position is considered to the token directly preceding
* the indicated cursor position.
*
* Returns null if there is not a token that meets this criteria or if the position is outside
* of the document range.
*/
getTokenAt(pos: Position): Token | null;
}

View File

@ -0,0 +1,13 @@
import { CoreEditor } from '../entities/core_editor';
import { Position} from '../entities/position';
import { TokenIterator } from '../entities/token_iterator';
interface Dependencies {
position: Position;
editor: CoreEditor;
}
export function createTokenIterator({ editor, position }: Dependencies) {
const provider = editor.getTokenProvider();
return new TokenIterator(provider, position);
}

View File

@ -0,0 +1,46 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { History } from '../../services';
import { ObjectStorageClient } from '../../entities/storage';
export interface Dependencies {
history: History;
objectStorageClient: ObjectStorageClient;
}
/**
* Once off migration to new text object data structure
*/
export async function migrateToTextObjects({
history,
objectStorageClient: objectStorageClient,
}: Dependencies): Promise<void> {
const legacyTextContent = history.getLegacySavedEditorState();
if (!legacyTextContent) return;
await objectStorageClient.text.create({
createdAt: Date.now(),
updatedAt: Date.now(),
text: legacyTextContent.content,
});
history.deleteLegacySavedEditorState();
}

View File

@ -0,0 +1,19 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
export { useDataInit } from './use_data_init';

View File

@ -0,0 +1,76 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useCallback, useEffect, useState } from 'react';
import { migrateToTextObjects } from './data_migration';
import { useEditorActionContext, useServicesContext } from '../../contexts';
export const useDataInit = () => {
const [error, setError] = useState<Error | null>(null);
const [done, setDone] = useState<boolean>(false);
const [retryToken, setRetryToken] = useState<object>({});
const retry = useCallback(() => {
setRetryToken({});
setDone(false);
setError(null);
}, []);
const {
services: { objectStorageClient, history },
} = useServicesContext();
const dispatch = useEditorActionContext();
useEffect(() => {
const load = async () => {
try {
await migrateToTextObjects({ history, objectStorageClient });
const results = await objectStorageClient.text.findAll();
if (!results.length) {
const newObject = await objectStorageClient.text.create({
createdAt: Date.now(),
updatedAt: Date.now(),
text: '',
});
dispatch({ type: 'setCurrentTextObject', payload: newObject });
} else {
dispatch({
type: 'setCurrentTextObject',
// For backwards compatibility, we sort here according to date created to
// always take the first item created.
payload: results.sort((a, b) => a.createdAt - b.createdAt)[0],
});
}
} catch (e) {
setError(e);
} finally {
setDone(true);
}
};
load();
}, [dispatch, objectStorageClient, history, retryToken]);
return {
error,
done,
retry,
};
};

View File

@ -0,0 +1,50 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { useRef, useCallback } from 'react';
import { throttle } from 'lodash';
import { useEditorReadContext, useServicesContext } from '../contexts';
const WAIT_MS = 500;
export const useSaveCurrentTextObject = () => {
const promiseChainRef = useRef(Promise.resolve());
const {
services: { objectStorageClient },
} = useServicesContext();
const { currentTextObject } = useEditorReadContext();
/* eslint-disable-next-line react-hooks/exhaustive-deps */
return useCallback(
throttle(
(text: string) => {
const { current: promise } = promiseChainRef;
if (!currentTextObject) return;
promise.finally(() =>
objectStorageClient.text.update({ ...currentTextObject, text, updatedAt: Date.now() })
);
},
WAIT_MS,
{ trailing: true }
),
[objectStorageClient, currentTextObject]
);
};

View File

@ -0,0 +1,122 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { useCallback } from 'react';
import { sendRequestToES } from './send_request_to_es';
import { instance as registry } from '../../contexts/editor_context/editor_registry';
import { useRequestActionContext } from '../../contexts/request_context';
import { useServicesContext } from '../../contexts/services_context';
import {getCommand} from '../../modules/mappings/mappings';
function buildRawCommonCommandRequest(cmd:any){
const {requests} = cmd._source;
const strReqs = requests.map((req: any)=>{
const {method, path, body} = req;
return `${method} ${path}\n${body}`;
})
return strReqs.join('\n');
}
export const useSendCurrentRequestToES = () => {
const dispatch = useRequestActionContext();
const { services: { history } } = useServicesContext();
return useCallback(async () => {
try {
const editor = registry.getInputEditor();
const requests = await editor.getRequestsInRange();
if (!requests.length) {
console.log('No request selected. Select a request by placing the cursor inside it.');
return;
}
const {url, method} = requests[0];
if(method === 'LOAD'){
const cmd = getCommand(url);
// const curPostion = editor.currentReqRange //(editor.getCoreEditor().getCurrentPosition());
const lineNumber = editor.getCoreEditor().getCurrentPosition().lineNumber;
let crange = await editor.getRequestRange(lineNumber)
const rawRequest = buildRawCommonCommandRequest(cmd)
await editor.getCoreEditor().replaceRange(crange as any, rawRequest);
// await editor.autoIndent();
// editor.getCoreEditor().getContainer().focus();
// crange = await editor.getRequestRange(lineNumber)
// editor.getCoreEditor().moveCursorToPosition({
// ...crange?.end as any,
// // column: editor.getCoreEditor().getLineValue(lineNumber).length + 1,
// });
return;
}
dispatch({ type: 'sendRequest', payload: undefined });
// @ts-ignore
const results = await sendRequestToES({ requests });
// let saveToHistoryError: undefined | Error;
// results.forEach(({ request: { path, method, data } }) => {
// try {
// history.addToHistory(path, method, data);
// } catch (e) {
// // Grab only the first error
// if (!saveToHistoryError) {
// saveToHistoryError = e;
// }
// }
// });
//
// if (saveToHistoryError) {
// console.log('save to history error')
// }
//
dispatch({
type: 'requestSuccess',
payload: {
data: results,
},
});
} catch (e) {
if (e?.response) {
dispatch({
type: 'requestFail',
payload: e,
});
} else {
dispatch({
type: 'requestFail',
payload: undefined,
});
}
}
}, [dispatch, history]);
};

View File

@ -0,0 +1,149 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { send } from '../../modules/es';
import { EsRequestArgs, ESRequestResult } from '../../entities/es_request';
import { collapseLiteralStrings } from '../../utils/json_xjson_translation_tools';
import { extractWarningMessages } from '../../utils/autocomplete';
let CURRENT_REQ_ID = 0;
export function sendRequestToES(args: EsRequestArgs): Promise<ESRequestResult[]> {
const requests = args.requests.slice();
return new Promise((resolve, reject) => {
const reqId = ++CURRENT_REQ_ID;
const results: ESRequestResult[] = [];
if (reqId !== CURRENT_REQ_ID) {
return;
}
if (requests.length === 0) {
return;
}
const isMultiRequest = requests.length > 1;
const sendNextRequest = () => {
if (reqId !== CURRENT_REQ_ID) {
resolve(results);
return;
}
if (requests.length === 0) {
resolve(results);
return;
}
const req = requests.shift()!;
const esPath = req.url;
const esMethod = req.method;
let esData = collapseLiteralStrings(req.data.join('\n'));
if (esData) {
esData += '\n';
} // append a new line for bulk requests.
const startTime = Date.now();
send(esMethod, esPath, esData).always(
(dataOrjqXHR, textStatus: string, jqXhrORerrorThrown) => {
if (reqId !== CURRENT_REQ_ID) {
return;
}
const xhr = dataOrjqXHR.promise ? dataOrjqXHR : jqXhrORerrorThrown;
const isSuccess =
typeof xhr.status === 'number' &&
// Things like DELETE index where the index is not there are OK.
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 404);
if (isSuccess) {
let value = xhr.responseText;
const warnings = xhr.getResponseHeader('warning');
if (warnings) {
const warningMessages = extractWarningMessages(warnings);
value = warningMessages.join('\n') + '\n' + value;
}
if (isMultiRequest) {
value = '# ' + req.method + ' ' + req.url + '\n' + value;
}
results.push({
response: {
timeMs: Date.now() - startTime,
statusCode: xhr.status,
statusText: xhr.statusText,
contentType: xhr.getResponseHeader('Content-Type'),
value,
},
request: {
data: esData,
method: esMethod,
path: esPath,
},
});
// single request terminate via sendNextRequest as well
sendNextRequest();
} else {
let value;
let contentType: string;
if (xhr.responseText) {
value = xhr.responseText; // ES error should be shown
contentType = xhr.getResponseHeader('Content-Type');
} else {
value = 'Request failed to get to the server (status code: ' + xhr.status + ')';
contentType = 'text/plain';
}
if (isMultiRequest) {
value = '# ' + req.method + ' ' + req.url + '\n' + value;
}
reject({
response: {
value,
contentType,
timeMs: Date.now() - startTime,
statusCode: xhr.status,
statusText: xhr.statusText,
},
request: {
data: esData,
method: esMethod,
path: esPath,
},
});
}
}
);
};
sendNextRequest();
});
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { useCallback } from 'react';
import { useEditorActionContext } from '../contexts/editor_context';
import { instance as registry } from '../contexts/editor_context/editor_registry';
import { SenseEditor } from '../entities/sense_editor';
export const useSetInputEditor = () => {
const dispatch = useEditorActionContext();
return useCallback(
(editor: SenseEditor) => {
dispatch({ type: 'setInputEditor', payload: editor });
registry.setInputEditor(editor);
},
[dispatch]
);
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,318 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { WalkingState, walkTokenPath, wrapComponentWithDefaults } from './engine';
import {
ConstantComponent,
SharedComponent,
ObjectComponent,
ConditionalProxy,
GlobalOnlyComponent,
} from './components';
function CompilingContext(endpointId, parametrizedComponentFactories) {
this.parametrizedComponentFactories = parametrizedComponentFactories;
this.endpointId = endpointId;
}
/**
* An object to resolve scope links (syntax endpoint.path1.path2)
* @param link the link either string (endpoint.path1.path2, or .path1.path2) or a function (context,editor)
* which returns a description to be compiled
* @constructor
* @param compilingContext
*
*
* For this to work we expect the context to include a method context.endpointComponentResolver(endpoint)
* which should return the top level components for the given endpoint
*/
function resolvePathToComponents(tokenPath, context, editor, components) {
const walkStates = walkTokenPath(
tokenPath,
[new WalkingState('ROOT', components, [])],
context,
editor
);
const result = [].concat.apply([], _.map(walkStates, 'components'));
return result;
}
class ScopeResolver extends SharedComponent {
constructor(link, compilingContext) {
super('__scope_link');
if (_.isString(link) && link[0] === '.') {
// relative link, inject current endpoint
if (link === '.') {
link = compilingContext.endpointId;
} else {
link = compilingContext.endpointId + link;
}
}
this.link = link;
this.compilingContext = compilingContext;
}
resolveLinkToComponents(context, editor) {
if (_.isFunction(this.link)) {
const desc = this.link(context, editor);
return compileDescription(desc, this.compilingContext);
}
if (!_.isString(this.link)) {
throw new Error('unsupported link format', this.link);
}
let path = this.link.replace(/\./g, '{').split(/(\{)/);
const endpoint = path[0];
let components;
try {
if (endpoint === 'GLOBAL') {
// global rules need an extra indirection
if (path.length < 3) {
throw new Error('missing term in global link: ' + this.link);
}
const term = path[2];
components = context.globalComponentResolver(term);
path = path.slice(3);
} else {
path = path.slice(1);
components = context.endpointComponentResolver(endpoint);
}
} catch (e) {
throw new Error('failed to resolve link [' + this.link + ']: ' + e);
}
return resolvePathToComponents(path, context, editor, components);
}
getTerms(context, editor) {
const options = [];
const components = this.resolveLinkToComponents(context, editor);
_.each(components, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
return options;
}
match(token, context, editor) {
const result = {
next: [],
};
const components = this.resolveLinkToComponents(context, editor);
_.each(components, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
return result;
}
}
function getTemplate(description) {
if (description.__template) {
if (description.__raw && _.isString(description.__template)) {
return {
// This is a special secret attribute that gets passed through to indicate that
// the raw value should be passed through to the console without JSON.stringifying it
// first.
//
// Primary use case is to allow __templates to contain extended JSON special values like
// triple quotes.
__raw: true,
value: description.__template,
};
}
return description.__template;
} else if (description.__one_of) {
return getTemplate(description.__one_of[0]);
} else if (description.__any_of) {
return [];
} else if (description.__scope_link) {
// assume an object for now.
return {};
} else if (Array.isArray(description)) {
if (description.length === 1) {
if (_.isObject(description[0])) {
// shortcut to save typing
const innerTemplate = getTemplate(description[0]);
return innerTemplate != null ? [innerTemplate] : [];
}
}
return [];
} else if (_.isObject(description)) {
return {};
} else if (_.isString(description) && !/^\{.*\}$/.test(description)) {
return description;
} else {
return description;
}
}
function getOptions(description) {
const options = {};
const template = getTemplate(description);
if (!_.isUndefined(template)) {
options.template = template;
}
return options;
}
/**
* @param description a json dict describing the endpoint
* @param compilingContext
*/
function compileDescription(description, compilingContext) {
if (Array.isArray(description)) {
return [compileList(description, compilingContext)];
} else if (_.isObject(description)) {
// test for objects list as arrays are also objects
if (description.__scope_link) {
return [new ScopeResolver(description.__scope_link, compilingContext)];
}
if (description.__any_of) {
return [compileList(description.__any_of, compilingContext)];
}
if (description.__one_of) {
return _.flatten(
_.map(description.__one_of, function (d) {
return compileDescription(d, compilingContext);
})
);
}
const obj = compileObject(description, compilingContext);
if (description.__condition) {
return [compileCondition(description.__condition, obj, compilingContext)];
} else {
return [obj];
}
} else if (_.isString(description) && /^\{.*\}$/.test(description)) {
return [compileParametrizedValue(description, compilingContext)];
} else {
return [new ConstantComponent(description)];
}
}
function compileParametrizedValue(value, compilingContext, template) {
value = value.substr(1, value.length - 2).toLowerCase();
let component = compilingContext.parametrizedComponentFactories.getComponent(value, true);
if (!component) {
throw new Error("no factory found for '" + value + "'");
}
component = component(value, null, template);
if (!_.isUndefined(template)) {
component = wrapComponentWithDefaults(component, { template: template });
}
return component;
}
function compileObject(objDescription, compilingContext) {
const objectC = new ConstantComponent('{');
const constants = [];
const patterns = [];
_.each(objDescription, function (desc, key) {
if (key.indexOf('__') === 0) {
// meta key
return;
}
const options = getOptions(desc);
let component;
if (/^\{.*\}$/.test(key)) {
component = compileParametrizedValue(key, compilingContext, options.template);
patterns.push(component);
} else if (key === '*') {
component = new SharedComponent(key);
patterns.push(component);
} else {
options.name = key;
component = new ConstantComponent(key, null, [options]);
constants.push(component);
}
_.map(compileDescription(desc, compilingContext), function (subComponent) {
component.addComponent(subComponent);
});
});
objectC.addComponent(new ObjectComponent('inner', constants, patterns));
return objectC;
}
function compileList(listRule, compilingContext) {
const listC = new ConstantComponent('[');
_.each(listRule, function (desc) {
_.each(compileDescription(desc, compilingContext), function (component) {
listC.addComponent(component);
});
});
return listC;
}
/** takes a compiled object and wraps in a {@link ConditionalProxy }*/
function compileCondition(description, compiledObject) {
if (description.lines_regex) {
return new ConditionalProxy(function (context, editor) {
const lines = editor
.getLines(context.requestStartRow, editor.getCurrentPosition().lineNumber)
.join('\n');
return new RegExp(description.lines_regex, 'm').test(lines);
}, compiledObject);
} else {
throw 'unknown condition type - got: ' + JSON.stringify(description);
}
}
// a list of component that match anything but give auto complete suggestions based on global API entries.
export function globalsOnlyAutocompleteComponents() {
return [new GlobalOnlyComponent('__global__')];
}
/**
* @param endpointId id of the endpoint being compiled.
* @param description a json dict describing the endpoint
* @param endpointComponentResolver a function (endpoint,context,editor) which should resolve an endpoint
* to it's list of compiled components.
* @param parametrizedComponentFactories a dict of the following structure
* that will be used as a fall back for pattern keys (i.e.: {type} ,resolved without the $s)
* {
* TYPE: function (part, parent, endpoint) {
* return new SharedComponent(part, parent)
* }
* }
*/
export function compileBodyDescription(endpointId, description, parametrizedComponentFactories) {
return compileDescription(
description,
new CompilingContext(endpointId, parametrizedComponentFactories)
);
}

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export const URL_PATH_END_MARKER = '__url_path_end__';
export class AcceptEndpointComponent extends SharedComponent {
constructor(endpoint, parent) {
super(endpoint.id, parent);
this.endpoint = endpoint;
}
match(token, context, editor) {
if (token !== URL_PATH_END_MARKER) {
return null;
}
if (this.endpoint.methods && -1 === _.indexOf(this.endpoint.methods, context.method)) {
return null;
}
const r = super.match(token, context, editor);
r.context_values = r.context_values || {};
r.context_values.endpoint = this.endpoint;
if (_.isNumber(this.endpoint.priority)) {
r.priority = this.endpoint.priority;
}
return r;
}
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export class AutocompleteComponent {
constructor(name) {
this.name = name;
}
/** called to get the possible suggestions for tokens, when this object is at the end of
* the resolving chain (and thus can suggest possible continuation paths)
*/
getTerms() {
return [];
}
/*
if the current matcher matches this term, this method should return an object with the following keys
{
context_values: {
values extract from term that should be added to the context
}
next: AutocompleteComponent(s) to use next
priority: optional priority to solve collisions between multiple paths. Min value is used across entire chain
}
*/
match() {
return {
next: this.next,
};
}
}

View File

@ -0,0 +1,35 @@
// import { ListComponent } from './list_component';
// export class CommandAutoCompleteComponent extends ListComponent {
// constructor(name, list, parent, multiValued) {
// super(name, list, parent, multiValued);
// }
// }
import _ from 'lodash';
import { getCommands } from '../../mappings/mappings';
import { ListComponent } from './list_component';
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
export class CommandAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, getCommands, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
}
getDefaultTermMeta() {
return 'command';
}
getContextKey() {
return 'command';
}
}

View File

@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { SharedComponent } from './shared_component';
export class ConditionalProxy extends SharedComponent {
constructor(predicate, delegate) {
super('__condition');
this.predicate = predicate;
this.delegate = delegate;
}
getTerms(context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.getTerms(context, editor);
} else {
return null;
}
}
match(token, context, editor) {
if (this.predicate(context, editor)) {
return this.delegate.match(token, context, editor);
} else {
return false;
}
}
}

View File

@ -0,0 +1,62 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export class ConstantComponent extends SharedComponent {
constructor(name, parent, options) {
super(name, parent);
if (_.isString(options)) {
options = [options];
}
this.options = options || [name];
}
getTerms() {
return this.options;
}
addOption(options) {
if (!Array.isArray(options)) {
options = [options];
}
[].push.apply(this.options, options);
this.options = _.uniq(this.options);
}
match(token, context, editor) {
if (token !== this.name) {
return null;
}
return super.match(token, context, editor);
}
}

View File

@ -0,0 +1,64 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { getFields } from '../../mappings/mappings';
import { ListComponent } from './list_component';
function FieldGenerator(context) {
return _.map(getFields(context.indices, context.types), function (field) {
return { name: field.name, meta: field.type };
});
}
export class FieldAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, FieldGenerator, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, function (token) {
return token.match(/[^\w.?*]/);
});
}
getDefaultTermMeta() {
return 'field';
}
getContextKey() {
return 'fields';
}
}

View File

@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { ConstantComponent } from './constant_component';
// @ts-ignore
export class FullRequestComponent extends ConstantComponent {
private readonly name: string;
constructor(name: string, parent: unknown, private readonly template: string) {
super(name, parent);
this.name = name;
}
getTerms() {
return [{ name: this.name, snippet: this.template }];
}
}

View File

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { SharedComponent } from './shared_component';
export class GlobalOnlyComponent extends SharedComponent {
getTerms() {
return null;
}
match(token, context) {
const result = {
next: [],
};
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
}
if (result.next.length) {
return result;
}
// just loop back to us
result.next = [this];
return result;
}
}

View File

@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
export class IdAutocompleteComponent extends SharedComponent {
constructor(name, parent, multi) {
super(name, parent);
this.multi_match = multi;
}
match(token, context, editor) {
if (!token) {
return null;
}
if (!this.multi_match && Array.isArray(token)) {
return null;
}
token = Array.isArray(token) ? token : [token];
if (
_.find(token, function (t) {
return t.match(/[\/,]/);
})
) {
return null;
}
const r = super.match(token, context, editor);
r.context_values = r.context_values || {};
r.context_values.id = token;
return r;
}
}

View File

@ -0,0 +1,48 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export { AutocompleteComponent } from './autocomplete_component';
export { SharedComponent } from './shared_component';
export { ConstantComponent } from './constant_component';
export { ListComponent } from './list_component';
export { SimpleParamComponent } from './simple_param_component';
export { ConditionalProxy } from './conditional_proxy';
export { GlobalOnlyComponent } from './global_only_component';
export { ObjectComponent } from './object_component';
export { AcceptEndpointComponent, URL_PATH_END_MARKER } from './accept_endpoint_component';
export { UrlPatternMatcher } from './url_pattern_matcher';
export { IndexAutocompleteComponent } from './index_autocomplete_component';
export { FieldAutocompleteComponent } from './field_autocomplete_component';
export { TypeAutocompleteComponent } from './type_autocomplete_component';
export { IdAutocompleteComponent } from './id_autocomplete_component';
export { TemplateAutocompleteComponent } from './template_autocomplete_component';
export { UsernameAutocompleteComponent } from './username_autocomplete_component';

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { getIndices } from '../../mappings/mappings';
import { ListComponent } from './list_component';
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
export class IndexAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, getIndices, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
}
getDefaultTermMeta() {
return 'index';
}
getContextKey() {
return 'indices';
}
}

View File

@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { SharedComponent } from './shared_component';
/** A component that suggests one of the give options, but accepts anything */
export class ListComponent extends SharedComponent {
constructor(name, list, parent, multiValued, allowNonValidValues) {
super(name, parent);
this.listGenerator = Array.isArray(list)
? function () {
return list;
}
: list;
this.multiValued = _.isUndefined(multiValued) ? true : multiValued;
this.allowNonValidValues = _.isUndefined(allowNonValidValues) ? false : allowNonValidValues;
}
getTerms(context, editor) {
if (!this.multiValued && context.otherTokenValues) {
// already have a value -> no suggestions
return [];
}
let alreadySet = context.otherTokenValues || [];
if (_.isString(alreadySet)) {
alreadySet = [alreadySet];
}
let ret = _.difference(this.listGenerator(context, editor), alreadySet);
if (this.getDefaultTermMeta()) {
const meta = this.getDefaultTermMeta();
ret = _.map(ret, function (term) {
if (_.isString(term)) {
term = { name: term };
}
return _.defaults(term, { meta: meta });
});
}
return ret;
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
// verify we have all tokens
const list = this.listGenerator();
const notFound = _.some(tokens, function (token) {
return list.indexOf(token) === -1;
});
if (notFound) {
return false;
}
return true;
}
getContextKey() {
return this.name;
}
getDefaultTermMeta() {
return this.name;
}
match(token, context, editor) {
if (!Array.isArray(token)) {
token = [token];
}
if (!this.allowNonValidValues && !this.validateTokens(token, context, editor)) {
return null;
}
const result = super.match(token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.getContextKey()] = token;
return result;
}
}

View File

@ -0,0 +1,86 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { SharedComponent } from './index';
/**
* @param constants list of components that represent constant keys
* @param patternsAndWildCards list of components that represent patterns and should be matched only if
* there is no constant matches
*/
export class ObjectComponent extends SharedComponent {
constructor(name, constants, patternsAndWildCards) {
super(name);
this.constants = constants;
this.patternsAndWildCards = patternsAndWildCards;
}
getTerms(context, editor) {
const options = [];
_.each(this.constants, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
_.each(this.patternsAndWildCards, function (component) {
options.push.apply(options, component.getTerms(context, editor));
});
return options;
}
match(token, context, editor) {
const result = {
next: [],
};
_.each(this.constants, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
// try to link to GLOBAL rules
const globalRules = context.globalComponentResolver(token, false);
if (globalRules) {
result.next.push.apply(result.next, globalRules);
}
if (result.next.length) {
return result;
}
_.each(this.patternsAndWildCards, function (component) {
const componentResult = component.match(token, context, editor);
if (componentResult && componentResult.next) {
result.next.push.apply(result.next, componentResult.next);
}
});
return result;
}
}

View File

@ -0,0 +1,56 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { AutocompleteComponent } from './autocomplete_component';
export class SharedComponent extends AutocompleteComponent {
constructor(name, parent) {
super(name);
this._nextDict = {};
if (parent) {
parent.addComponent(this);
}
// for debugging purposes
this._parent = parent;
}
/* return the first component with a given name */
getComponent(name) {
return (this._nextDict[name] || [undefined])[0];
}
addComponent(component) {
const current = this._nextDict[component.name] || [];
current.push(component);
this._nextDict[component.name] = current;
this.next = [].concat.apply([], _.values(this._nextDict));
}
}

View File

@ -0,0 +1,44 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { SharedComponent } from './shared_component';
export class SimpleParamComponent extends SharedComponent {
constructor(name, parent) {
super(name, parent);
}
match(token, context, editor) {
const result = super.match(token, context, editor);
result.context_values = result.context_values || {};
result.context_values[this.name] = token;
return result;
}
}

View File

@ -0,0 +1,43 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { getTemplates } from '../../mappings/mappings';
import { ListComponent } from './list_component';
export class TemplateAutocompleteComponent extends ListComponent {
constructor(name, parent) {
super(name, getTemplates, parent, true, true);
}
getContextKey() {
return 'template';
}
}

View File

@ -0,0 +1,61 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { ListComponent } from './list_component';
import { getTypes } from '../../mappings/mappings';
function TypeGenerator(context) {
return getTypes(context.indices);
}
function nonValidIndexType(token) {
return !(token === '_all' || token[0] !== '_');
}
export class TypeAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, TypeGenerator, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidIndexType);
}
getDefaultTermMeta() {
return 'type';
}
getContextKey() {
return 'types';
}
}

View File

@ -0,0 +1,148 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import {
SharedComponent,
ConstantComponent,
AcceptEndpointComponent,
ListComponent,
SimpleParamComponent,
} from './index';
import { FullRequestComponent } from './full_request_component';
/**
* @param parametrizedComponentFactories a dict of the following structure
* that will be used as a fall back for pattern parameters (i.e.: {indices})
* {
* indices: function (part, parent) {
* return new SharedComponent(part, parent)
* }
* }
* @constructor
*/
export class UrlPatternMatcher {
// This is not really a component, just a handy container to make iteration logic simpler
constructor(parametrizedComponentFactories) {
// We'll group endpoints by the methods which are attached to them,
//to avoid suggesting endpoints that are incompatible with the
//method that the user has entered.
['HEAD', 'GET', 'PUT', 'POST', 'DELETE'].forEach((method) => {
this[method] = {
rootComponent: new SharedComponent('ROOT'),
parametrizedComponentFactories: parametrizedComponentFactories || {
getComponent: () => {},
},
};
});
}
addEndpoint(pattern, endpoint) {
endpoint.methods.forEach((method) => {
let c;
let activeComponent = this[method].rootComponent;
if (endpoint.template) {
new FullRequestComponent(pattern + '[body]', activeComponent, endpoint.template);
}
const endpointComponents = endpoint.url_components || {};
const partList = pattern.split('/');
_.each(partList, (part, partIndex) => {
if (part.search(/^{.+}$/) >= 0) {
part = part.substr(1, part.length - 2);
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
return;
}
// a new path, resolve.
if ((c = endpointComponents[part])) {
// endpoint specific. Support list
if (Array.isArray(c)) {
c = new ListComponent(part, c, activeComponent);
} else if (_.isObject(c) && c.type === 'list') {
c = new ListComponent(
part,
c.list,
activeComponent,
c.multiValued,
c.allow_non_valid
);
} else {
console.warn('incorrectly configured url component ', part, ' in endpoint', endpoint);
c = new SharedComponent(part);
}
} else if ((c = this[method].parametrizedComponentFactories.getComponent(part))) {
// c is a f
c = c(part, activeComponent);
} else {
// just accept whatever with not suggestions
c = new SimpleParamComponent(part, activeComponent);
}
activeComponent = c;
} else {
// not pattern
let lookAhead = part;
let s;
for (partIndex++; partIndex < partList.length; partIndex++) {
s = partList[partIndex];
if (s.indexOf('{') >= 0) {
break;
}
lookAhead += '/' + s;
}
if (activeComponent.getComponent(part)) {
// we already have something for this, reuse
activeComponent = activeComponent.getComponent(part);
activeComponent.addOption(lookAhead);
} else {
c = new ConstantComponent(part, activeComponent, lookAhead);
activeComponent = c;
}
}
});
// mark end of endpoint path
new AcceptEndpointComponent(endpoint, activeComponent);
});
}
getTopLevelComponents = function (method) {
const methodRoot = this[method];
if (!methodRoot) {
return [];
}
return methodRoot.rootComponent.next;
};
}

View File

@ -0,0 +1,57 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { getIndices } from '../../mappings/mappings';
import { ListComponent } from './list_component';
function nonValidUsernameType(token) {
return token[0] === '_';
}
export class UsernameAutocompleteComponent extends ListComponent {
constructor(name, parent, multiValued) {
super(name, getIndices, parent, multiValued);
}
validateTokens(tokens) {
if (!this.multiValued && tokens.length > 1) {
return false;
}
return !_.find(tokens, nonValidUsernameType);
}
getDefaultTermMeta() {
return 'username';
}
getContextKey() {
return 'username';
}
}

View File

@ -0,0 +1,182 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
export function wrapComponentWithDefaults(component, defaults) {
const originalGetTerms = component.getTerms;
component.getTerms = function (context, editor) {
let result = originalGetTerms.call(component, context, editor);
if (!result) {
return result;
}
result = _.map(result, (term) => {
if (!_.isObject(term)) {
term = { name: term };
}
return _.defaults(term, defaults);
});
return result;
};
return component;
}
const tracer = function () {
if (window.engine_trace) {
console.log.call(console, ...arguments);
}
};
function passThroughContext(context, extensionList) {
function PTC() {}
PTC.prototype = context;
const result = new PTC();
if (extensionList) {
extensionList.unshift(result);
_.assign.apply(_, extensionList);
extensionList.shift();
}
return result;
}
export function WalkingState(parentName, components, contextExtensionList, depth, priority) {
this.parentName = parentName;
this.components = components;
this.contextExtensionList = contextExtensionList;
this.depth = depth || 0;
this.priority = priority;
}
export function walkTokenPath(tokenPath, walkingStates, context, editor) {
if (!tokenPath || tokenPath.length === 0) {
return walkingStates;
}
const token = tokenPath[0];
const nextWalkingStates = [];
_.each(walkingStates, function (ws) {
const contextForState = passThroughContext(context, ws.contextExtensionList);
_.each(ws.components, function (component) {
const result = component.match(token, contextForState, editor);
if (result && !_.isEmpty(result)) {
let next;
let extensionList;
if (result.next && !Array.isArray(result.next)) {
next = [result.next];
} else {
next = result.next;
}
if (result.context_values) {
extensionList = [];
[].push.apply(extensionList, ws.contextExtensionList);
extensionList.push(result.context_values);
} else {
extensionList = ws.contextExtensionList;
}
let priority = ws.priority;
if (_.isNumber(result.priority)) {
if (_.isNumber(priority)) {
priority = Math.min(priority, result.priority);
} else {
priority = result.priority;
}
}
nextWalkingStates.push(
new WalkingState(component.name, next, extensionList, ws.depth + 1, priority)
);
}
});
});
if (nextWalkingStates.length === 0) {
// no where to go, still return context variables returned so far..
return _.map(walkingStates, function (ws) {
return new WalkingState(ws.name, [], ws.contextExtensionList);
});
}
return walkTokenPath(tokenPath.slice(1), nextWalkingStates, context, editor);
}
export function populateContext(tokenPath, context, editor, includeAutoComplete, components) {
let walkStates = walkTokenPath(
tokenPath,
[new WalkingState('ROOT', components, [])],
context,
editor
);
if (includeAutoComplete) {
let autoCompleteSet = [];
_.each(walkStates, function (ws) {
const contextForState = passThroughContext(context, ws.contextExtensionList);
_.each(ws.components, function (component) {
_.each(component.getTerms(contextForState, editor), function (term) {
if (!_.isObject(term)) {
term = { name: term };
}
autoCompleteSet.push(term);
});
});
});
autoCompleteSet = _.uniq(autoCompleteSet);
context.autoCompleteSet = autoCompleteSet;
}
// apply what values were set so far to context, selecting the deepest on which sets the context
if (walkStates.length !== 0) {
let wsToUse;
walkStates = _.sortBy(walkStates, function (ws) {
return _.isNumber(ws.priority) ? ws.priority : Number.MAX_VALUE;
});
wsToUse = _.find(walkStates, function (ws) {
return _.isEmpty(ws.components);
});
if (!wsToUse && walkStates.length > 1 && !includeAutoComplete) {
console.info(
"more then one context active for current path, but autocomplete isn't requested",
walkStates
);
}
if (!wsToUse) {
wsToUse = walkStates[0];
}
_.each(wsToUse.contextExtensionList, function (extension) {
_.assign(context, extension);
});
}
}

View File

@ -0,0 +1,59 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import { CoreEditor } from '../../entities/core_editor';
import { Position } from '../../entities/position';
import { getCurrentMethodAndTokenPaths } from './autocomplete';
import RowParser from '../../entities/row_parser';
// @ts-ignore
import { getTopLevelUrlCompleteComponents } from '../kb/kb';
// @ts-ignore
import { populateContext } from './engine';
export function getEndpointFromPosition(editor: CoreEditor, pos: Position, parser: RowParser) {
const lineValue = editor.getLineValue(pos.lineNumber);
const context = {
...getCurrentMethodAndTokenPaths(
editor,
{
column: lineValue.length + 1 /* Go to the very end of the line */,
lineNumber: pos.lineNumber,
},
parser,
true
),
};
const components = getTopLevelUrlCompleteComponents(context.method);
populateContext(context.urlTokenPath, context, editor, true, components);
return context.endpoint;
}

View File

@ -0,0 +1,78 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { ConstantComponent, ListComponent, SharedComponent } from './components';
export class ParamComponent extends ConstantComponent {
constructor(name, parent, description) {
super(name, parent);
this.description = description;
}
getTerms() {
const t = { name: this.name };
if (this.description === '__flag__') {
t.meta = 'flag';
} else {
t.meta = 'param';
t.insertValue = this.name + '=';
}
return [t];
}
}
export class UrlParams {
constructor(description, defaults) {
// This is not really a component, just a handy container to make iteration logic simpler
this.rootComponent = new SharedComponent('ROOT');
if (_.isUndefined(defaults)) {
defaults = {
pretty: '__flag__',
format: ['json', 'yaml'],
filter_path: '',
};
}
description = _.clone(description || {});
_.defaults(description, defaults);
_.each(description, (pDescription, param) => {
const component = new ParamComponent(param, this.rootComponent, pDescription);
if (Array.isArray(pDescription)) {
new ListComponent(param, pDescription, component);
} else if (pDescription === '__flag__') {
new ListComponent(param, ['true', 'false'], component);
}
});
}
getTopLevelComponents() {
return this.rootComponent.next;
}
}

View File

@ -0,0 +1,138 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
// @ts-ignore
import $ from 'jquery';
// @ts-ignore
import { stringify } from 'query-string';
interface SendOptions {
asSystemRequest?: boolean;
}
const esVersion: string[] = [];
export function getVersion() {
return esVersion;
}
export function getContentType(body: unknown) {
if (!body) return;
return 'application/json';
}
export function extractClusterIDFromURL(){
const matchs = location.hash.match(/\/elasticsearch\/(\w+)\/?/);
if(!matchs){
return ''
}
return matchs[1];
}
export function send(
method: string,
path: string,
data: string | object,
{ asSystemRequest }: SendOptions = {}
) {
const wrappedDfd = $.Deferred();
const clusterID = extractClusterIDFromURL();
if(!clusterID){
console.log('can not get clusterid from url');
return;
}
// @ts-ignore
const options: JQuery.AjaxSettings = {
url: `/elasticsearch/${clusterID}/_proxy?` + stringify({ path, method }),
headers: {
'infini-xsrf': 'search-center',
...(asSystemRequest && { 'infini-request': 'true' }),
},
data,
contentType: getContentType(data),
cache: false,
crossDomain: true,
type: 'POST',
dataType: 'json', // disable automatic guessing
};
$.ajax(options).then(
(responseData: any, textStatus: string, jqXHR: unknown) => {
wrappedDfd.resolveWith({}, [responseData, textStatus, jqXHR]);
},
((jqXHR: { status: number; responseText: string }, textStatus: string, errorThrown: Error) => {
if (jqXHR.status === 0) {
jqXHR.responseText =
"\n\nFailed to connect to Console's backend.\nPlease check the server is up and running";
}
wrappedDfd.rejectWith({}, [jqXHR, textStatus, errorThrown]);
}) as any
);
return wrappedDfd;
}
export function queryCommonCommands(title?: string) {
const clusterID = extractClusterIDFromURL();
if(!clusterID){
console.log('can not get clusterid from url');
return;
}
let url = `/elasticsearch/${clusterID}/command/_search`;
if(title){
url +=`?title=${title}`
}
return fetch(url, {
method: 'GET',
})
}
export function constructESUrl(baseUri: string, path: string) {
baseUri = baseUri.replace(/\/+$/, '');
path = path.replace(/^\/+/, '');
return baseUri + '/' + path;
}
export function saveCommonCommand(params: any) {
const clusterID = extractClusterIDFromURL();
if(!clusterID){
console.log('can not get clusterid from url');
return;
}
return fetch(`/elasticsearch/${clusterID}/command`, {
method: 'POST',
body: JSON.stringify(params),
headers:{
'content-type': 'application/json'
}
})
}

View File

@ -0,0 +1,114 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import _ from 'lodash';
import { UrlPatternMatcher } from '../autocomplete/components';
import { UrlParams } from '../autocomplete/url_params';
import {
globalsOnlyAutocompleteComponents,
compileBodyDescription,
} from '../autocomplete/body_completer';
/**
*
* @param urlParametrizedComponentFactories a dictionary of factory functions
* that will be used as fallback for parametrized path part (i.e., {indices} )
* see UrlPatternMatcher
* @constructor
* @param bodyParametrizedComponentFactories same as urlParametrizedComponentFactories but used for body compilation
*/
function Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactories) {
this.globalRules = Object.create(null);
this.endpoints = Object.create(null);
this.urlPatternMatcher = new UrlPatternMatcher(urlParametrizedComponentFactories);
this.globalBodyComponentFactories = bodyParametrizedComponentFactories;
this.name = '';
}
(function (cls) {
cls.addGlobalAutocompleteRules = function (parentNode, rules) {
this.globalRules[parentNode] = compileBodyDescription(
'GLOBAL.' + parentNode,
rules,
this.globalBodyComponentFactories
);
};
cls.getGlobalAutocompleteComponents = function (term, throwOnMissing) {
const result = this.globalRules[term];
if (_.isUndefined(result) && (throwOnMissing || _.isUndefined(throwOnMissing))) {
throw new Error("failed to resolve global components for ['" + term + "']");
}
return result;
};
cls.addEndpointDescription = function (endpoint, description) {
const copiedDescription = {};
_.assign(copiedDescription, description || {});
_.defaults(copiedDescription, {
id: endpoint,
patterns: [endpoint],
methods: ['GET'],
});
_.each(copiedDescription.patterns, (p) => {
this.urlPatternMatcher.addEndpoint(p, copiedDescription);
});
copiedDescription.paramsAutocomplete = new UrlParams(copiedDescription.url_params);
copiedDescription.bodyAutocompleteRootComponents = compileBodyDescription(
copiedDescription.id,
copiedDescription.data_autocomplete_rules,
this.globalBodyComponentFactories
);
this.endpoints[endpoint] = copiedDescription;
};
cls.getEndpointDescriptionByEndpoint = function (endpoint) {
return this.endpoints[endpoint];
};
cls.getTopLevelUrlCompleteComponents = function (method) {
return this.urlPatternMatcher.getTopLevelComponents(method);
};
cls.getUnmatchedEndpointComponents = function () {
return globalsOnlyAutocompleteComponents();
};
cls.clear = function () {
this.endpoints = {};
this.globalRules = {};
};
})(Api.prototype);
export default Api;

View File

@ -0,0 +1,33 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export * from './kb';

View File

@ -0,0 +1,167 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
import {
TypeAutocompleteComponent,
IdAutocompleteComponent,
IndexAutocompleteComponent,
FieldAutocompleteComponent,
ListComponent,
TemplateAutocompleteComponent,
UsernameAutocompleteComponent,
} from '../autocomplete/components';
import { SpecDefinitionsService } from '../../server/services/spec_definitions_service';
import $ from 'jquery';
import _ from 'lodash';
import Api from './api';
let ACTIVE_API = new Api();
const isNotAnIndexName = (name) => name[0] === '_' && name !== '_all';
const idAutocompleteComponentFactory = (name, parent) => {
return new IdAutocompleteComponent(name, parent);
};
const parametrizedComponentFactories = {
getComponent: function (name, parent, provideDefault) {
if (this[name]) {
return this[name];
} else if (provideDefault) {
return idAutocompleteComponentFactory;
}
},
index: function (name, parent) {
if (isNotAnIndexName(name)) return;
return new IndexAutocompleteComponent(name, parent, false);
},
indices: function (name, parent) {
if (isNotAnIndexName(name)) return;
return new IndexAutocompleteComponent(name, parent, true);
},
type: function (name, parent) {
return new TypeAutocompleteComponent(name, parent, false);
},
types: function (name, parent) {
return new TypeAutocompleteComponent(name, parent, true);
},
id: function (name, parent) {
return idAutocompleteComponentFactory(name, parent);
},
transform_id: function (name, parent) {
return idAutocompleteComponentFactory(name, parent);
},
username: function (name, parent) {
return new UsernameAutocompleteComponent(name, parent);
},
user: function (name, parent) {
return new UsernameAutocompleteComponent(name, parent);
},
template: function (name, parent) {
return new TemplateAutocompleteComponent(name, parent);
},
task_id: function (name, parent) {
return idAutocompleteComponentFactory(name, parent);
},
ids: function (name, parent) {
return idAutocompleteComponentFactory(name, parent, true);
},
fields: function (name, parent) {
return new FieldAutocompleteComponent(name, parent, true);
},
field: function (name, parent) {
return new FieldAutocompleteComponent(name, parent, false);
},
nodes: function (name, parent) {
return new ListComponent(
name,
['_local', '_master', 'data:true', 'data:false', 'master:true', 'master:false'],
parent
);
},
node: function (name, parent) {
return new ListComponent(name, [], parent, false);
},
};
export function getUnmatchedEndpointComponents() {
return ACTIVE_API.getUnmatchedEndpointComponents();
}
export function getEndpointDescriptionByEndpoint(endpoint) {
return ACTIVE_API.getEndpointDescriptionByEndpoint(endpoint);
}
export function getEndpointBodyCompleteComponents(endpoint) {
const desc = getEndpointDescriptionByEndpoint(endpoint);
if (!desc) {
throw new Error("failed to resolve endpoint ['" + endpoint + "']");
}
return desc.bodyAutocompleteRootComponents;
}
export function getTopLevelUrlCompleteComponents(method) {
return ACTIVE_API.getTopLevelUrlCompleteComponents(method);
}
export function getGlobalAutocompleteComponents(term, throwOnMissing) {
return ACTIVE_API.getGlobalAutocompleteComponents(term, throwOnMissing);
}
function loadApisFromJson(
json,
urlParametrizedComponentFactories,
bodyParametrizedComponentFactories
) {
urlParametrizedComponentFactories =
urlParametrizedComponentFactories || parametrizedComponentFactories;
bodyParametrizedComponentFactories =
bodyParametrizedComponentFactories || urlParametrizedComponentFactories;
const api = new Api(urlParametrizedComponentFactories, bodyParametrizedComponentFactories);
const names = [];
_.each(json, function (apiJson, name) {
names.unshift(name);
_.each(apiJson.globals || {}, function (globalJson, globalName) {
api.addGlobalAutocompleteRules(globalName, globalJson);
});
_.each(apiJson.endpoints || {}, function (endpointJson, endpointName) {
api.addEndpointDescription(endpointName, endpointJson);
});
});
api.name = names.join(',');
return api;
}
const specDefinitionsService = new SpecDefinitionsService();
specDefinitionsService.start();
const json = specDefinitionsService.asJson();
ACTIVE_API = loadApisFromJson({ es: json });

View File

@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export {
ElasticsearchSqlHighlightRules,
ScriptHighlightRules,
XJsonHighlightRules,
addXJsonToRules,
XJsonMode,
installXJsonMode,
} from './modes';

View File

@ -0,0 +1,40 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
export {
ElasticsearchSqlHighlightRules,
ScriptHighlightRules,
XJsonHighlightRules,
addXJsonToRules,
} from './lexer_rules';
export { installXJsonMode, XJsonMode } from './x_json';

Some files were not shown because too many files have changed in this diff Show More