first commit
This commit is contained in:
commit
3b6963483c
7
.env.example
Normal file
7
.env.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
MM_RUDDER_CALLS_PROD=
|
||||||
|
MM_RUDDER_DATAPLANE_URL=
|
||||||
|
|
||||||
|
MM_SERVICESETTINGS_SITEURL=http://localhost:8065
|
||||||
|
MM_ADMIN_TOKEN=
|
||||||
|
MM_SERVICESETTINGS_ENABLEDEVELOPER=true
|
||||||
|
#MM_DEBUG=true
|
||||||
17
.gitea/workflows/build-and-push-plugin.yml
Normal file
17
.gitea/workflows/build-and-push-plugin.yml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
name: release
|
||||||
|
on:
|
||||||
|
- release
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
uses: wilix-infra/actions/.gitea/workflows/loop-plugin-with-marketplace-template.yml@master
|
||||||
|
with:
|
||||||
|
go_version: "1.21"
|
||||||
|
node_version: "22.14.0"
|
||||||
|
vault_secrets_base_path: dev/wilix/loop/data/ci/plugin
|
||||||
|
artifacts_url: https://artifacts.wilix.dev
|
||||||
|
artifacts_repository: loop-files
|
||||||
|
secrets:
|
||||||
|
VAULT_ROLE_ID: ${{ secrets.VAULT_ROLE_ID }}
|
||||||
|
VAULT_SECRET_ID: ${{ secrets.VAULT_SECRET_ID }}
|
||||||
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
dist/
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# Jetbrains
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
#Mage files
|
||||||
|
make
|
||||||
|
make.exe
|
||||||
|
|
||||||
|
#Secrets
|
||||||
|
servers.json
|
||||||
|
.qodo
|
||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
server/dist
|
||||||
|
webapp/dist
|
||||||
|
.yarn
|
||||||
|
|
||||||
382
Makefile
Normal file
382
Makefile
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
GO ?= $(shell command -v go 2> /dev/null)
|
||||||
|
NPM ?= $(shell command -v yarn 2> /dev/null)
|
||||||
|
CURL ?= $(shell command -v curl 2> /dev/null)
|
||||||
|
MM_DEBUG ?=
|
||||||
|
MANIFEST_FILE ?= plugin.json
|
||||||
|
GOPATH ?= $(shell go env GOPATH)
|
||||||
|
GO_TEST_FLAGS ?= -race
|
||||||
|
GO_BUILD_FLAGS ?=
|
||||||
|
MM_UTILITIES_DIR ?= ../mattermost-utilities
|
||||||
|
DLV_DEBUG_PORT := 2346
|
||||||
|
DEFAULT_GOOS := $(shell go env GOOS)
|
||||||
|
DEFAULT_GOARCH := $(shell go env GOARCH)
|
||||||
|
SANS_RESTAPITEST = $(shell go list ./... | grep -v 'git.wilix.dev/loop/loop-plugin-apps/test/restapitest' | sed -e 's/git.wilix.dev\/loop\/loop-plugin-apps\//.\//g')
|
||||||
|
|
||||||
|
ifneq (,$(wildcard ./.env))
|
||||||
|
include .env
|
||||||
|
export $(shell sed 's/=.*//' .env)
|
||||||
|
endif
|
||||||
|
|
||||||
|
export GO111MODULE=on
|
||||||
|
|
||||||
|
# You can include assets this directory into the bundle. This can be e.g. used to include profile pictures.
|
||||||
|
ASSETS_DIR ?= assets
|
||||||
|
|
||||||
|
## Define the default target (make all)
|
||||||
|
.PHONY: default
|
||||||
|
default: all
|
||||||
|
|
||||||
|
# Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed.
|
||||||
|
include build/setup.mk
|
||||||
|
|
||||||
|
BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz
|
||||||
|
|
||||||
|
# Include custom makefile, if present
|
||||||
|
ifneq ($(wildcard build/custom.mk),)
|
||||||
|
include build/custom.mk
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ====================================================================================
|
||||||
|
# Used for semver bumping
|
||||||
|
PROTECTED_BRANCH := master
|
||||||
|
APP_NAME := $(shell basename -s .git `git config --get remote.origin.url`)
|
||||||
|
CURRENT_VERSION := $(shell git describe --abbrev=0 --tags)
|
||||||
|
VERSION_PARTS := $(subst ., ,$(subst v,,$(subst -rc, ,$(CURRENT_VERSION))))
|
||||||
|
MAJOR := $(word 1,$(VERSION_PARTS))
|
||||||
|
MINOR := $(word 2,$(VERSION_PARTS))
|
||||||
|
PATCH := $(word 3,$(VERSION_PARTS))
|
||||||
|
RC := $(shell echo $(CURRENT_VERSION) | grep -oE 'rc[0-9]+' | sed 's/rc//')
|
||||||
|
# Check if current branch is protected
|
||||||
|
define check_protected_branch
|
||||||
|
@current_branch=$$(git rev-parse --abbrev-ref HEAD); \
|
||||||
|
if ! echo "$(PROTECTED_BRANCH)" | grep -wq "$$current_branch" && ! echo "$$current_branch" | grep -q "^release"; then \
|
||||||
|
echo "Error: Tagging is only allowed from $(PROTECTED_BRANCH) or release branches. You are on $$current_branch branch."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
endef
|
||||||
|
# Check if there are pending pulls
|
||||||
|
define check_pending_pulls
|
||||||
|
@git fetch; \
|
||||||
|
current_branch=$$(git rev-parse --abbrev-ref HEAD); \
|
||||||
|
if [ "$$(git rev-parse HEAD)" != "$$(git rev-parse origin/$$current_branch)" ]; then \
|
||||||
|
echo "Error: Your branch is not up to date with upstream. Please pull the latest changes before performing a release"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
endef
|
||||||
|
# Prompt for approval
|
||||||
|
define prompt_approval
|
||||||
|
@read -p "About to bump $(APP_NAME) to version $(1), approve? (y/n) " userinput; \
|
||||||
|
if [ "$$userinput" != "y" ]; then \
|
||||||
|
echo "Bump aborted."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
endef
|
||||||
|
# ====================================================================================
|
||||||
|
|
||||||
|
.PHONY: patch minor major patch-rc minor-rc major-rc
|
||||||
|
|
||||||
|
patch: ## to bump patch version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
@$(eval PATCH := $(shell echo $$(($(PATCH)+1))))
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH))
|
||||||
|
@echo Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
@echo Bumped $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
|
||||||
|
minor: ## to bump minor version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
@$(eval MINOR := $(shell echo $$(($(MINOR)+1))))
|
||||||
|
@$(eval PATCH := 0)
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH))
|
||||||
|
@echo Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
@echo Bumped $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
|
||||||
|
major: ## to bump major version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
$(eval MAJOR := $(shell echo $$(($(MAJOR)+1))))
|
||||||
|
$(eval MINOR := 0)
|
||||||
|
$(eval PATCH := 0)
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH))
|
||||||
|
@echo Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m "Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
@echo Bumped $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)
|
||||||
|
|
||||||
|
patch-rc: ## to bump patch release candidate version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
@$(eval RC := $(shell echo $$(($(RC)+1))))
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC))
|
||||||
|
@echo Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
@echo Bumped $(APP_NAME) to Patch RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
|
||||||
|
minor-rc: ## to bump minor release candidate version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
@$(eval MINOR := $(shell echo $$(($(MINOR)+1))))
|
||||||
|
@$(eval PATCH := 0)
|
||||||
|
@$(eval RC := 1)
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC))
|
||||||
|
@echo Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
@echo Bumped $(APP_NAME) to Minor RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
|
||||||
|
major-rc: ## to bump major release candidate version (semver)
|
||||||
|
$(call check_protected_branch)
|
||||||
|
$(call check_pending_pulls)
|
||||||
|
@$(eval MAJOR := $(shell echo $$(($(MAJOR)+1))))
|
||||||
|
@$(eval MINOR := 0)
|
||||||
|
@$(eval PATCH := 0)
|
||||||
|
@$(eval RC := 1)
|
||||||
|
$(call prompt_approval,$(MAJOR).$(MINOR).$(PATCH)-rc$(RC))
|
||||||
|
@echo Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
git tag -s -a v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC) -m "Bumping $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)"
|
||||||
|
git push origin v$(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
@echo Bumped $(APP_NAME) to Major RC version $(MAJOR).$(MINOR).$(PATCH)-rc$(RC)
|
||||||
|
|
||||||
|
## Checks the code style, tests, builds and bundles the plugin.
|
||||||
|
.PHONY: all
|
||||||
|
all: check-style test dist
|
||||||
|
|
||||||
|
.PHONY: deps
|
||||||
|
deps:
|
||||||
|
@$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2
|
||||||
|
@$(GO) install github.com/nicksnyder/go-i18n/v2/goi18n@v2.2.0
|
||||||
|
|
||||||
|
## Runs eslint and golangci-lint
|
||||||
|
.PHONY: check-style
|
||||||
|
check-style: webapp/node_modules
|
||||||
|
@echo Checking for style guide compliance
|
||||||
|
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
cd webapp && npm run lint
|
||||||
|
cd webapp && npm run check-types
|
||||||
|
endif
|
||||||
|
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
@echo Running golangci-lint
|
||||||
|
golangci-lint run ./... --verbose
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Builds the server, if it exists, for all supported architectures, unless MM_SERVICESETTINGS_ENABLEDEVELOPER is set
|
||||||
|
.PHONY: server
|
||||||
|
server:
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
mkdir -p server/dist;
|
||||||
|
ifeq ($(MM_DEBUG),)
|
||||||
|
ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),)
|
||||||
|
cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH);
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-amd64;
|
||||||
|
else
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-amd64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-linux-arm64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-amd64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-darwin-arm64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -trimpath -o dist/plugin-windows-amd64.exe;
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
$(info DEBUG mode is on; to disable, unset MM_DEBUG)
|
||||||
|
ifneq ($(MM_SERVICESETTINGS_ENABLEDEVELOPER),)
|
||||||
|
cd server && env CGO_ENABLED=0 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-$(DEFAULT_GOOS)-$(DEFAULT_GOARCH);
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-linux-amd64;
|
||||||
|
else
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-linux-amd64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-linux-arm64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-darwin-amd64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-darwin-arm64;
|
||||||
|
cd server && env CGO_ENABLED=0 GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -gcflags "all=-N -l" -trimpath -o dist/plugin-windows-amd64.exe;
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Ensures NPM dependencies are installed without having to run this all the time.
|
||||||
|
webapp/node_modules: $(wildcard webapp/package.json)
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
cd webapp && $(NPM) install
|
||||||
|
touch $@
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Builds the webapp, if it exists.
|
||||||
|
.PHONY: webapp
|
||||||
|
webapp: webapp/node_modules
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
ifeq ($(MM_DEBUG),)
|
||||||
|
cd webapp && $(NPM) build;
|
||||||
|
else
|
||||||
|
cd webapp && $(NPM) debug;
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Generates a tar bundle of the plugin for install.
|
||||||
|
.PHONY: bundle
|
||||||
|
bundle:
|
||||||
|
rm -rf dist/
|
||||||
|
mkdir -p dist/$(PLUGIN_ID)
|
||||||
|
cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/
|
||||||
|
ifneq ($(wildcard $(ASSETS_DIR)/.),)
|
||||||
|
cp -r $(ASSETS_DIR) dist/$(PLUGIN_ID)/
|
||||||
|
endif
|
||||||
|
ifneq ($(HAS_PUBLIC),)
|
||||||
|
cp -r public dist/$(PLUGIN_ID)/
|
||||||
|
endif
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
mkdir -p dist/$(PLUGIN_ID)/server
|
||||||
|
cp -r server/dist dist/$(PLUGIN_ID)/server/
|
||||||
|
endif
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
mkdir -p dist/$(PLUGIN_ID)/webapp
|
||||||
|
cp -r webapp/dist dist/$(PLUGIN_ID)/webapp/
|
||||||
|
endif
|
||||||
|
cd dist && \
|
||||||
|
find ./ -type f \( -name ".DS_Store" -o -name "._*" -o -name ".Trashes" -o -name ".Spotlight-*" -o -name ".fseventsd" \) -delete && \
|
||||||
|
tar -cvzf $(BUNDLE_NAME) $(PLUGIN_ID)
|
||||||
|
|
||||||
|
@echo plugin built at: dist/$(BUNDLE_NAME)
|
||||||
|
|
||||||
|
## Builds and bundles the plugin.
|
||||||
|
.PHONY: dist
|
||||||
|
dist: server webapp bundle
|
||||||
|
|
||||||
|
## Builds and installs the plugin to a server.
|
||||||
|
.PHONY: deploy
|
||||||
|
deploy: dist
|
||||||
|
./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME)
|
||||||
|
|
||||||
|
## Builds and installs the plugin to a server, updating the webapp automatically when changed.
|
||||||
|
.PHONY: watch
|
||||||
|
watch: apply server bundle
|
||||||
|
ifeq ($(MM_DEBUG),)
|
||||||
|
cd webapp && $(NPM) run build:watch
|
||||||
|
else
|
||||||
|
cd webapp && $(NPM) run debug:watch
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Installs a previous built plugin with updated webpack assets to a server.
|
||||||
|
.PHONY: deploy-from-watch
|
||||||
|
deploy-from-watch: bundle
|
||||||
|
./build/bin/pluginctl deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME)
|
||||||
|
|
||||||
|
## Setup dlv for attaching, identifying the plugin PID for other targets.
|
||||||
|
.PHONY: setup-attach
|
||||||
|
setup-attach:
|
||||||
|
$(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}'))
|
||||||
|
$(eval NUM_PID := $(shell echo -n ${PLUGIN_PID} | wc -w))
|
||||||
|
|
||||||
|
@if [ ${NUM_PID} -gt 2 ]; then \
|
||||||
|
echo "** There is more than 1 plugin process running. Run 'make kill reset' to restart just one."; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Check if setup-attach succeeded.
|
||||||
|
.PHONY: check-attach
|
||||||
|
check-attach:
|
||||||
|
@if [ -z ${PLUGIN_PID} ]; then \
|
||||||
|
echo "Could not find plugin PID; the plugin is not running. Exiting."; \
|
||||||
|
exit 1; \
|
||||||
|
else \
|
||||||
|
echo "Located Plugin running with PID: ${PLUGIN_PID}"; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Attach dlv to an existing plugin instance.
|
||||||
|
.PHONY: attach
|
||||||
|
attach: setup-attach check-attach
|
||||||
|
dlv attach ${PLUGIN_PID}
|
||||||
|
|
||||||
|
## Attach dlv to an existing plugin instance, exposing a headless instance on $DLV_DEBUG_PORT.
|
||||||
|
.PHONY: attach-headless
|
||||||
|
attach-headless: setup-attach check-attach
|
||||||
|
dlv attach ${PLUGIN_PID} --listen :$(DLV_DEBUG_PORT) --headless=true --api-version=2 --accept-multiclient
|
||||||
|
|
||||||
|
## Detach dlv from an existing plugin instance, if previously attached.
|
||||||
|
.PHONY: detach
|
||||||
|
detach: setup-attach
|
||||||
|
@DELVE_PID=$(shell ps aux | grep "dlv attach ${PLUGIN_PID}" | grep -v "grep" | awk -F " " '{print $$2}') && \
|
||||||
|
if [ "$$DELVE_PID" -gt 0 ] > /dev/null 2>&1 ; then \
|
||||||
|
echo "Located existing delve process running with PID: $$DELVE_PID. Killing." ; \
|
||||||
|
kill -9 $$DELVE_PID ; \
|
||||||
|
fi
|
||||||
|
|
||||||
|
## Runs any lints and unit tests defined for the server and webapp, if they exist.
|
||||||
|
.PHONY: test
|
||||||
|
test: webapp/node_modules
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
$(GO) test -v $(GO_TEST_FLAGS) $(SANS_RESTAPITEST)
|
||||||
|
endif
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
cd webapp && $(NPM) run test;
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Creates a coverage report for the server code.
|
||||||
|
.PHONY: coverage
|
||||||
|
coverage: webapp/node_modules
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
$(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt $(SANS_RESTAPITEST)
|
||||||
|
$(GO) tool cover -html=server/coverage.txt
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Extract strings for translation from the source code.
|
||||||
|
.PHONY: i18n-extract
|
||||||
|
i18n-extract:
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
ifeq ($(HAS_MM_UTILITIES),)
|
||||||
|
@echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command"
|
||||||
|
else
|
||||||
|
cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir $(PWD)/webapp
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Disable the plugin.
|
||||||
|
.PHONY: disable
|
||||||
|
disable: detach
|
||||||
|
./build/bin/pluginctl disable $(PLUGIN_ID)
|
||||||
|
|
||||||
|
## Enable the plugin.
|
||||||
|
.PHONY: enable
|
||||||
|
enable:
|
||||||
|
./build/bin/pluginctl enable $(PLUGIN_ID)
|
||||||
|
|
||||||
|
## Reset the plugin, effectively disabling and re-enabling it on the server.
|
||||||
|
.PHONY: reset
|
||||||
|
reset: detach
|
||||||
|
./build/bin/pluginctl reset $(PLUGIN_ID)
|
||||||
|
|
||||||
|
## Kill all instances of the plugin, detaching any existing dlv instance.
|
||||||
|
.PHONY: kill
|
||||||
|
kill: detach
|
||||||
|
$(eval PLUGIN_PID := $(shell ps aux | grep "plugins/${PLUGIN_ID}" | grep -v "grep" | awk -F " " '{print $$2}'))
|
||||||
|
|
||||||
|
@for PID in ${PLUGIN_PID}; do \
|
||||||
|
echo "Killing plugin pid $$PID"; \
|
||||||
|
kill -9 $$PID; \
|
||||||
|
done; \
|
||||||
|
|
||||||
|
## Clean removes all build artifacts.
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -fr dist/
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
rm -fr server/coverage.txt
|
||||||
|
rm -fr server/dist
|
||||||
|
endif
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
rm -fr webapp/junit.xml
|
||||||
|
rm -fr webapp/dist
|
||||||
|
rm -fr webapp/node_modules
|
||||||
|
endif
|
||||||
|
rm -fr build/bin/
|
||||||
|
rm -fr test/restapitest/mattermost.log
|
||||||
|
rm -fr test/restapitest/notifications.log
|
||||||
|
rm -fr test/restapitest/data/
|
||||||
|
|
||||||
|
# Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||||
|
help:
|
||||||
|
@cat Makefile build/*.mk | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//" | sed -e "s/^## //" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort
|
||||||
43
README.md
Normal file
43
README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Template Plugin
|
||||||
|
|
||||||
|
Шаблон плагина для LOOP.
|
||||||
|
|
||||||
|
## Структура проекта
|
||||||
|
|
||||||
|
- `server/` - серверная часть плагина (Go)
|
||||||
|
- `webapp/` - клиентская часть плагина (React/TypeScript)
|
||||||
|
- `plugin.json` - манифест плагина
|
||||||
|
- `plugin.go` - точка входа плагина
|
||||||
|
|
||||||
|
## Установка и сборка
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
go mod download
|
||||||
|
|
||||||
|
cd ../webapp
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
cd ..
|
||||||
|
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Разработка
|
||||||
|
|
||||||
|
Для разработки используйте команды из Makefile:
|
||||||
|
|
||||||
|
- `make server` - сборка серверной части
|
||||||
|
- `make webapp` - сборка клиентской части
|
||||||
|
- `make dist` - полная сборка плагина
|
||||||
|
- `make check-style` - проверка стиля кода
|
||||||
|
- `make test` - запуск тестов
|
||||||
|
|
||||||
|
## Настройка
|
||||||
|
|
||||||
|
Измените следующие файлы для настройки плагина:
|
||||||
|
|
||||||
|
- `plugin.json` - ID, имя и описание плагина
|
||||||
|
- `server/plugin/plugin.go` - основная логика сервера
|
||||||
|
- `webapp/src/index.tsx` - точка входа клиентской части
|
||||||
|
- `webapp/src/components/RootComponent.tsx` - корневой компонент React
|
||||||
1
assets/icon.svg
Normal file
1
assets/icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="-0.05 27.99 512.05 456.01"><path d="M505.4 460.4c8.8-14.8 8.8-33.2 0-48L296.8 51c-8.4-14.6-24.1-23.4-40.9-23-16.8-.2-32.4 8.5-40.9 23l-67.2 117.1 16.6 10.2c50.3 28.9 92 70.6 120.9 120.9 25.8 44.3 40.7 94 43.5 145.2h-47.3c-3-42.8-15.7-84.4-37.1-121.5-24.6-43.4-60.4-79.4-103.6-104.3l-17.3-9.6-62.7 108.7L77.4 328c42.4 24.8 70.9 67.8 77.4 116.4H46.1c-2.7-.1-5.1-1.5-6.4-3.8-1.6-2.5-1.6-5.8 0-8.3l30.7-51.2c-10.2-8.8-21.9-15.8-34.5-20.5L6.4 413c-8.6 14.6-8.6 32.7 0 47.3 8.3 14.7 24 23.8 40.9 23.7h148.4v-20.5c0-34.3-8.8-68-25.6-97.9-14.1-24.1-33-45-55.6-61.4l23.7-40.9c29.6 20.5 54.3 47.4 72.3 78.7 21.3 37 32.6 78.9 32.6 121.5v19.2h126v-19.2c-.1-64.7-17.3-128.3-49.9-184.2-28.7-50.7-69-93.9-117.7-126l48-83.2c1.3-2.3 3.7-3.8 6.4-3.8 2.7.1 5.1 1.5 6.4 3.8l209.2 361.4c1.4 2.4 1.4 5.3 0 7.7a6.88 6.88 0 0 1-6.4 3.8h-49.3v39.7h49.3c16.8.2 32.3-8.9 40.3-23.7z"/></svg>
|
||||||
|
After Width: | Height: | Size: 933 B |
1
build/.gitignore
vendored
Normal file
1
build/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
bin
|
||||||
82
build/custom.mk
Normal file
82
build/custom.mk
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
# Include custome targets and environment variables here
|
||||||
|
default: all
|
||||||
|
|
||||||
|
# If there's no MM_RUDDER_PLUGINS_PROD, add DEV data
|
||||||
|
RUDDER_WRITE_KEY = ""
|
||||||
|
ifdef MM_RUDDER_PLUGINS_PROD
|
||||||
|
RUDDER_WRITE_KEY = $(MM_RUDDER_PLUGINS_PROD)
|
||||||
|
endif
|
||||||
|
|
||||||
|
BUILD_DATE = $(shell date -u)
|
||||||
|
BUILD_HASH = $(shell git rev-parse HEAD)
|
||||||
|
BUILD_HASH_SHORT = $(shell git rev-parse --short HEAD)
|
||||||
|
LDFLAGS += -X "plugin.buildHash=$(BUILD_HASH)"
|
||||||
|
LDFLAGS+= -X "plugin.isDebug=$(MM_DEBUG)"
|
||||||
|
LDFLAGS += -X "plugin.rudderWriteKey=$(MM_RUDDER_CALLS_PROD)"
|
||||||
|
LDFLAGS += -X "plugin.rudderDataplaneURL=$(MM_RUDDER_DATAPLANE_URL)"
|
||||||
|
GO_BUILD_FLAGS += -ldflags '$(LDFLAGS)'
|
||||||
|
GO_TEST_FLAGS += -ldflags '$(LDFLAGS)'
|
||||||
|
|
||||||
|
MM_SERVER_PATH ?= ${PWD}/../mattermost-server
|
||||||
|
export MM_SERVER_PATH
|
||||||
|
|
||||||
|
## Generates mock golang interfaces for testing
|
||||||
|
.PHONY: mock
|
||||||
|
mock:
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
go install github.com/golang/mock/mockgen@v1.6.0
|
||||||
|
mockgen -destination server/mocks/mock_proxy/mock_expand_getter.go git.wilix.dev/loop/loop-plugin-starter-template/server/proxy ExpandGetter
|
||||||
|
mockgen -destination server/mocks/mock_upstream/mock_upstream.go git.wilix.dev/loop/loop-plugin-starter-template/upstream Upstream
|
||||||
|
mockgen -destination server/mocks/mock_store/mock_appstore.go git.wilix.dev/loop/loop-plugin-starter-template/server/store AppStore
|
||||||
|
mockgen -destination server/mocks/mock_store/mock_session.go git.wilix.dev/loop/loop-plugin-starter-template/server/store SessionStore
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Generates mock golang interfaces for testing
|
||||||
|
.PHONY: clean_mock
|
||||||
|
clean_mock:
|
||||||
|
ifneq ($(HAS_SERVER),)
|
||||||
|
rm -rf ./server/mocks
|
||||||
|
endif
|
||||||
|
|
||||||
|
## Run Go REST API system tests
|
||||||
|
.PHONY: test-rest-api
|
||||||
|
test-rest-api: dist
|
||||||
|
@echo Running REST API tests
|
||||||
|
ifneq ($(RUN),)
|
||||||
|
PLUGIN_BUNDLE=$(shell pwd)/dist/$(BUNDLE_NAME) $(GO) test -v $(GO_TEST_FLAGS) ./test/restapitest --run $(RUN)
|
||||||
|
else
|
||||||
|
PLUGIN_BUNDLE=$(shell pwd)/dist/$(BUNDLE_NAME) $(GO) test -v $(GO_TEST_FLAGS) ./test/restapitest
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
## Extract new translation messages
|
||||||
|
.PHONY: i18n-extract-server
|
||||||
|
i18n-extract-server:
|
||||||
|
@goi18n extract -format json -outdir assets/i18n/ server/ utils/ apps/ cmd/ upstream/
|
||||||
|
@for x in assets/i18n/active.*.json; do echo $$x | sed 's/active/translate/' | sed 's/^/touch /' | bash; done
|
||||||
|
@goi18n merge -format json -outdir assets/i18n/ assets/i18n/active.*.json
|
||||||
|
@echo "Please update your assets/i18n/translate.*.json files and execute \"make i18n-merge-server\""
|
||||||
|
@echo "If you don't want to change any locale file, simply remove the assets/i18n/translate.??.json file before calling \"make i18n-merge-server\""
|
||||||
|
@echo "If you want to add a new language (for example french), simply run \"touch assets/i18n/active.fr.json\" and then run the \"make i18n-extract-server\" again"
|
||||||
|
|
||||||
|
## Merge translated messages
|
||||||
|
.PHONY: i18n-merge-server
|
||||||
|
i18n-merge-server:
|
||||||
|
@goi18n merge -format json -outdir assets/i18n/ assets/i18n/active.*.json assets/i18n/translate.*.json
|
||||||
|
@rm -f assets/i18n/translate.*.json
|
||||||
|
@echo "Translations merged, please verify your "git diff" before you submit the changes"
|
||||||
|
|
||||||
|
## Run a simple Mattermost Server
|
||||||
|
.PHONY: dev_server
|
||||||
|
dev_server:
|
||||||
|
cd dev && docker-compose up mattermost db
|
||||||
|
|
||||||
|
## Run the hello-world app
|
||||||
|
.PHONY: run-example-hello-4000
|
||||||
|
run-example-hello-4000:
|
||||||
|
cd test/hello-world && go run .
|
||||||
|
|
||||||
|
## Run the test app
|
||||||
|
.PHONY: run-test-app-8081
|
||||||
|
run-test-app-8081:
|
||||||
|
cd test/app && go run .
|
||||||
76
build/manifest/main.go
Normal file
76
build/manifest/main.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) <= 1 {
|
||||||
|
panic("no cmd specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest, err := findManifest()
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to find manifest: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := os.Args[1]
|
||||||
|
switch cmd {
|
||||||
|
case "id":
|
||||||
|
dumpPluginID(manifest)
|
||||||
|
|
||||||
|
case "version":
|
||||||
|
dumpPluginVersion(manifest)
|
||||||
|
|
||||||
|
case "has_server":
|
||||||
|
if manifest.HasServer() {
|
||||||
|
fmt.Printf("true")
|
||||||
|
}
|
||||||
|
|
||||||
|
case "has_webapp":
|
||||||
|
if manifest.HasWebapp() {
|
||||||
|
fmt.Printf("true")
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("unrecognized command: " + cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findManifest() (*model.Manifest, error) {
|
||||||
|
_, manifestFilePath, err := model.FindManifest(".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to find manifest in current working directory")
|
||||||
|
}
|
||||||
|
manifestFile, err := os.Open(manifestFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to open %s", manifestFilePath)
|
||||||
|
}
|
||||||
|
defer manifestFile.Close()
|
||||||
|
|
||||||
|
// Re-decode the manifest, disallowing unknown fields. When we write the manifest back out,
|
||||||
|
// we don't want to accidentally clobber anything we won't preserve.
|
||||||
|
var manifest model.Manifest
|
||||||
|
decoder := json.NewDecoder(manifestFile)
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
if err = decoder.Decode(&manifest); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to parse manifest")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPluginId writes the plugin id from the given manifest to standard out
|
||||||
|
func dumpPluginID(manifest *model.Manifest) {
|
||||||
|
fmt.Printf("%s", manifest.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPluginVersion writes the plugin version from the given manifest to standard out
|
||||||
|
func dumpPluginVersion(manifest *model.Manifest) {
|
||||||
|
fmt.Printf("%s", manifest.Version)
|
||||||
|
}
|
||||||
180
build/pluginctl/main.go
Normal file
180
build/pluginctl/main.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
// main handles deployment of the plugin to a development server using the Client4 API.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
const commandTimeout = 120 * time.Second
|
||||||
|
|
||||||
|
const helpText = `
|
||||||
|
Usage:
|
||||||
|
pluginctl deploy <plugin id> <bundle path>
|
||||||
|
pluginctl disable <plugin id>
|
||||||
|
pluginctl enable <plugin id>
|
||||||
|
pluginctl reset <plugin id>
|
||||||
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := pluginctl()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed: %s\n", err.Error())
|
||||||
|
fmt.Print(helpText)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginctl() error {
|
||||||
|
if len(os.Args) < 3 {
|
||||||
|
return errors.New("invalid number of arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client, err := getClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "deploy":
|
||||||
|
if len(os.Args) < 4 {
|
||||||
|
return errors.New("invalid number of arguments")
|
||||||
|
}
|
||||||
|
return deploy(ctx, client, os.Args[2], os.Args[3])
|
||||||
|
case "disable":
|
||||||
|
return disablePlugin(ctx, client, os.Args[2])
|
||||||
|
case "enable":
|
||||||
|
return enablePlugin(ctx, client, os.Args[2])
|
||||||
|
case "reset":
|
||||||
|
return resetPlugin(ctx, client, os.Args[2])
|
||||||
|
default:
|
||||||
|
return errors.New("invalid second argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClient(ctx context.Context) (*model.Client4, error) {
|
||||||
|
socketPath := os.Getenv("MM_LOCALSOCKETPATH")
|
||||||
|
if socketPath == "" {
|
||||||
|
socketPath = model.LocalModeSocketPath
|
||||||
|
}
|
||||||
|
|
||||||
|
client, connected := getUnixClient(socketPath)
|
||||||
|
if connected {
|
||||||
|
log.Printf("Connecting using local mode over %s", socketPath)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if os.Getenv("MM_LOCALSOCKETPATH") != "" {
|
||||||
|
log.Printf("No socket found at %s for local mode deployment. Attempting to authenticate with credentials.", socketPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL")
|
||||||
|
adminToken := os.Getenv("MM_ADMIN_TOKEN")
|
||||||
|
adminUsername := os.Getenv("MM_ADMIN_USERNAME")
|
||||||
|
adminPassword := os.Getenv("MM_ADMIN_PASSWORD")
|
||||||
|
|
||||||
|
if siteURL == "" {
|
||||||
|
return nil, errors.New("MM_SERVICESETTINGS_SITEURL is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client = model.NewAPIv4Client(siteURL)
|
||||||
|
|
||||||
|
if adminToken != "" {
|
||||||
|
log.Printf("Authenticating using token against %s.", siteURL)
|
||||||
|
client.SetToken(adminToken)
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if adminUsername != "" && adminPassword != "" {
|
||||||
|
client := model.NewAPIv4Client(siteURL)
|
||||||
|
log.Printf("Authenticating as %s against %s.", adminUsername, siteURL)
|
||||||
|
_, _, err := client.Login(ctx, adminUsername, adminPassword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("one of MM_ADMIN_TOKEN or MM_ADMIN_USERNAME/MM_ADMIN_PASSWORD must be defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUnixClient(socketPath string) (*model.Client4, bool) {
|
||||||
|
_, err := net.Dial("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return model.NewAPIv4SocketClient(socketPath), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploy attempts to upload and enable a plugin via the Client4 API.
|
||||||
|
// It will fail if plugin uploads are disabled.
|
||||||
|
func deploy(ctx context.Context, client *model.Client4, pluginID, bundlePath string) error {
|
||||||
|
pluginBundle, err := os.Open(bundlePath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open %s: %w", bundlePath, err)
|
||||||
|
}
|
||||||
|
defer pluginBundle.Close()
|
||||||
|
|
||||||
|
log.Print("Uploading plugin via API.")
|
||||||
|
_, _, err = client.UploadPluginForced(ctx, pluginBundle)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to upload plugin bundle: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Print("Enabling plugin.")
|
||||||
|
_, err = client.EnablePlugin(ctx, pluginID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable plugin: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// disablePlugin attempts to disable the plugin via the Client4 API.
|
||||||
|
func disablePlugin(ctx context.Context, client *model.Client4, pluginID string) error {
|
||||||
|
log.Print("Disabling plugin.")
|
||||||
|
_, err := client.DisablePlugin(ctx, pluginID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to disable plugin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// enablePlugin attempts to enable the plugin via the Client4 API.
|
||||||
|
func enablePlugin(ctx context.Context, client *model.Client4, pluginID string) error {
|
||||||
|
log.Print("Enabling plugin.")
|
||||||
|
_, err := client.EnablePlugin(ctx, pluginID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to enable plugin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resetPlugin attempts to reset the plugin via the Client4 API.
|
||||||
|
func resetPlugin(ctx context.Context, client *model.Client4, pluginID string) error {
|
||||||
|
err := disablePlugin(ctx, client, pluginID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = enablePlugin(ctx, client, pluginID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
45
build/setup.mk
Normal file
45
build/setup.mk
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Ensure that go is installed. Note that this is independent of whether or not a server is being
|
||||||
|
# built, since the build script itself uses go.
|
||||||
|
ifeq ($(GO),)
|
||||||
|
$(error "go is not available: see https://golang.org/doc/install")
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Ensure that the build tools are compiled. Go's caching makes this quick.
|
||||||
|
$(shell cd build/manifest && $(GO) build -o ../bin/manifest)
|
||||||
|
|
||||||
|
# Ensure that the deployment tools are compiled. Go's caching makes this quick.
|
||||||
|
$(shell cd build/pluginctl && $(GO) build -o ../bin/pluginctl)
|
||||||
|
|
||||||
|
# Extract the plugin id from the manifest.
|
||||||
|
PLUGIN_ID ?= $(shell build/bin/manifest id)
|
||||||
|
ifeq ($(PLUGIN_ID),)
|
||||||
|
$(error "Cannot parse id from $(MANIFEST_FILE)")
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Extract the plugin version from the manifest.
|
||||||
|
PLUGIN_VERSION ?= $(shell build/bin/manifest version)
|
||||||
|
ifeq ($(PLUGIN_VERSION),)
|
||||||
|
$(error "Cannot parse version from $(MANIFEST_FILE)")
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Determine if a server is defined in the manifest.
|
||||||
|
HAS_SERVER ?= $(shell build/bin/manifest has_server)
|
||||||
|
|
||||||
|
# Determine if a webapp is defined in the manifest.
|
||||||
|
HAS_WEBAPP ?= $(shell build/bin/manifest has_webapp)
|
||||||
|
|
||||||
|
# Determine if a /public folder is in use
|
||||||
|
HAS_PUBLIC ?= $(wildcard public/.)
|
||||||
|
|
||||||
|
# Determine if the mattermost-utilities repo is present
|
||||||
|
HAS_MM_UTILITIES ?= $(wildcard $(MM_UTILITIES_DIR)/.)
|
||||||
|
|
||||||
|
# Store the current path for later use
|
||||||
|
PWD ?= $(shell pwd)
|
||||||
|
|
||||||
|
# Ensure that npm (and thus node) is installed.
|
||||||
|
ifneq ($(HAS_WEBAPP),)
|
||||||
|
ifeq ($(NPM),)
|
||||||
|
$(error "npm is not available: see https://www.npmjs.com/get-npm")
|
||||||
|
endif
|
||||||
|
endif
|
||||||
59
go.mod
Normal file
59
go.mod
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
module git.wilix.dev/loop/loop-plugin-starter-template
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/mattermost/mattermost/server/public v0.0.8
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
github.com/rudderlabs/analytics-go v3.3.3+incompatible
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||||
|
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a // indirect
|
||||||
|
github.com/fatih/color v1.15.0 // indirect
|
||||||
|
github.com/francoispqt/gojay v1.2.13 // indirect
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a // indirect
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/go-plugin v1.4.10 // indirect
|
||||||
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
|
github.com/lib/pq v1.10.9 // indirect
|
||||||
|
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
|
||||||
|
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
|
||||||
|
github.com/mattermost/logr/v2 v2.0.16 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||||
|
github.com/oklog/run v1.1.0 // indirect
|
||||||
|
github.com/pborman/uuid v1.2.1 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
|
github.com/segmentio/backo-go v1.0.1 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
|
github.com/tidwall/gjson v1.14.3 // indirect
|
||||||
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
|
github.com/tinylib/msgp v1.1.8 // indirect
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||||
|
github.com/wiggin77/merror v1.0.5 // indirect
|
||||||
|
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||||
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||||
|
golang.org/x/crypto v0.35.0 // indirect
|
||||||
|
golang.org/x/net v0.26.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
|
||||||
|
google.golang.org/grpc v1.64.1 // indirect
|
||||||
|
google.golang.org/protobuf v1.33.0 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
339
go.sum
Normal file
339
go.sum
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=
|
||||||
|
dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=
|
||||||
|
dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=
|
||||||
|
dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=
|
||||||
|
dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
|
||||||
|
git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||||
|
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||||
|
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
|
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||||
|
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a h1:etIrTD8BQqzColk9nKRusM9um5+1q0iOEJLqfBMIK64=
|
||||||
|
github.com/dyatlov/go-opengraph/opengraph v0.0.0-20220524092352-606d7b1e5f8a/go.mod h1:emQhSYTXqB0xxjLITTw4EaWZ+8IIQYw+kx9GqNUKdLg=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
|
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
|
||||||
|
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||||
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
||||||
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a h1:i0+Se9S+2zL5CBxJouqn2Ej6UQMwH1c57ZB6DVnqck4=
|
||||||
|
github.com/graph-gophers/graphql-go v1.5.1-0.20230110080634-edea822f558a/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
|
||||||
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
|
||||||
|
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
|
github.com/hashicorp/go-plugin v1.4.10 h1:xUbmA4jC6Dq163/fWcp8P3JuHilrHHMLNRxzGQJ9hNk=
|
||||||
|
github.com/hashicorp/go-plugin v1.4.10/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0=
|
||||||
|
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
||||||
|
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
|
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
|
||||||
|
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
|
||||||
|
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
|
||||||
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
|
||||||
|
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
|
||||||
|
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d h1:/RJ/UV7M5c7L2TQ0KNm4yZxxFvC1nvRz/gY/Daa35aI=
|
||||||
|
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ=
|
||||||
|
github.com/mattermost/logr/v2 v2.0.16 h1:jnePX4cPskC3WDFvUardh/xZfxNdsFXbEERJQ1kUEDE=
|
||||||
|
github.com/mattermost/logr/v2 v2.0.16/go.mod h1:1dm/YhTpozsqANXxo5Pi5zYLBsal2xY0pX+JZNbzYJY=
|
||||||
|
github.com/mattermost/mattermost/server/public v0.0.8 h1:YFgI5zT2U5xOvrYMb7s1YtwuLGNFModi8XJMS1zZHWE=
|
||||||
|
github.com/mattermost/mattermost/server/public v0.0.8/go.mod h1:sgXQrYzs+IJy51mB8E8OBljagk2u3YwQRoYlBH5goiw=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||||
|
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||||
|
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||||
|
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||||
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
|
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
|
||||||
|
github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
|
||||||
|
github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||||
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
|
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
|
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
|
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/rudderlabs/analytics-go v3.3.3+incompatible h1:OG0XlKoXfr539e2t1dXtTB+Gr89uFW+OUNQBVhHIIBY=
|
||||||
|
github.com/rudderlabs/analytics-go v3.3.3+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30=
|
||||||
|
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
|
github.com/segmentio/backo-go v1.0.1 h1:68RQccglxZeyURy93ASB/2kc9QudzgIDexJ927N++y4=
|
||||||
|
github.com/segmentio/backo-go v1.0.1/go.mod h1:9/Rh6yILuLysoQnZ2oNooD2g7aBnvM7r/fNVxRNWfBc=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||||
|
github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=
|
||||||
|
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
|
github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=
|
||||||
|
github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=
|
||||||
|
github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||||
|
github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||||
|
github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=
|
||||||
|
github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=
|
||||||
|
github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=
|
||||||
|
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||||
|
github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=
|
||||||
|
github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=
|
||||||
|
github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=
|
||||||
|
github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=
|
||||||
|
github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||||
|
github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=
|
||||||
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
|
github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=
|
||||||
|
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||||
|
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||||
|
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||||
|
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
|
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||||
|
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||||
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
|
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
|
||||||
|
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
|
||||||
|
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
|
||||||
|
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
|
||||||
|
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||||
|
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||||
|
github.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME=
|
||||||
|
github.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=
|
||||||
|
github.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=
|
||||||
|
github.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=
|
||||||
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c h1:3lbZUMbMiGUW/LMkfsEABsc5zNT9+b1CvsJx47JzJ8g=
|
||||||
|
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
|
||||||
|
go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
|
||||||
|
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
||||||
|
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
|
||||||
|
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||||
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
|
google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=
|
||||||
|
google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||||
|
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||||
|
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
|
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||||
|
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||||
|
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||||
|
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||||
|
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=
|
||||||
|
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
|
||||||
|
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||||
45
plugin.json
Normal file
45
plugin.json
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"id": "ru.loop.plugin.template",
|
||||||
|
"name": "Template",
|
||||||
|
"description": "Template",
|
||||||
|
"icon_path": "assets/icon.svg",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"min_server_version": "9.11.0",
|
||||||
|
"server": {
|
||||||
|
"executables": {
|
||||||
|
"darwin-amd64": "server/dist/plugin-darwin-amd64",
|
||||||
|
"darwin-arm64": "server/dist/plugin-darwin-arm64",
|
||||||
|
"linux-amd64": "server/dist/plugin-linux-amd64",
|
||||||
|
"linux-arm64": "server/dist/plugin-linux-arm64",
|
||||||
|
"windows-amd64": "server/dist/plugin-windows-amd64.exe"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webapp": {
|
||||||
|
"bundle_path": "webapp/dist/main.js"
|
||||||
|
},
|
||||||
|
"settings_schema": {
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"key": "SentryUrl",
|
||||||
|
"display_name": "Sentry URL",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Base URL of your Sentry instance (e.g. https://sentry.io or self-hosted URL)",
|
||||||
|
"placeholder": "https://sentry.io"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SentryOrganisationName",
|
||||||
|
"display_name": "Sentry Organisation Name",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Sentry organisation slug",
|
||||||
|
"placeholder": "my-organisation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "SentryAuthToken",
|
||||||
|
"display_name": "Sentry Auth Token",
|
||||||
|
"type": "text",
|
||||||
|
"help_text": "Sentry API token with project:read permission",
|
||||||
|
"placeholder": "sntrys_XXXXXXXXXXXXXXXX"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
12
server/main.go
Normal file
12
server/main.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
plug "git.wilix.dev/loop/loop-plugin-starter-template/server/plugin"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
p := &plug.Plugin{}
|
||||||
|
p.InitApi()
|
||||||
|
plugin.ClientMain(p)
|
||||||
|
}
|
||||||
295
server/plugin/api.go
Normal file
295
server/plugin/api.go
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type httpResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Data interface{} `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentryTag struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentryExceptionValue struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
Stacktrace struct {
|
||||||
|
Frames []struct {
|
||||||
|
Filename string `json:"filename"`
|
||||||
|
Function string `json:"function"`
|
||||||
|
Module string `json:"module"`
|
||||||
|
Lineno int `json:"lineno"`
|
||||||
|
Colno int `json:"colno"`
|
||||||
|
AbsPath string `json:"abs_path"`
|
||||||
|
ContextLine string `json:"context_line"`
|
||||||
|
} `json:"frames"`
|
||||||
|
} `json:"stacktrace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentryException struct {
|
||||||
|
Values []SentryExceptionValue `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentryEvent struct {
|
||||||
|
Project int `json:"project"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Level string `json:"level"`
|
||||||
|
Culprit string `json:"culprit,omitempty"`
|
||||||
|
Logger string `json:"logger,omitempty"`
|
||||||
|
Platform string `json:"platform,omitempty"`
|
||||||
|
WebURL string `json:"web_url"`
|
||||||
|
IssueID string `json:"issue_id"`
|
||||||
|
Tags [][]string `json:"tags"` // ← вот здесь
|
||||||
|
Exception *SentryException `json:"exception,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SentryPayload struct {
|
||||||
|
Action string `json:"action"`
|
||||||
|
Data struct {
|
||||||
|
Event SentryEvent `json:"event"`
|
||||||
|
TriggeredRule string `json:"triggered_rule"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func levelToColor(level string) string {
|
||||||
|
switch strings.ToLower(level) {
|
||||||
|
case "fatal":
|
||||||
|
return "#B10DC9" // фиолетовый, критический
|
||||||
|
case "error":
|
||||||
|
return "#FF4136" // красный
|
||||||
|
case "warning":
|
||||||
|
return "#FF851B" // оранжевый
|
||||||
|
case "log":
|
||||||
|
return "#AAAAAA" // серый
|
||||||
|
case "info":
|
||||||
|
return "#0074D9" // синий
|
||||||
|
case "debug":
|
||||||
|
return "#2ECC40" // зелёный
|
||||||
|
default:
|
||||||
|
return "#AAAAAA" // серый для неизвестных
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTagFromArray(tags [][]string, key string) string {
|
||||||
|
for _, t := range tags {
|
||||||
|
if len(t) == 2 && t[0] == key {
|
||||||
|
return t[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatStacktrace(ex *SentryExceptionValue) string {
|
||||||
|
if ex == nil || len(ex.Stacktrace.Frames) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := make([]string, 0, len(ex.Stacktrace.Frames))
|
||||||
|
for _, f := range ex.Stacktrace.Frames {
|
||||||
|
lines = append(lines, fmt.Sprintf("%s:%d %s - %s", f.Filename, f.Lineno, f.Function, f.ContextLine))
|
||||||
|
}
|
||||||
|
return strings.Join(lines, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var payload SentryPayload
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
p.API.LogError("Failed to decode Sentry payload", "error", err.Error())
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := payload.Data.Event
|
||||||
|
|
||||||
|
// Найти канал по projectID
|
||||||
|
channelID, err := p.getChannelForProject(strconv.Itoa(event.Project))
|
||||||
|
if err != nil || channelID == "" {
|
||||||
|
p.API.LogWarn("No channel linked for project", "project", event.Project)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основные теги
|
||||||
|
environment := getTagFromArray(event.Tags, "environment")
|
||||||
|
release := getTagFromArray(event.Tags, "release")
|
||||||
|
user := getTagFromArray(event.Tags, "user")
|
||||||
|
|
||||||
|
// Составляем attachment
|
||||||
|
attachment := &model.SlackAttachment{
|
||||||
|
Color: levelToColor(event.Level),
|
||||||
|
Title: event.Title,
|
||||||
|
TitleLink: event.WebURL,
|
||||||
|
Text: event.Message,
|
||||||
|
Fields: []*model.SlackAttachmentField{
|
||||||
|
{Title: "Project ID", Value: strconv.Itoa(event.Project), Short: true},
|
||||||
|
{Title: "Issue ID", Value: event.IssueID, Short: true},
|
||||||
|
{Title: "Environment", Value: environment, Short: true},
|
||||||
|
{Title: "Level", Value: event.Level, Short: true},
|
||||||
|
{Title: "Culprit", Value: event.Culprit, Short: false},
|
||||||
|
{Title: "Logger", Value: event.Logger, Short: true},
|
||||||
|
{Title: "Platform", Value: event.Platform, Short: true},
|
||||||
|
{Title: "Release", Value: release, Short: true},
|
||||||
|
{Title: "User", Value: user, Short: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// if event.Exception != nil && len(event.Exception.Values) > 0 {
|
||||||
|
// for _, ex := range event.Exception.Values {
|
||||||
|
// attachment.Fields = append(attachment.Fields, &model.SlackAttachmentField{
|
||||||
|
// Title: "Exception",
|
||||||
|
// Value: fmt.Sprintf("Type: %s\nValue: %s\nStacktrace:\n%s", ex.Type, ex.Value, formatStacktrace(&ex)),
|
||||||
|
// Short: true,
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
for _, tag := range event.Tags {
|
||||||
|
if len(tag) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := tag[0]
|
||||||
|
value := tag[1]
|
||||||
|
|
||||||
|
if key == "environment" || key == "release" || key == "user" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attachment.Fields = append(attachment.Fields, &model.SlackAttachmentField{
|
||||||
|
Title: key,
|
||||||
|
Value: value,
|
||||||
|
Short: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем пост
|
||||||
|
post := &model.Post{
|
||||||
|
UserId: p.botUserID,
|
||||||
|
ChannelId: channelID,
|
||||||
|
Props: map[string]interface{}{
|
||||||
|
"attachments": []*model.SlackAttachment{attachment},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := p.API.CreatePost(post); err != nil {
|
||||||
|
p.API.LogError("Failed to create post", "error", err.Error())
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) InitApi() {
|
||||||
|
p.router = mux.NewRouter()
|
||||||
|
p.router.HandleFunc("/webhook", p.handleWebhook).Methods("POST")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !p.IsReady {
|
||||||
|
http.Error(w, "Plugin not ready", http.StatusServiceUnavailable)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.router.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) registerCommands() error {
|
||||||
|
command := &model.Command{
|
||||||
|
Trigger: "sentry",
|
||||||
|
DisplayName: "Sentry",
|
||||||
|
Description: "Manage Sentry alerts and integrations",
|
||||||
|
AutoComplete: true,
|
||||||
|
AutoCompleteDesc: "Available commands: help, link, unlink, list",
|
||||||
|
AutoCompleteHint: "help | link <project> | unlink <project> | list",
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.API.RegisterCommand(command); err != nil {
|
||||||
|
p.API.LogError("Failed to register Sentry command", "error", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) ExecuteCommand(
|
||||||
|
ctx *plugin.Context,
|
||||||
|
args *model.CommandArgs,
|
||||||
|
) (*model.CommandResponse, *model.AppError) {
|
||||||
|
|
||||||
|
split := strings.Fields(args.Command)
|
||||||
|
if len(split) < 2 {
|
||||||
|
return p.commandHelp(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch split[1] {
|
||||||
|
case "setup":
|
||||||
|
return p.commandSetup(args)
|
||||||
|
case "link":
|
||||||
|
return p.commandLink(args, split)
|
||||||
|
case "unlink":
|
||||||
|
return p.commandUnlink(args, split)
|
||||||
|
case "list":
|
||||||
|
return p.commandList(args)
|
||||||
|
case "help":
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return p.commandHelp(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) ensureBot() error {
|
||||||
|
const botUsername = "sentry"
|
||||||
|
|
||||||
|
user, appErr := p.API.GetUserByUsername(botUsername)
|
||||||
|
if appErr == nil && user != nil {
|
||||||
|
if !user.IsBot {
|
||||||
|
return fmt.Errorf("user @%s exists but is not a bot", botUsername)
|
||||||
|
}
|
||||||
|
p.botUserID = user.Id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bot := &model.Bot{
|
||||||
|
Username: botUsername,
|
||||||
|
DisplayName: "Sentry",
|
||||||
|
Description: "Sentry notifications bot",
|
||||||
|
}
|
||||||
|
|
||||||
|
createdBot, appErr := p.API.CreateBot(bot)
|
||||||
|
if appErr != nil {
|
||||||
|
p.API.LogError("Failed to create Sentry bot", "error", appErr.Error())
|
||||||
|
return appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
p.botUserID = createdBot.UserId
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) getChannelForProject(project string) (string, error) {
|
||||||
|
key := "sentry:project:" + project
|
||||||
|
|
||||||
|
data, appErr := p.API.KVGet(key)
|
||||||
|
if appErr != nil || data == nil {
|
||||||
|
return "", appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
278
server/plugin/commands.go
Normal file
278
server/plugin/commands.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost/server/public/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Plugin) fetchSentryProjectID(projectSlug string) (string, error) {
|
||||||
|
cfg := p.GetConfiguration()
|
||||||
|
|
||||||
|
if cfg.SentryUrl == "" || cfg.SentryOrganisationName == "" || cfg.SentryAuthToken == "" {
|
||||||
|
return "", errors.New("sentry is not configured")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := fmt.Sprintf(
|
||||||
|
"%s/api/0/projects/%s/%s/",
|
||||||
|
strings.TrimRight(cfg.SentryUrl, "/"),
|
||||||
|
cfg.SentryOrganisationName,
|
||||||
|
projectSlug,
|
||||||
|
)
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+cfg.SentryAuthToken)
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(resp.Body)
|
||||||
|
return "", fmt.Errorf("sentry api error (%d): %s", resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
var result struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.ID == "" {
|
||||||
|
return "", errors.New("project id not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.ID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) commandLink(args *model.CommandArgs, split []string) (*model.CommandResponse, *model.AppError) {
|
||||||
|
if len(split) < 3 {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "❌ Usage: /sentry link <project_slug>",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := p.GetConfiguration()
|
||||||
|
if cfg.SentryUrl == "" || cfg.SentryOrganisationName == "" || cfg.SentryAuthToken == "" {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "❌ Sentry is not configured. Please fill plugin settings.",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
projectSlug := split[2]
|
||||||
|
channelID := args.ChannelId
|
||||||
|
|
||||||
|
// fetch project id from Sentry
|
||||||
|
projectID, err := p.fetchSentryProjectID(projectSlug)
|
||||||
|
if err != nil {
|
||||||
|
p.API.LogError("Failed to fetch Sentry project", "project", projectSlug, "err", err.Error())
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "❌ Failed to fetch Sentry project: " + err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// projectID -> channel
|
||||||
|
if err := p.API.KVSet("sentry:project:"+projectID, []byte(channelID)); err != nil {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "❌ Failed to save project mapping",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// store list of linked projects
|
||||||
|
data, _ := p.API.KVGet("sentry:projects")
|
||||||
|
var projects []string
|
||||||
|
if data != nil {
|
||||||
|
projects = strings.Split(string(data), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, id := range projects {
|
||||||
|
if id == projectID {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
projects = append(projects, projectID)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = p.API.KVSet("sentry:projects", []byte(strings.Join(projects, ",")))
|
||||||
|
|
||||||
|
p.API.CreatePost(&model.Post{
|
||||||
|
UserId: p.botUserID,
|
||||||
|
ChannelId: channelID,
|
||||||
|
Message: fmt.Sprintf(
|
||||||
|
"✅ Sentry project `%s` (id: %s) linked to this channel",
|
||||||
|
projectSlug,
|
||||||
|
projectID,
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "🔗 Linked project `" + projectSlug + "`",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) commandUnlink(args *model.CommandArgs, split []string) (*model.CommandResponse, *model.AppError) {
|
||||||
|
if len(split) < 3 {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "❌ Usage: /sentry unlink <project_slug>",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
project := split[2]
|
||||||
|
|
||||||
|
// удаляем привязку project -> channel
|
||||||
|
p.API.KVDelete("sentry:project:" + project)
|
||||||
|
|
||||||
|
// обновляем список проектов
|
||||||
|
data, _ := p.API.KVGet("sentry:projects")
|
||||||
|
var projects []string
|
||||||
|
if data != nil {
|
||||||
|
projects = strings.Split(string(data), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
var newProjects []string
|
||||||
|
for _, p := range projects {
|
||||||
|
if p != project {
|
||||||
|
newProjects = append(newProjects, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.API.KVSet("sentry:projects", []byte(strings.Join(newProjects, ",")))
|
||||||
|
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "✅ Unlinked project `" + project + "`",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) commandList(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
|
||||||
|
// Для MVP мы перебираем все известные ключи в KV
|
||||||
|
// Так как KVListKeys нет, можно завести slice всех проектов,
|
||||||
|
// которые были когда-либо linked через /sentry link
|
||||||
|
// Эти проекты мы храним отдельно в ключе "sentry:projects"
|
||||||
|
data, _ := p.API.KVGet("sentry:projects")
|
||||||
|
var projects []string
|
||||||
|
if data != nil {
|
||||||
|
projects = strings.Split(string(data), ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(projects) == 0 {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "_No linked Sentry projects_",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines []string
|
||||||
|
|
||||||
|
for _, project := range projects {
|
||||||
|
channelData, _ := p.API.KVGet("sentry:project:" + project)
|
||||||
|
if channelData == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
channelID := string(channelData)
|
||||||
|
|
||||||
|
channel, err := p.API.GetChannel(channelID)
|
||||||
|
channelName := channelID
|
||||||
|
if err == nil && channel != nil {
|
||||||
|
channelName = "~" + channel.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, "• "+project+" → "+channelName)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := "🔗 **Linked Sentry projects:**\n\n" + strings.Join(lines, "\n")
|
||||||
|
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: text,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) commandHelp() *model.CommandResponse {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: `**Sentry plugin commands**
|
||||||
|
|
||||||
|
• /sentry link <project_slug> — link project to channel
|
||||||
|
• /sentry help — show help`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) commandSetup(args *model.CommandArgs) (*model.CommandResponse, *model.AppError) {
|
||||||
|
if args.TriggerId == "" {
|
||||||
|
return &model.CommandResponse{
|
||||||
|
ResponseType: model.CommandResponseTypeEphemeral,
|
||||||
|
Text: "This command must be run from Mattermost UI",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
channels, _ := p.API.GetChannelsForTeamForUser(
|
||||||
|
args.TeamId,
|
||||||
|
args.UserId,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
|
||||||
|
options := []*model.PostActionOptions{}
|
||||||
|
for _, ch := range channels {
|
||||||
|
options = append(options, &model.PostActionOptions{
|
||||||
|
Text: ch.DisplayName,
|
||||||
|
Value: ch.Id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
modal := &model.Dialog{
|
||||||
|
Title: "Sentry Setup",
|
||||||
|
CallbackId: "sentry_setup",
|
||||||
|
SubmitLabel: "Save",
|
||||||
|
Elements: []model.DialogElement{
|
||||||
|
{
|
||||||
|
DisplayName: "Channel",
|
||||||
|
Name: "channel_id",
|
||||||
|
Type: "select",
|
||||||
|
Options: options,
|
||||||
|
Default: args.ChannelId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DisplayName: "Sentry Project",
|
||||||
|
Name: "project",
|
||||||
|
Type: "text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
req := model.OpenDialogRequest{
|
||||||
|
TriggerId: args.TriggerId,
|
||||||
|
URL: "/plugins/" + "ru.loop.plugin.template" + "/dialog/submit",
|
||||||
|
Dialog: *modal,
|
||||||
|
}
|
||||||
|
|
||||||
|
if appErr := p.API.OpenInteractiveDialog(req); appErr != nil {
|
||||||
|
return nil, appErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.CommandResponse{}, nil
|
||||||
|
}
|
||||||
78
server/plugin/configuration.go
Normal file
78
server/plugin/configuration.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Configuration struct {
|
||||||
|
SentryUrl string
|
||||||
|
SentryOrganisationName string
|
||||||
|
SentryAuthToken string
|
||||||
|
// Add your configuration fields here
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone shallow copies the Configuration. Your implementation may require a deep copy if
|
||||||
|
// your Configuration has reference types.
|
||||||
|
func (c *Configuration) Clone() *Configuration {
|
||||||
|
var clone = *c
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfiguration retrieves the active Configuration under lock, making it safe to use
|
||||||
|
// concurrently. The active Configuration may change underneath the client of this method, but
|
||||||
|
// the struct returned by this API call is considered immutable.
|
||||||
|
func (p *Plugin) GetConfiguration() *Configuration {
|
||||||
|
p.configurationLock.RLock()
|
||||||
|
defer p.configurationLock.RUnlock()
|
||||||
|
|
||||||
|
if p.configuration == nil {
|
||||||
|
return &Configuration{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetConfiguration replaces the active Configuration under lock.
|
||||||
|
//
|
||||||
|
// Do not call SetConfiguration while holding the configurationLock, as sync.Mutex is not
|
||||||
|
// reentrant. In particular, avoid using the plugin API entirely, as this may in turn trigger a
|
||||||
|
// hook back into the plugin. If that hook attempts to acquire this lock, a deadlock may occur.
|
||||||
|
//
|
||||||
|
// This method panics if SetConfiguration is called with the existing Configuration. This almost
|
||||||
|
// certainly means that the Configuration was modified without being cloned and may result in
|
||||||
|
// an unsafe access.
|
||||||
|
func (p *Plugin) SetConfiguration(configuration *Configuration) {
|
||||||
|
p.configurationLock.Lock()
|
||||||
|
defer p.configurationLock.Unlock()
|
||||||
|
p.API.LogInfo("Setting configuration")
|
||||||
|
|
||||||
|
if configuration != nil && p.configuration == configuration {
|
||||||
|
// Ignore assignment if the Configuration struct is empty. Go will optimize the
|
||||||
|
// allocation for same to point at the same memory address, breaking the check
|
||||||
|
// above.
|
||||||
|
if reflect.ValueOf(*configuration).NumField() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.API.LogInfo("Panic in SetConfiguration")
|
||||||
|
panic("SetConfiguration called with the existing Configuration")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.configuration = configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnConfigurationChange is invoked when Configuration changes may have been made.
|
||||||
|
func (p *Plugin) OnConfigurationChange() error {
|
||||||
|
p.API.LogInfo("OnConfigurationChange")
|
||||||
|
var configuration = new(Configuration)
|
||||||
|
// Load the public Configuration fields from the Mattermost server Configuration.
|
||||||
|
err := p.API.LoadPluginConfiguration(configuration)
|
||||||
|
if err == nil {
|
||||||
|
p.SetConfiguration(configuration)
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.Wrap(err, "failed to load plugin Configuration")
|
||||||
|
}
|
||||||
|
}
|
||||||
65
server/plugin/plugin.go
Normal file
65
server/plugin/plugin.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.wilix.dev/loop/loop-plugin-starter-template/server/telemetry"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/mattermost/mattermost/server/public/plugin"
|
||||||
|
"github.com/mattermost/mattermost/server/public/pluginapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
var buildHash string
|
||||||
|
var rudderWriteKey string
|
||||||
|
var rudderDataplaneURL string
|
||||||
|
|
||||||
|
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
||||||
|
type Plugin struct {
|
||||||
|
plugin.MattermostPlugin
|
||||||
|
IsReady bool
|
||||||
|
bundlePath string
|
||||||
|
configurationLock sync.RWMutex
|
||||||
|
configuration *Configuration
|
||||||
|
sdk *pluginapi.Client
|
||||||
|
router *mux.Router
|
||||||
|
mut sync.RWMutex
|
||||||
|
telemetry *telemetry.Client
|
||||||
|
botUserID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) OnActivate() error {
|
||||||
|
p.API.LogInfo("Activating Sentry plugin...")
|
||||||
|
p.sdk = pluginapi.NewClient(p.API, p.Driver)
|
||||||
|
|
||||||
|
if p.router == nil {
|
||||||
|
p.InitApi()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.ensureBot(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
configuration := p.GetConfiguration()
|
||||||
|
p.configuration = configuration
|
||||||
|
|
||||||
|
bundlePath, err := p.API.GetBundlePath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.bundlePath = bundlePath
|
||||||
|
|
||||||
|
if err := p.registerCommands(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.IsReady = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) OnDeactivate() error {
|
||||||
|
p.API.LogInfo("Deactivating template plugin...")
|
||||||
|
if err := p.uninitTelemetry(); err != nil {
|
||||||
|
p.API.LogError(err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
3
server/plugin/store.go
Normal file
3
server/plugin/store.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package plugin
|
||||||
|
|
||||||
|
// Add your store utility functions here
|
||||||
47
server/plugin/telemetry.go
Normal file
47
server/plugin/telemetry.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.wilix.dev/loop/loop-plugin-starter-template/server/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p *Plugin) uninitTelemetry() error {
|
||||||
|
p.mut.Lock()
|
||||||
|
defer p.mut.Unlock()
|
||||||
|
if p.telemetry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.telemetry.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) initTelemetry(enableDiagnostics *bool) error {
|
||||||
|
p.mut.Lock()
|
||||||
|
defer p.mut.Unlock()
|
||||||
|
if p.telemetry == nil && enableDiagnostics != nil && *enableDiagnostics {
|
||||||
|
p.API.LogDebug("Initializing telemetry")
|
||||||
|
// setup telemetry
|
||||||
|
client, err := telemetry.NewClient(telemetry.ClientConfig{
|
||||||
|
WriteKey: rudderWriteKey,
|
||||||
|
DataplaneURL: rudderDataplaneURL,
|
||||||
|
DiagnosticID: p.API.GetDiagnosticId(),
|
||||||
|
DefaultProps: map[string]any{
|
||||||
|
"ServerVersion": p.API.GetServerVersion(),
|
||||||
|
"PluginBuild": buildHash,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.telemetry = client
|
||||||
|
} else if p.telemetry != nil && (enableDiagnostics == nil || !*enableDiagnostics) {
|
||||||
|
p.API.LogDebug("Deinitializing telemetry")
|
||||||
|
// destroy telemetry
|
||||||
|
if err := p.telemetry.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.telemetry = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
76
server/telemetry/telemetry.go
Normal file
76
server/telemetry/telemetry.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) 2020-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package telemetry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/rudderlabs/analytics-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientConfig struct {
|
||||||
|
WriteKey string
|
||||||
|
DataplaneURL string
|
||||||
|
DiagnosticID string
|
||||||
|
DefaultProps map[string]any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientConfig) isValid() error {
|
||||||
|
if c.WriteKey == "" {
|
||||||
|
return fmt.Errorf("WriteKey should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DataplaneURL == "" {
|
||||||
|
return fmt.Errorf("DataplaneURL should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.DiagnosticID == "" {
|
||||||
|
return fmt.Errorf("DiagnosticID should not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
config ClientConfig
|
||||||
|
client analytics.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(config ClientConfig) (*Client, error) {
|
||||||
|
if err := config.isValid(); err != nil {
|
||||||
|
return nil, fmt.Errorf("telemetry: config validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
config: config,
|
||||||
|
client: analytics.New(config.WriteKey, config.DataplaneURL),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Track(event string, props map[string]any, ctx *analytics.Context) error {
|
||||||
|
if props == nil {
|
||||||
|
props = map[string]any{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range c.config.DefaultProps {
|
||||||
|
props[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.client.Enqueue(analytics.Track{
|
||||||
|
Event: event,
|
||||||
|
UserId: c.config.DiagnosticID,
|
||||||
|
Properties: props,
|
||||||
|
Context: ctx,
|
||||||
|
}); err != nil {
|
||||||
|
return fmt.Errorf("telemetry: failed to track event: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() error {
|
||||||
|
if err := c.client.Close(); err != nil {
|
||||||
|
return fmt.Errorf("telemetry: failed to close client: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
31
server/utils/rest_client.go
Normal file
31
server/utils/rest_client.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MakeRequest(method, url string, body interface{}) (*http.Response, error) {
|
||||||
|
var reqBody io.Reader
|
||||||
|
if body != nil {
|
||||||
|
jsonBody, err := json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
reqBody = bytes.NewBuffer(jsonBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(method, url, reqBody)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if body != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
return client.Do(req)
|
||||||
|
}
|
||||||
47
webapp/.eslintrc.json
Normal file
47
webapp/.eslintrc.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"typescript": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react": {
|
||||||
|
"version": "17.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:react-hooks/recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": [
|
||||||
|
"react",
|
||||||
|
"import",
|
||||||
|
"@typescript-eslint",
|
||||||
|
"unused-imports"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"impliedStrict": true,
|
||||||
|
"modules": true,
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"jquery": false,
|
||||||
|
"es6": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"no-unused-vars": "warn",
|
||||||
|
"no-unused-expressions": "warn",
|
||||||
|
"unused-imports/no-unused-imports": "warn",
|
||||||
|
"import/no-named-as-default": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
webapp/.gitignore
vendored
Normal file
3
webapp/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.eslintcache
|
||||||
|
junit.xml
|
||||||
|
node_modules
|
||||||
1
webapp/.npmrc
Normal file
1
webapp/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
save-exact=true
|
||||||
1
webapp/.yarnrc.yml
Normal file
1
webapp/.yarnrc.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
nodeLinker: node-modules
|
||||||
4
webapp/i18n/en.json
Normal file
4
webapp/i18n/en.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
3
webapp/i18n/ru.json
Normal file
3
webapp/i18n/ru.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
63
webapp/package.json
Normal file
63
webapp/package.json
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22",
|
||||||
|
"npm": ">=10"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite",
|
||||||
|
"lint": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --cache",
|
||||||
|
"fix": "eslint --ignore-pattern node_modules --ignore-pattern dist --ext .js --ext .jsx --ext tsx --ext ts . --quiet --fix --cache",
|
||||||
|
"check-types": "tsc"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/plugin-transform-modules-umd": "^7.27.1",
|
||||||
|
"@babel/preset-typescript": "^7.27.1",
|
||||||
|
"@emotion/core": "10.0.28",
|
||||||
|
"@eslint/js": "^9.25.1",
|
||||||
|
"@rollup/plugin-replace": "^6.0.2",
|
||||||
|
"@types/node": "14.0.20",
|
||||||
|
"@types/react": "^17",
|
||||||
|
"@types/react-dom": "^17",
|
||||||
|
"@types/react-intl": "3.0.0",
|
||||||
|
"@types/react-redux": "7.1.9",
|
||||||
|
"@types/react-router-dom": "5.1.5",
|
||||||
|
"@types/react-transition-group": "4.4.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "3.6.0",
|
||||||
|
"@typescript-eslint/parser": "3.6.0",
|
||||||
|
"@vitejs/plugin-react": "^4.4.1",
|
||||||
|
"bootstrap": "5.1.3",
|
||||||
|
"core-js": "3.6.5",
|
||||||
|
"css-loader": "7.1.2",
|
||||||
|
"eslint": "^9.26.0",
|
||||||
|
"eslint-plugin-import": "2.22.0",
|
||||||
|
"eslint-plugin-react": "7.20.3",
|
||||||
|
"eslint-plugin-react-hooks": "4.0.6",
|
||||||
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
|
"file-loader": "6.0.0",
|
||||||
|
"identity-obj-proxy": "3.0.0",
|
||||||
|
"loop-plugin-sdk": "https://artifacts.wilix.dev/repository/npm-public-loop/loop-plugin-sdk/-/loop-plugin-sdk-0.1.6.tgz",
|
||||||
|
"moment-timezone": "^0.5.48",
|
||||||
|
"react": "17.0.2",
|
||||||
|
"react-bootstrap": "^2.10.10",
|
||||||
|
"react-dom": "17.0.2",
|
||||||
|
"react-intl": "6.8.9",
|
||||||
|
"react-redux": "8.1.3",
|
||||||
|
"redux": "4.2.1",
|
||||||
|
"sass": "1.86.0",
|
||||||
|
"sass-loader": "13.0.2",
|
||||||
|
"style-loader": "1.2.1",
|
||||||
|
"styled-components": "6.1.16",
|
||||||
|
"typescript": "4.9.5",
|
||||||
|
"typescript-eslint": "^8.31.1",
|
||||||
|
"vite": "^6.3.1",
|
||||||
|
"vite-plugin-css-injected-by-js": "^3.5.2",
|
||||||
|
"vite-plugin-externals": "^0.6.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.1",
|
||||||
|
"redux-batched-actions": "^0.5.0"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538"
|
||||||
|
}
|
||||||
19
webapp/src/components/RootComponent.tsx
Normal file
19
webapp/src/components/RootComponent.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import manifest from '../manifest';
|
||||||
|
import usePlugin from '../utils/usePlugin';
|
||||||
|
|
||||||
|
const RootComponent: React.FC = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const plugin = usePlugin();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Add your root component content here */}
|
||||||
|
<p>Template Plugin Root Component</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RootComponent
|
||||||
1
webapp/src/components/global.scss
Normal file
1
webapp/src/components/global.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
// Add your global styles here
|
||||||
14
webapp/src/constants/assets.ts
Normal file
14
webapp/src/constants/assets.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { getPluginAssetsPath } from '../utils/utils';
|
||||||
|
|
||||||
|
export const basePath = getPluginAssetsPath();
|
||||||
|
|
||||||
|
// Add your assets configuration here
|
||||||
|
export type AssetsType = {
|
||||||
|
// Define your asset types
|
||||||
|
}
|
||||||
|
|
||||||
|
const assets: AssetsType = {
|
||||||
|
// Add your assets here
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export default assets
|
||||||
5
webapp/src/constants/index.js
Normal file
5
webapp/src/constants/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Svgs from './svgs.js';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Svgs,
|
||||||
|
};
|
||||||
1
webapp/src/env.d.ts
vendored
Normal file
1
webapp/src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
17
webapp/src/icons/CompassIcons.tsx
Normal file
17
webapp/src/icons/CompassIcons.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
icon: string,
|
||||||
|
className?: string,
|
||||||
|
} & React.HTMLAttributes<HTMLElement>
|
||||||
|
|
||||||
|
export default function CompassIcon({icon, className, ...rest}: Props): JSX.Element {
|
||||||
|
// All compass icon classes start with icon,
|
||||||
|
// so not expecting that prefix in props.
|
||||||
|
return (
|
||||||
|
<i
|
||||||
|
className={`CompassIcon icon-${icon} ${className}`}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
26
webapp/src/index.tsx
Normal file
26
webapp/src/index.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { PluginRegistry } from 'loop-plugin-sdk';
|
||||||
|
import { Store, Action } from 'redux';
|
||||||
|
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||||
|
import 'loop-plugin-sdk/window'
|
||||||
|
import manifest from './manifest';
|
||||||
|
import registerApp from './registerApp';
|
||||||
|
|
||||||
|
export default class Plugin {
|
||||||
|
// @ts-ignore
|
||||||
|
public store: Store;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
public registry: PluginRegistry
|
||||||
|
|
||||||
|
public onStoreChanged: any;
|
||||||
|
public uninitialize: any;
|
||||||
|
|
||||||
|
public async initialize(registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>) {
|
||||||
|
this.store = store;
|
||||||
|
this.registry = registry;
|
||||||
|
|
||||||
|
await registerApp(this, registry, store)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.registerPlugin(manifest.id, new Plugin());
|
||||||
5
webapp/src/manifest.ts
Normal file
5
webapp/src/manifest.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import manifest from '../../plugin.json';
|
||||||
|
|
||||||
|
export default manifest;
|
||||||
|
export const id = manifest.id;
|
||||||
|
export const version = manifest.version;
|
||||||
50
webapp/src/registerApp.tsx
Normal file
50
webapp/src/registerApp.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { PluginRegistry } from 'loop-plugin-sdk';
|
||||||
|
import { getCurrentUserLocale } from 'loop-plugin-sdk/loop/redux/selectors/entities/i18n';
|
||||||
|
import { Action, Store } from 'redux';
|
||||||
|
import RootComponent from './components/RootComponent';
|
||||||
|
import Plugin from './index';
|
||||||
|
import manifest, { id as pluginId } from './manifest';
|
||||||
|
import reducer from './store/reducers';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { GlobalStatePlugin } from './types/store';
|
||||||
|
import { getTranslations } from './utils/utils';
|
||||||
|
import { Provider, useSelector } from 'react-redux';
|
||||||
|
import { IntlProvider } from 'react-intl';
|
||||||
|
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||||
|
|
||||||
|
export const pluginStoreId = `plugins-${pluginId}`;
|
||||||
|
|
||||||
|
export default async function Initialize(plugin: Plugin, registry: PluginRegistry, store: Store<GlobalState, Action<Record<string, unknown>>>) {
|
||||||
|
const Providers: React.FC<React.PropsWithChildren<any>> = ({ children }) => {
|
||||||
|
const locale = useSelector((state) => getCurrentUserLocale(state as GlobalState) || 'en');
|
||||||
|
return (
|
||||||
|
<IntlProvider locale={locale} key={locale} messages={getTranslations(locale)}>
|
||||||
|
<Provider store={store}>{children}</Provider>
|
||||||
|
</IntlProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const regIds: string[] = [];
|
||||||
|
|
||||||
|
registry.registerReducer(reducer);
|
||||||
|
registry.registerTranslations(getTranslations);
|
||||||
|
|
||||||
|
plugin.uninitialize = () => {
|
||||||
|
// Add cleanup logic here
|
||||||
|
};
|
||||||
|
|
||||||
|
const reinit = () => {
|
||||||
|
if (regIds.length > 0) {
|
||||||
|
regIds.forEach(regId => registry.unregisterComponent(regId));
|
||||||
|
regIds.splice(0, regIds.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
regIds.push(registry.registerRootComponent(() => (
|
||||||
|
<Providers>
|
||||||
|
<RootComponent />
|
||||||
|
</Providers>
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
reinit();
|
||||||
|
}
|
||||||
13
webapp/src/store/actions.ts
Normal file
13
webapp/src/store/actions.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { DispatchFunc } from '../types/store';
|
||||||
|
import { ACTIONS } from './reducers';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
|
// Add your action creators here
|
||||||
|
export function exampleAction(data: string) {
|
||||||
|
return ((dispatch: DispatchFunc) => {
|
||||||
|
dispatch({
|
||||||
|
type: ACTIONS.EXAMPLE_ACTION,
|
||||||
|
data: { example: data }
|
||||||
|
});
|
||||||
|
}) as unknown as AnyAction;
|
||||||
|
}
|
||||||
37
webapp/src/store/reducers.ts
Normal file
37
webapp/src/store/reducers.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { PluginStore } from '../types/store';
|
||||||
|
|
||||||
|
export type ActionType = {
|
||||||
|
type: ACTIONS
|
||||||
|
data: PluginActionData
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ACTIONS {
|
||||||
|
// Add your action types here
|
||||||
|
EXAMPLE_ACTION = 'EXAMPLE_ACTION',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PluginActionData = {
|
||||||
|
// Add your action data types here
|
||||||
|
example?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmptyState: PluginStore = {
|
||||||
|
// Add your initial state here
|
||||||
|
}
|
||||||
|
|
||||||
|
function pluginState(state: PluginStore = EmptyState, { type, data }: ActionType): PluginStore {
|
||||||
|
switch (type) {
|
||||||
|
case ACTIONS.EXAMPLE_ACTION:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
// Handle action
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
pluginState,
|
||||||
|
});
|
||||||
15
webapp/src/types/global.d.ts
vendored
Normal file
15
webapp/src/types/global.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Plugin from '../index';
|
||||||
|
|
||||||
|
declare module "*.module.css";
|
||||||
|
declare module "*.module.scss";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const __PLUGIN_DEV__: boolean;
|
||||||
|
const __PLUGIN_COMPONENTS_HOST__: string | null;
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
plugins: Record<string, Plugin | unknown>;
|
||||||
|
basename: string;
|
||||||
|
desktopAPI?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
webapp/src/types/store.ts
Normal file
16
webapp/src/types/store.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||||
|
import { ActionType } from '../store/reducers';
|
||||||
|
|
||||||
|
export type PluginStore = {
|
||||||
|
// Add your store state here
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GlobalStatePlugin = GlobalState & {
|
||||||
|
[name: string]: {
|
||||||
|
pluginState: PluginStore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetStateType = () => GlobalStatePlugin
|
||||||
|
|
||||||
|
export type DispatchFunc = (action: ActionType) => any;
|
||||||
1
webapp/src/types/telemetry.ts
Normal file
1
webapp/src/types/telemetry.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
// trackEvent: (event: Telemetry.Event, source: Telemetry.Source, props?: Record<string, string>) => void,
|
||||||
39
webapp/src/utils/api.ts
Normal file
39
webapp/src/utils/api.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Client4 from 'loop-plugin-sdk/loop/client/client4';
|
||||||
|
import { id as pluginId } from '../manifest';
|
||||||
|
|
||||||
|
export type StdApiResp<T = undefined> = {
|
||||||
|
status: string
|
||||||
|
error?: string
|
||||||
|
data?: T
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApiClient {
|
||||||
|
client = new Client4();
|
||||||
|
|
||||||
|
async exampleRequest(): Promise<StdApiResp<{ message: string }>> {
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
return await this.client.doFetch<StdApiResp<{ message: string }>>(`/plugins/${pluginId}/example`, {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return {status: 'error', error: error as string};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async apiPingServerStatus(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await this.client.ping();
|
||||||
|
return response.status === 'OK';
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Ошибка пинга сервера:', err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiClient = new ApiClient();
|
||||||
|
|
||||||
|
export default apiClient
|
||||||
18
webapp/src/utils/useIsDarkTheme.ts
Normal file
18
webapp/src/utils/useIsDarkTheme.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { getTheme } from 'loop-plugin-sdk/loop/redux/selectors/entities/preferences';
|
||||||
|
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { isDarkTheme } from './colorUtils';
|
||||||
|
|
||||||
|
const useIsDarkTheme = () => {
|
||||||
|
const [isDark, setIsDark] = React.useState(false);
|
||||||
|
const theme = useSelector((state: GlobalState) => getTheme(state))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsDark(isDarkTheme());
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
|
return isDark
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useIsDarkTheme
|
||||||
6
webapp/src/utils/usePlugin.ts
Normal file
6
webapp/src/utils/usePlugin.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Plugin from '../index';
|
||||||
|
import { id as pluginId } from '../manifest';
|
||||||
|
|
||||||
|
export default function usePlugin() {
|
||||||
|
return window.plugins[pluginId] as Plugin
|
||||||
|
}
|
||||||
97
webapp/src/utils/utils.ts
Normal file
97
webapp/src/utils/utils.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { WebSocketClient } from 'loop-plugin-sdk/loop/client';
|
||||||
|
import { DispatchFunc } from 'loop-plugin-sdk/loop/redux/types/actions';
|
||||||
|
import { GlobalState } from 'loop-plugin-sdk/loop/types/store';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
import en from '../../i18n/en.json';
|
||||||
|
import ru from '../../i18n/ru.json';
|
||||||
|
import Plugin from '../index';
|
||||||
|
import manifest, { id as pluginId } from '../manifest';
|
||||||
|
import { pluginStoreId } from '../registerApp';
|
||||||
|
import { GlobalStatePlugin } from '../types/store';
|
||||||
|
|
||||||
|
export function getPluginAssetsPath() {
|
||||||
|
return `${window.basename || ''}/plugins/${pluginId}/assets`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlugin(): Plugin {
|
||||||
|
return window.plugins[manifest.id] as Plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isDesktopApp(): boolean {
|
||||||
|
return window.navigator.userAgent.indexOf('Electron') !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWebappUtils() {
|
||||||
|
let utils;
|
||||||
|
try {
|
||||||
|
utils = window.opener ? window.opener.WebappUtils : window.WebappUtils;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductApi = {
|
||||||
|
selectRhsPost: (postId: string) => (dispatch: DispatchFunc) => AnyAction
|
||||||
|
closeRhs: () => (dispatch: DispatchFunc) => AnyAction
|
||||||
|
getIsRhsOpen: (state: GlobalState) => boolean
|
||||||
|
getRhsSelectedPostId: (state: GlobalState) => string
|
||||||
|
useWebSocketClient: () => WebSocketClient
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getProductApi(): ProductApi {
|
||||||
|
let utils: any;
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
utils = window.opener ? window.opener.ProductApi : window['ProductApi'];
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
return utils;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPluginStoreFromState(state: GlobalStatePlugin) {
|
||||||
|
return state[pluginStoreId].pluginState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTranslations(locale: string) {
|
||||||
|
switch (locale) {
|
||||||
|
case "en":
|
||||||
|
return en;
|
||||||
|
case "ru":
|
||||||
|
return ru;
|
||||||
|
}
|
||||||
|
return en;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRandomInt(min: number, max: number) {
|
||||||
|
min = Math.ceil(min);
|
||||||
|
max = Math.floor(max);
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function waitForServer(
|
||||||
|
pingFn: () => Promise<any>,
|
||||||
|
interval = 2000,
|
||||||
|
maxAttempts = 10
|
||||||
|
): Promise<void> {
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const check = async () => {
|
||||||
|
attempts++;
|
||||||
|
const ok = await pingFn();
|
||||||
|
if (ok) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts >= maxAttempts) {
|
||||||
|
reject(new Error("Не удалось подключиться после нескольких попыток"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setTimeout(check, interval);
|
||||||
|
};
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
}
|
||||||
35
webapp/tsconfig.json
Normal file
35
webapp/tsconfig.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"module": "es2020",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"baseUrl": "./src",
|
||||||
|
"typeRoots": [ "./src/types", "./node_modules/@types"],
|
||||||
|
"types": ["vite/client"],
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"!node_modules/@types"
|
||||||
|
]
|
||||||
|
}
|
||||||
131
webapp/vite.config.ts
Normal file
131
webapp/vite.config.ts
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import replace from '@rollup/plugin-replace';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
import { viteExternalsPlugin } from 'vite-plugin-externals';
|
||||||
|
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js';
|
||||||
|
import manifest from './src/manifest';
|
||||||
|
|
||||||
|
const isDev = ['development', 'develop'].includes(process.env.NODE_ENV);
|
||||||
|
|
||||||
|
console.warn('Vite building in ', isDev ? 'DEVELOPER MODE' : 'PRODUCTION MODE');
|
||||||
|
|
||||||
|
const optimizeDepsInclude = [
|
||||||
|
'**/*.scss',
|
||||||
|
];
|
||||||
|
// В dev: подготавливаем react/react-dom/react-redux как ESM-шимы
|
||||||
|
if (isDev) {
|
||||||
|
optimizeDepsInclude.push('react-redux')
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
...(isDev ? [
|
||||||
|
viteExternalsPlugin({
|
||||||
|
react: 'React',
|
||||||
|
'react-dom': 'ReactDOM',
|
||||||
|
redux: 'Redux',
|
||||||
|
'react-redux': 'ReactRedux',
|
||||||
|
'prop-types': 'PropTypes',
|
||||||
|
'react-bootstrap': 'ReactBootstrap',
|
||||||
|
'react-router-dom': 'ReactRouterDom',
|
||||||
|
}),
|
||||||
|
] : [
|
||||||
|
]),
|
||||||
|
|
||||||
|
// 2) В dev включаем плагин React (он генерит preamble + jsx runtime + fast refresh)
|
||||||
|
...(isDev ? [react()] : []),
|
||||||
|
|
||||||
|
cssInjectedByJsPlugin(),
|
||||||
|
replace({
|
||||||
|
// Переопределяем NODE_ENV внутри бандла
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'),
|
||||||
|
preventAssignment: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Трансформация JSX через esbuild
|
||||||
|
esbuild: {
|
||||||
|
jsx: 'transform',
|
||||||
|
jsxFactory: 'React.createElement',
|
||||||
|
jsxFragment: 'React.Fragment',
|
||||||
|
},
|
||||||
|
|
||||||
|
// В dev: подготавливаем react/react-dom/react-redux как ESM-шимы
|
||||||
|
optimizeDeps: {
|
||||||
|
include: optimizeDepsInclude,
|
||||||
|
},
|
||||||
|
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
modules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modules: {
|
||||||
|
localsConvention: 'camelCaseOnly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'src/index.tsx'),
|
||||||
|
formats: ['umd'],
|
||||||
|
name: manifest.id,
|
||||||
|
cssFileName: manifest.id,
|
||||||
|
fileName: () => 'main.js',
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
// В проде всё это выносится в глобальные переменные Mattermost
|
||||||
|
external: [
|
||||||
|
'react',
|
||||||
|
'react-dom',
|
||||||
|
'redux',
|
||||||
|
'react-redux',
|
||||||
|
'prop-types',
|
||||||
|
'react-bootstrap',
|
||||||
|
'react-router-dom',
|
||||||
|
'core-js',
|
||||||
|
'react-intl',
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
inlineDynamicImports: true,
|
||||||
|
manualChunks: undefined,
|
||||||
|
globals: {
|
||||||
|
react: 'React',
|
||||||
|
'react-dom': 'ReactDOM',
|
||||||
|
redux: 'Redux',
|
||||||
|
'react-redux': 'ReactRedux',
|
||||||
|
'prop-types': 'PropTypes',
|
||||||
|
'react-bootstrap': 'ReactBootstrap',
|
||||||
|
'react-router-dom': 'ReactRouterDom',
|
||||||
|
'react-intl': 'ReactIntl',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
port: 3001,
|
||||||
|
cors: true,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
// ----------------------------------------------------
|
||||||
|
// HMR-настройка для плагина, загружаемого в Loop
|
||||||
|
hmr: {
|
||||||
|
protocol: 'ws', // WebSocket
|
||||||
|
host: 'localhost', // где запущен vite dev
|
||||||
|
port: 3001, // тот же порт
|
||||||
|
clientPort: 3001, // куда клиент будет подключаться
|
||||||
|
},
|
||||||
|
// ----------------------------------------------------
|
||||||
|
},
|
||||||
|
|
||||||
|
define: {
|
||||||
|
__PLUGIN_DEV__: isDev,
|
||||||
|
__PLUGIN_COMPONENTS_HOST__: isDev
|
||||||
|
? JSON.stringify('http://localhost:3001')
|
||||||
|
: JSON.stringify(null),
|
||||||
|
},
|
||||||
|
});
|
||||||
7063
webapp/yarn.lock
Normal file
7063
webapp/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user