mirror of
https://github.com/OliveTin/OliveTin
synced 2025-12-11 08:35:37 +00:00
Compare commits
93 Commits
2023.03.24
...
2023.12.21
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b0e414932 | ||
|
|
00927f3ba3 | ||
|
|
b0faecfa75 | ||
|
|
c15449f99a | ||
|
|
68f04c0912 | ||
|
|
c3c010443f | ||
|
|
15c8abf3d6 | ||
|
|
d0f74c1ab7 | ||
|
|
ca921c1890 | ||
|
|
3b60bbce0a | ||
|
|
8f6b384fe6 | ||
|
|
4d04264caa | ||
|
|
912fd8089e | ||
|
|
5739091773 | ||
|
|
a09c278585 | ||
|
|
a7fb49a11b | ||
|
|
3db8ae53b5 | ||
|
|
311f9a1d00 | ||
|
|
50204f8180 | ||
|
|
d639a802dc | ||
|
|
f41eafe3bd | ||
|
|
8b080eb3cc | ||
|
|
268d8a3a90 | ||
|
|
44d6c40c27 | ||
|
|
9522e25b1d | ||
|
|
db475895ca | ||
|
|
dc33509127 | ||
|
|
2ada67be04 | ||
|
|
77b17604f3 | ||
|
|
d169b3f2b1 | ||
|
|
c8210568eb | ||
|
|
2ea6430a3b | ||
|
|
ac5f997f85 | ||
|
|
e1930c0899 | ||
|
|
020281c1e6 | ||
|
|
9dc81a6280 | ||
|
|
a9906addff | ||
|
|
37269cef02 | ||
|
|
186ec00de7 | ||
|
|
abd13b756c | ||
|
|
6851683d94 | ||
|
|
2b4a3ab137 | ||
|
|
6d21bbe03a | ||
|
|
6f4a0e68a7 | ||
|
|
3de0f38049 | ||
|
|
216ccbfcef | ||
|
|
66b012cd55 | ||
|
|
8339bd3cb1 | ||
|
|
e93c62b06d | ||
|
|
43ceb33b53 | ||
|
|
ae0b45c308 | ||
|
|
bbaeff00f3 | ||
|
|
75a9697586 | ||
|
|
77bd37ca90 | ||
|
|
3a44a6a3b4 | ||
|
|
604d956d0c | ||
|
|
822327edfc | ||
|
|
3ea14f1353 | ||
|
|
7dbc077f76 | ||
|
|
e8cb661938 | ||
|
|
e5a870ed94 | ||
|
|
6116e954ba | ||
|
|
56ef7ce95c | ||
|
|
6e2e585175 | ||
|
|
8c1c0c6029 | ||
|
|
11dad79794 | ||
|
|
4b3485145f | ||
|
|
4b5a579b0b | ||
|
|
07bd09473c | ||
|
|
adba3b0d4b | ||
|
|
f6162c58f2 | ||
|
|
ed949d1dd8 | ||
|
|
5d94de418b | ||
|
|
f7fd8af124 | ||
|
|
d74734972b | ||
|
|
8736f5e387 | ||
|
|
89f08ae6c7 | ||
|
|
0b75b3847d | ||
|
|
6d27c3db11 | ||
|
|
b78065e23c | ||
|
|
c611c5c749 | ||
|
|
5b637154ea | ||
|
|
0b6edb3c38 | ||
|
|
f1ba1c55a6 | ||
|
|
2940a63d09 | ||
|
|
cc311f88a5 | ||
|
|
0911df0442 | ||
|
|
86e5dfe2ee | ||
|
|
34b5570563 | ||
|
|
2d806d8557 | ||
|
|
a792a0aa55 | ||
|
|
e7ab8441d7 | ||
|
|
d0b7efa24c |
44
.air.toml
Normal file
44
.air.toml
Normal file
@@ -0,0 +1,44 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./OliveTin"
|
||||
cmd = "go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin"
|
||||
delay = 1
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = true
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
2
.github/ISSUE_TEMPLATE/support_request.md
vendored
2
.github/ISSUE_TEMPLATE/support_request.md
vendored
@@ -39,7 +39,7 @@ If possible, please copy and paste your OliveTin logs from when the error happen
|
||||
**Screenshot of WebDeveloper console logs**
|
||||
|
||||
If you know how, and if you think it's relevant, a screenshot of the
|
||||
WebDeveloper console from when you clicked a button is often really helpful.
|
||||
WebDeveloper console from when you clicked a button is often really helpful.
|
||||
|
||||
**Anything else?**
|
||||
|
||||
|
||||
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
8
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -4,7 +4,7 @@ First of all, thank you for considering to raise a pull request!
|
||||
|
||||
Don’t be afraid to ask for advice before working on a contribution. If you’re thinking about a bigger change, especially that might affect the core working or architecture, it’s almost essential to talk and ask about what you’re planning might affect things. Some of the larger future plans may not be documented well so it’s difficult to understand how your change might affect the general direction and roadmap of this project without asking.
|
||||
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
|
||||
Helpful information to understand the project can be found here: [CONTRIBUTING](https://github.com/OliveTin/OliveTin/blob/main/CONTRIBUTING.adoc)
|
||||
|
||||
@@ -13,10 +13,10 @@ Helpful information to understand the project can be found here: [CONTRIBUTING](
|
||||
# Checklist
|
||||
Please put a X in the boxes as evidence of reading through the checklist.
|
||||
|
||||
- [ ] I have forked the project, and raised this PR on a feature branch.
|
||||
- [ ] I have forked the project, and raised this PR on a feature branch.
|
||||
- [ ] `make githooks` has been run, and my git commit message was accepted by the git hook.
|
||||
- [ ] `make daemon-compile` runs without any issues.
|
||||
- [ ] `make daemon-codestyle` runs without any issues.
|
||||
- [ ] `make daemon-unittests` runs without any issues.
|
||||
- [ ] `make webui-codestyle` runs without any issues.
|
||||
- [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.
|
||||
- [ ] `make webui-codestyle` runs without any issues.
|
||||
- [ ] I understand and accept the [AGPL-3.0 license](LICENSE) and [code of conduct](CODE_OF_CONDUCT.md), and my contributions fall under these.
|
||||
|
||||
27
.github/workflows/build-snapshot.yml
vendored
27
.github/workflows/build-snapshot.yml
vendored
@@ -11,6 +11,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
@@ -28,11 +30,20 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '>=1.18.0'
|
||||
go-version: '^1.18.0'
|
||||
cache: true
|
||||
|
||||
- name: grpc
|
||||
run: make grpc
|
||||
run: make -w grpc
|
||||
|
||||
- name: make daemon
|
||||
run: make -w daemon-compile-x64-lin
|
||||
|
||||
- name: unit tests
|
||||
run: make -w daemon-unittests
|
||||
|
||||
- name: integration tests
|
||||
run: cd integration-tests && make -w
|
||||
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v4.2.0
|
||||
@@ -41,14 +52,20 @@ jobs:
|
||||
version: latest
|
||||
args: release --snapshot --clean --parallelism 1 --skip-docker
|
||||
|
||||
- name: get date
|
||||
run: |
|
||||
echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Archive binaries
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: "OliveTin-snapshot-${{ github.sha }}-dist"
|
||||
name: "OliveTin-snapshot-${{ env.DATE }}-${{ github.sha }}"
|
||||
path: dist/OliveTin*.*
|
||||
|
||||
- name: Archive integration tests
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: integration-tests
|
||||
path: integration-tests
|
||||
name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}"
|
||||
path: |
|
||||
integration-tests
|
||||
!integration-tests/node_modules
|
||||
|
||||
25
.github/workflows/build-tag.yml
vendored
25
.github/workflows/build-tag.yml
vendored
@@ -12,6 +12,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up QEMU
|
||||
id: qemu
|
||||
@@ -33,31 +35,40 @@ jobs:
|
||||
cache: true
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_KEY }}
|
||||
|
||||
- name: Login to ghcr
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.CONTAINER_TOKEN }}
|
||||
|
||||
- name: grpc
|
||||
run: make grpc
|
||||
|
||||
- name: goreleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
uses: goreleaser/goreleaser-action@v4.2.0
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist --parallelism 1
|
||||
args: release --clean --parallelism 1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
|
||||
|
||||
- name: Archive binaries
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dist
|
||||
name: "OliveTin-${{ github.ref_name }}"
|
||||
path: dist/OliveTin*.*
|
||||
|
||||
- name: Archive integration tests
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: integration-tests
|
||||
path: integration-tests
|
||||
path: |
|
||||
integration-tests
|
||||
!integration-tests/node_modules
|
||||
|
||||
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
7
.github/workflows/codestyle.yml
vendored
7
.github/workflows/codestyle.yml
vendored
@@ -16,12 +16,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.16.0'
|
||||
go-version: '^1.18.0'
|
||||
cache: true
|
||||
|
||||
- name: deps
|
||||
run: make grpc
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,3 +9,4 @@ reports
|
||||
releases/
|
||||
dist/
|
||||
installation-id.txt
|
||||
tmp/
|
||||
|
||||
@@ -18,7 +18,7 @@ builds:
|
||||
|
||||
goarm:
|
||||
- 5 # For old RPIs
|
||||
- 6
|
||||
- 6
|
||||
- 7
|
||||
|
||||
main: cmd/OliveTin/main.go
|
||||
@@ -62,10 +62,10 @@ changelog:
|
||||
- '^refactor:'
|
||||
|
||||
archives:
|
||||
-
|
||||
-
|
||||
format: tar.gz
|
||||
|
||||
files:
|
||||
files:
|
||||
- config.yaml
|
||||
- LICENSE
|
||||
- README.md
|
||||
@@ -74,10 +74,6 @@ archives:
|
||||
- OliveTin.service
|
||||
- ./var/
|
||||
|
||||
replacements:
|
||||
darwin: macOS
|
||||
arm: arm32v
|
||||
|
||||
name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}{{ .Arm }}"
|
||||
|
||||
wrap_in_directory: true
|
||||
@@ -96,23 +92,20 @@ dockers:
|
||||
skip_push: false
|
||||
build_flag_templates:
|
||||
- "--platform=linux/amd64"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Tag}}"
|
||||
- "--platform=linux/amd64"
|
||||
extra_files:
|
||||
- webui
|
||||
|
||||
- image_templates:
|
||||
- "docker.io/jamesread/olivetin:{{ .Tag }}-arm64"
|
||||
- "ghcr.io/olivetin/olivetin:{{ .Tag }}-amd64"
|
||||
- "ghcr.io/olivetin/olivetin:{{ .Tag }}-arm64"
|
||||
dockerfile: Dockerfile.arm64
|
||||
goos: linux
|
||||
goarch: arm64
|
||||
skip_push: false
|
||||
build_flag_templates:
|
||||
- "--platform=linux/arm64"
|
||||
- "--label=org.opencontainers.image.title={{.ProjectName}}"
|
||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
||||
- "--label=org.opencontainers.image.version={{.Tag}}"
|
||||
extra_files:
|
||||
@@ -233,10 +226,13 @@ release:
|
||||
|
||||
- `docker pull docker.io/jamesread/olivetin:{{ .Version }}`
|
||||
|
||||
## Upgrade warnings, or breaking changes
|
||||
|
||||
- No such issues between the last release and this version.
|
||||
|
||||
## Useful links
|
||||
|
||||
- [Which download do I need?](https://docs.olivetin.app/choose-package.html)
|
||||
- [Ask for help and chat with others users in the Discord community](https://discord.gg/jhYWWpNJ3v)
|
||||
|
||||
|
||||
Thanks for your interest in OliveTin!
|
||||
|
||||
|
||||
10
.pre-commit-config.yaml
Normal file
10
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v3.2.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
@@ -7,9 +7,9 @@ of multiple projects, your time and interest in contributing is most welcome.
|
||||
If you're not sure where to get started, raise an issue in the project.
|
||||
|
||||
Ideas may be discussed, purely on their merits and issues. Our Code of Conduct
|
||||
(CoC) is straightforward - it's important that contributors feel comfortable in
|
||||
discussion throughout the whole process. This project respects the
|
||||
link:https://www.kernel.org/doc/html/latest/process/code-of-conduct.html[Linux Kernel code of conduct].
|
||||
(CoC) is straightforward - it's important that contributors feel comfortable in
|
||||
discussion throughout the whole process. This project respects the
|
||||
link:https://www.kernel.org/doc/html/latest/process/code-of-conduct.html[Linux Kernel code of conduct].
|
||||
|
||||
== If you're not sure, ask!
|
||||
|
||||
@@ -18,19 +18,19 @@ contribution. If you're thinking about a bigger change, especially that might
|
||||
affect the core working or architecture, it's almost essential to talk and ask
|
||||
about what you're planning might affect things. Some of the larger future plans may not be
|
||||
documented well so it's difficult to understand how your change might affect
|
||||
the general direction and roadmap of this project without asking.
|
||||
the general direction and roadmap of this project without asking.
|
||||
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
The preferred way to communicate is probably via Discord or GitHub issues.
|
||||
|
||||
=== Dev environment setup and clean build - Fedora
|
||||
=== Dev environment setup and clean build - Fedora
|
||||
|
||||
```
|
||||
dnf install git go protobuf-compiler make -y
|
||||
dnf install git go protobuf-compiler make -y
|
||||
git clone https://github.com/OliveTin/OliveTin.git
|
||||
cd OliveTin
|
||||
|
||||
# `make grpc` will also run `make go-tools`, which installs "buf". This binary
|
||||
# will be put in your GOPATH/bin/, which should be on your path. buf is used to
|
||||
# `make grpc` will also run `make go-tools`, which installs "buf". This binary
|
||||
# will be put in your GOPATH/bin/, which should be on your path. buf is used to
|
||||
# generate the protobuf / grpc stubs.
|
||||
make grpc
|
||||
make
|
||||
@@ -39,10 +39,10 @@ make
|
||||
|
||||
=== Getting started to contribute;
|
||||
|
||||
The project layout is reasonably straightforward;
|
||||
The project layout is reasonably straightforward;
|
||||
|
||||
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
|
||||
* The API is defined in protobuf+grpc - you will need to `make grpc`.
|
||||
* The API is defined in protobuf+grpc - you will need to `make grpc`.
|
||||
* The Go daemon is built from the `cmd` and `internal` directories mostly.
|
||||
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:36-x86_64
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title=OliveTin
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
&& microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
&& microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
openssh-clients \
|
||||
shadow-utils \
|
||||
docker \
|
||||
&& microdnf clean all
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
VOLUME /config
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
FROM --platform=linux/arm64 registry.fedoraproject.org/fedora-minimal:36-aarch64
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title=OliveTin
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
&& \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
openssh-clients
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
VOLUME /config
|
||||
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
FROM --platform=linux/armhfp registry.fedoraproject.org/fedora-minimal:36-armhfp
|
||||
|
||||
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
|
||||
LABEL org.opencontainers.image.title=OliveTin
|
||||
|
||||
RUN mkdir -p /config /var/www/olivetin \
|
||||
&& \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
microdnf install -y --nodocs --noplugins --setopt=keepcache=0 --setopt=install_weak_deps=0 \
|
||||
iputils \
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
shadow-utils \
|
||||
openssh-clients
|
||||
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
RUN useradd --system --create-home olivetin -u 1000
|
||||
|
||||
EXPOSE 1337/tcp
|
||||
EXPOSE 1337/tcp
|
||||
|
||||
VOLUME /config
|
||||
|
||||
|
||||
8
Jenkinsfile
vendored
8
Jenkinsfile
vendored
@@ -14,7 +14,7 @@ pipeline {
|
||||
sh 'make go-tools'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stage('Compile') {
|
||||
steps {
|
||||
withEnv(["PATH+GO=/root/go/bin/"]) {
|
||||
@@ -25,9 +25,9 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stage ('Post-Compile') {
|
||||
parallel {
|
||||
parallel {
|
||||
stage('Codestyle') {
|
||||
steps {
|
||||
withEnv(["PATH+GO=/root/go/bin/"]) {
|
||||
@@ -45,6 +45,6 @@ pipeline {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
14
Makefile
14
Makefile
@@ -1,9 +1,9 @@
|
||||
compile: daemon-compile-x64-lin
|
||||
|
||||
daemon-compile-armhf:
|
||||
daemon-compile-armhf:
|
||||
GOARCH=arm GOARM=6 go build -o OliveTin.armhf github.com/OliveTin/OliveTin/cmd/OliveTin
|
||||
|
||||
daemon-compile-x64-lin:
|
||||
daemon-compile-x64-lin:
|
||||
GOOS=linux go build -o OliveTin github.com/OliveTin/OliveTin/cmd/OliveTin
|
||||
|
||||
daemon-compile-x64-win:
|
||||
@@ -14,7 +14,7 @@ daemon-compile: daemon-compile-armhf daemon-compile-x64-lin daemon-compile-x64-w
|
||||
daemon-codestyle:
|
||||
go fmt ./...
|
||||
go vet ./...
|
||||
gocyclo -over 4 cmd internal
|
||||
gocyclo -over 4 cmd internal
|
||||
gocritic check ./...
|
||||
|
||||
daemon-unittests:
|
||||
@@ -24,7 +24,7 @@ daemon-unittests:
|
||||
|
||||
githooks:
|
||||
cp -v .githooks/* .git/hooks/
|
||||
|
||||
|
||||
go-tools:
|
||||
go install "github.com/bufbuild/buf/cmd/buf"
|
||||
go install "github.com/fzipp/gocyclo/cmd/gocyclo"
|
||||
@@ -37,7 +37,7 @@ go-tools:
|
||||
grpc: go-tools
|
||||
buf generate
|
||||
|
||||
dist: protoc
|
||||
dist: protoc
|
||||
|
||||
protoc:
|
||||
protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. -I .:/usr/include/ OliveTin.proto
|
||||
@@ -54,7 +54,7 @@ podman-container:
|
||||
integration-tests-docker-image:
|
||||
docker rm -f olivetin && docker rmi -f olivetin
|
||||
docker build -t olivetin:latest .
|
||||
docker create --name olivetin -p 1337:1337 -v `pwd`/integration-tests/configs/:/config/ olivetin
|
||||
docker create --name olivetin -p 1337:1337 -v `pwd`/integration-tests/configs/:/config/ olivetin
|
||||
|
||||
devrun: compile
|
||||
killall OliveTin || true
|
||||
@@ -70,4 +70,4 @@ webui-codestyle:
|
||||
clean:
|
||||
rm -rf dist OliveTin OliveTin.armhf OliveTin.exe reports gen
|
||||
|
||||
.PHONY: grpc
|
||||
.PHONY: grpc
|
||||
|
||||
104
OliveTin.proto
104
OliveTin.proto
@@ -8,16 +8,16 @@ message Action {
|
||||
string id = 1;
|
||||
string title = 2;
|
||||
string icon = 3;
|
||||
bool canExec = 4;
|
||||
|
||||
bool can_exec = 4;
|
||||
repeated ActionArgument arguments = 5;
|
||||
bool popup_on_start = 6;
|
||||
}
|
||||
|
||||
message ActionArgument {
|
||||
string name = 1;
|
||||
string title = 2;
|
||||
string title = 2;
|
||||
string type = 3;
|
||||
string defaultValue = 4;
|
||||
string default_value = 4;
|
||||
|
||||
repeated ActionArgumentChoice choices = 5;
|
||||
|
||||
@@ -44,9 +44,11 @@ message GetDashboardComponentsResponse {
|
||||
message GetDashboardComponentsRequest {}
|
||||
|
||||
message StartActionRequest {
|
||||
string actionName = 1;
|
||||
string action_name = 1;
|
||||
|
||||
repeated StartActionArgument arguments = 2;
|
||||
|
||||
string uuid = 3;
|
||||
}
|
||||
|
||||
message StartActionArgument {
|
||||
@@ -55,22 +57,52 @@ message StartActionArgument {
|
||||
}
|
||||
|
||||
message StartActionResponse {
|
||||
LogEntry logEntry = 1;
|
||||
string execution_uuid = 2;
|
||||
}
|
||||
|
||||
message StartActionAndWaitRequest {
|
||||
string action_name = 1;
|
||||
}
|
||||
|
||||
message StartActionAndWaitResponse {
|
||||
LogEntry log_entry = 1;
|
||||
}
|
||||
|
||||
message StartActionByAliasRequest {
|
||||
string action_alias = 1;
|
||||
}
|
||||
|
||||
message StartActionByAliasResponse {
|
||||
string execution_uuid = 2;
|
||||
}
|
||||
|
||||
message StartActionByAliasAndWaitRequest {
|
||||
string action_alias = 1;
|
||||
}
|
||||
|
||||
message StartActionByAliasAndWaitResponse {
|
||||
LogEntry log_entry = 1;
|
||||
}
|
||||
|
||||
message GetLogsRequest{};
|
||||
|
||||
message LogEntry {
|
||||
string datetime = 1;
|
||||
string actionTitle = 2;
|
||||
string datetime_started = 1;
|
||||
string action_title = 2;
|
||||
string stdout = 3;
|
||||
string stderr = 4;
|
||||
bool timedOut = 5;
|
||||
int32 exitCode = 6;
|
||||
bool timed_out = 5;
|
||||
int32 exit_code = 6;
|
||||
string user = 7;
|
||||
string userClass = 8;
|
||||
string actionIcon = 9;
|
||||
string user_class = 8;
|
||||
string action_icon = 9;
|
||||
repeated string tags = 10;
|
||||
string execution_uuid = 11;
|
||||
string datetime_finished = 12;
|
||||
string uuid = 13;
|
||||
bool execution_started = 14;
|
||||
bool execution_finished = 15;
|
||||
bool blocked = 16;
|
||||
}
|
||||
|
||||
message GetLogsResponse {
|
||||
@@ -87,10 +119,26 @@ message ValidateArgumentTypeResponse {
|
||||
string description = 2;
|
||||
}
|
||||
|
||||
message WatchExecutionRequest {
|
||||
string execution_uuid = 1;
|
||||
}
|
||||
|
||||
message WatchExecutionUpdate {
|
||||
string update = 1;
|
||||
}
|
||||
|
||||
message ExecutionStatusRequest {
|
||||
string execution_uuid = 1;
|
||||
}
|
||||
|
||||
message ExecutionStatusResponse {
|
||||
LogEntry log_entry = 1;
|
||||
}
|
||||
|
||||
message WhoAmIRequest {}
|
||||
|
||||
message WhoAmIResponse {
|
||||
string authenticatedUser = 1;
|
||||
string authenticated_user = 1;
|
||||
}
|
||||
|
||||
message SosReportRequest {}
|
||||
@@ -99,7 +147,7 @@ message SosReportResponse {
|
||||
string alert = 1;
|
||||
}
|
||||
|
||||
service OliveTinApi {
|
||||
service OliveTinApiService {
|
||||
rpc GetDashboardComponents(GetDashboardComponentsRequest) returns (GetDashboardComponentsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/GetDashboardComponents"
|
||||
@@ -113,6 +161,34 @@ service OliveTinApi {
|
||||
};
|
||||
}
|
||||
|
||||
rpc StartActionAndWait(StartActionAndWaitRequest) returns (StartActionAndWaitResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/StartActionAndWait"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc StartActionByAlias(StartActionByAliasRequest) returns (StartActionByAliasResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/StartActionByAlias/{action_alias}"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc StartActionByAliasAndWait(StartActionByAliasAndWaitRequest) returns (StartActionByAliasAndWaitResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/StartActionByAliasAndWait/{action_alias}"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/api/ExecutionStatus"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetLogs(GetLogsRequest) returns (GetLogsResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/GetLogs"
|
||||
|
||||
29
README.md
29
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
<img alt = "project logo" src = "https://github.com/OliveTin/OliveTin/blob/main/webui/OliveTinLogo.png" align = "right" width = "160px" />
|
||||
|
||||
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
|
||||
OliveTin gives **safe** and **simple** access to predefined shell commands from a web interface.
|
||||
|
||||
[](https://discord.gg/jhYWWpNJ3v)
|
||||
[](https://github.com/awesome-selfhosted/awesome-selfhosted#automation)
|
||||
@@ -11,7 +11,7 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
|
||||
[](https://goreportcard.com/report/github.com/OliveTin/OliveTin)
|
||||
[](https://github.com/OliveTin/OliveTin/actions/workflows/build-snapshot.yml)
|
||||
|
||||
## Use cases
|
||||
## Use cases
|
||||
|
||||
**Safely** give access to commands, for less technical people;
|
||||
|
||||
@@ -34,10 +34,10 @@ OliveTin gives **safe** and **simple** access to predefined shell commands from
|
||||
## Features
|
||||
|
||||
* **Responsive, touch-friendly UI** - great for tablets and mobile
|
||||
* **Super simple config in YAML** - because if it's not YAML now-a-days, it's not "cloud native" :-)
|
||||
* **Super simple config in YAML** - because if it's not YAML now-a-days, it's not "cloud native" :-)
|
||||
* **Dark mode** - for those of you that roll that way.
|
||||
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
|
||||
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
|
||||
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
|
||||
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
|
||||
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretially you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
|
||||
* **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/gRPC API.
|
||||
* **Good amount of unit tests and style checks** - helps potential contributors be consistent, and helps with maintainability.
|
||||
@@ -48,17 +48,17 @@ Desktop web browser;
|
||||
|
||||

|
||||
|
||||
Desktop web browser (dark mode);
|
||||
Desktop web browser (dark mode);
|
||||
|
||||

|
||||
|
||||
Mobile screen size (responsive layout);
|
||||
Mobile screen size (responsive layout);
|
||||
|
||||

|
||||
|
||||
## Documentation
|
||||
|
||||
All documentation can be found at http://docs.olivetin.app . This includes installation and usage guide, etc.
|
||||
All documentation can be found at http://docs.olivetin.app . This includes installation and usage guide, etc.
|
||||
|
||||
### Quickstart reference for `config.yaml`
|
||||
|
||||
@@ -75,19 +75,19 @@ Put this `config.yaml` in `/etc/OliveTin/` if you're running a standard service,
|
||||
|
||||
```yaml
|
||||
# Listen on all addresses available, port 1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
# Choose from INFO (default), WARN and DEBUG
|
||||
logLevel: "INFO"
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
# Docs: https://docs.olivetin.app/action-container-control.html
|
||||
actions:
|
||||
# Docs: https://docs.olivetin.app/action-container-control.html
|
||||
- title: Restart Plex
|
||||
icon: restart
|
||||
shell: docker restart plex
|
||||
|
||||
# This will send 1 ping
|
||||
|
||||
# This will send 1 ping
|
||||
# Docs: https://docs.olivetin.app/action-ping.html
|
||||
- title: Ping host
|
||||
shell: ping {{ host }} -c {{ count }}
|
||||
@@ -102,7 +102,7 @@ actions:
|
||||
title: Count
|
||||
type: int
|
||||
default: 1
|
||||
|
||||
|
||||
# Restart http on host "webserver1"
|
||||
# Docs: https://docs.olivetin.app/action-ssh.html
|
||||
- title: restart httpd
|
||||
@@ -111,4 +111,3 @@ actions:
|
||||
```
|
||||
|
||||
A full example config can be found at in this repository - [config.yaml](https://github.com/OliveTin/OliveTin/blob/main/config.yaml).
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Currently, only the `main` branch is "supported".
|
||||
Currently, only the `main` branch is "supported".
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
@@ -10,4 +10,4 @@ Currently, only the `main` branch is "supported".
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please email `contact@jread.com` for responsible disclosure. Accepted issues will be made public once patched, and you will be given credit.
|
||||
Please email `contact@jread.com` for responsible disclosure. Accepted issues will be made public once patched, and you will be given credit.
|
||||
|
||||
@@ -17,4 +17,3 @@ plugins:
|
||||
|
||||
# - name: openapiv2
|
||||
# out: reports/openapiv2
|
||||
|
||||
|
||||
2
buf.yaml
2
buf.yaml
@@ -1,5 +1,5 @@
|
||||
version: v1
|
||||
deps:
|
||||
deps:
|
||||
- buf.build/googleapis/googleapis
|
||||
lint:
|
||||
use:
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
grpcapi "github.com/OliveTin/OliveTin/internal/grpcapi"
|
||||
"github.com/OliveTin/OliveTin/internal/installationinfo"
|
||||
"github.com/OliveTin/OliveTin/internal/oncron"
|
||||
"github.com/OliveTin/OliveTin/internal/onfileindir"
|
||||
"github.com/OliveTin/OliveTin/internal/onstartup"
|
||||
updatecheck "github.com/OliveTin/OliveTin/internal/updatecheck"
|
||||
"github.com/OliveTin/OliveTin/internal/websocket"
|
||||
|
||||
"github.com/OliveTin/OliveTin/internal/httpservers"
|
||||
|
||||
@@ -29,27 +31,52 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
initLog()
|
||||
|
||||
initViperConfig(initCliFlags())
|
||||
|
||||
initCheckEnvironment()
|
||||
|
||||
initInstallationInfo()
|
||||
|
||||
log.Info("OliveTin initialization complete")
|
||||
}
|
||||
|
||||
func initLog() {
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
ForceQuote: true,
|
||||
DisableTimestamp: true,
|
||||
})
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"version": version,
|
||||
"commit": commit,
|
||||
"date": date,
|
||||
}).Info("OliveTin initializing")
|
||||
|
||||
log.SetLevel(log.DebugLevel) // Default to debug, to catch cfg issues
|
||||
// Use debug this early on to catch details about startup errors. The
|
||||
// default config will raise the log level later, if not set.
|
||||
log.SetLevel(log.DebugLevel) // Default to debug, to catch cfg issue
|
||||
}
|
||||
|
||||
func initCliFlags() string {
|
||||
var configDir string
|
||||
flag.StringVar(&configDir, "configdir", ".", "Config directory path")
|
||||
|
||||
var printVersion bool
|
||||
flag.BoolVar(&printVersion, "version", false, "Prints the version number and exits")
|
||||
flag.Parse()
|
||||
|
||||
// This log message should be the first log message OliveTin prints.
|
||||
if printVersion {
|
||||
logStartupMessage("OliveTin is just printing the startup message")
|
||||
os.Exit(1)
|
||||
} else {
|
||||
logStartupMessage("OliveTin initializing")
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"value": configDir,
|
||||
}).Debugf("Value of -configdir flag")
|
||||
|
||||
return configDir
|
||||
}
|
||||
|
||||
func initViperConfig(configDir string) {
|
||||
viper.AutomaticEnv()
|
||||
viper.SetConfigName("config.yaml")
|
||||
viper.SetConfigType("yaml")
|
||||
@@ -74,15 +101,25 @@ func init() {
|
||||
})
|
||||
|
||||
reloadConfig()
|
||||
}
|
||||
|
||||
warnIfPuidGuid()
|
||||
|
||||
func initInstallationInfo() {
|
||||
installationinfo.Config = cfg
|
||||
installationinfo.Build.Version = version
|
||||
installationinfo.Build.Commit = commit
|
||||
installationinfo.Build.Date = date
|
||||
}
|
||||
|
||||
log.Info("Init complete")
|
||||
func logStartupMessage(message string) {
|
||||
log.WithFields(log.Fields{
|
||||
"version": version,
|
||||
"commit": commit,
|
||||
"date": date,
|
||||
}).Info(message)
|
||||
}
|
||||
|
||||
func initCheckEnvironment() {
|
||||
warnIfPuidGuid()
|
||||
}
|
||||
|
||||
func warnIfPuidGuid() {
|
||||
@@ -110,9 +147,11 @@ func main() {
|
||||
log.Debugf("Config: %+v", cfg)
|
||||
|
||||
executor := executor.DefaultExecutor()
|
||||
executor.AddListener(websocket.ExecutionListener)
|
||||
|
||||
go onstartup.Execute(cfg, executor)
|
||||
go oncron.Schedule(cfg, executor)
|
||||
go onfileindir.WatchFilesInDirectory(cfg, executor)
|
||||
|
||||
go updatecheck.StartUpdateChecker(version, commit, cfg, configDir)
|
||||
|
||||
|
||||
27
config.yaml
27
config.yaml
@@ -1,18 +1,24 @@
|
||||
# There is a built-in micro proxy that will host the webui and REST API all on
|
||||
# one port (this is called the "Single HTTP Frontend") and means you just need
|
||||
# one open port in the container/firewalls/etc.
|
||||
# There is a built-in micro proxy that will host the webui and REST API all on
|
||||
# one port (this is called the "Single HTTP Frontend") and means you just need
|
||||
# one open port in the container/firewalls/etc.
|
||||
#
|
||||
# Listen on all addresses available, port 1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
# Choose from INFO (default), WARN and DEBUG
|
||||
logLevel: "INFO"
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
actions:
|
||||
- title: date
|
||||
shell: date
|
||||
popupOnStart: true
|
||||
|
||||
# This will run a simple script that you create.
|
||||
- title: Run backup script
|
||||
shell: /opt/backupScript.sh
|
||||
shellAfterCompleted: "apprise -t 'Notification: Backup script completed' -b 'The backup script completed with code {{ exitCode}}. The log is: \n {{ stdout }} '"
|
||||
maxConcurrent: 1
|
||||
icon: backup
|
||||
|
||||
# This will send 1 ping (-c 1)
|
||||
@@ -20,6 +26,7 @@ actions:
|
||||
- title: Ping host
|
||||
shell: ping {{ host }} -c {{ count }}
|
||||
icon: ping
|
||||
timeout: 100
|
||||
arguments:
|
||||
- name: host
|
||||
title: host
|
||||
@@ -32,15 +39,16 @@ actions:
|
||||
type: int
|
||||
default: 1
|
||||
description: How many times to do you want to ping?
|
||||
|
||||
|
||||
# Restart lightdm on host "server1"
|
||||
# Docs: https://docs.olivetin.app/action-ping.html
|
||||
- title: restart httpd
|
||||
titleAlias: restart_httpd
|
||||
icon: restart
|
||||
shell: ssh root@server1 'service httpd restart'
|
||||
|
||||
# OliveTin can run long-running jobs like Ansible playbooks.
|
||||
#
|
||||
# OliveTin can run long-running jobs like Ansible playbooks.
|
||||
#
|
||||
# For such jobs, you will need to install ansible-playbook on the host where
|
||||
# you are running OliveTin, or in the container.
|
||||
#
|
||||
@@ -51,7 +59,7 @@ actions:
|
||||
timeout: 120
|
||||
|
||||
# OliveTin can control containers - docker is just a command line app.
|
||||
#
|
||||
#
|
||||
# However, if you are running in a container you will need to do some setup,
|
||||
# see the docs below.
|
||||
#
|
||||
@@ -76,4 +84,3 @@ actions:
|
||||
shell: sleep 5
|
||||
timeout: 5
|
||||
icon: "😪"
|
||||
|
||||
|
||||
95
go.mod
95
go.mod
@@ -3,40 +3,44 @@ module github.com/OliveTin/OliveTin
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/bufbuild/buf v1.15.1
|
||||
github.com/bufbuild/buf v1.26.0
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/fzipp/gocyclo v0.6.0
|
||||
github.com/go-critic/go-critic v0.7.0
|
||||
github.com/go-critic/go-critic v0.8.2
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833
|
||||
google.golang.org/grpc v1.53.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
||||
google.golang.org/grpc v1.57.1
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0
|
||||
google.golang.org/protobuf v1.30.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/bufbuild/connect-go v1.5.2 // indirect
|
||||
github.com/bufbuild/protocompile v0.5.1 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/bufbuild/connect-go v1.10.0 // indirect
|
||||
github.com/bufbuild/connect-opentelemetry-go v0.4.0 // indirect
|
||||
github.com/bufbuild/protocompile v0.6.0 // indirect
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/cristalhq/acmd v0.11.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/docker/cli v23.0.1+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/docker v23.0.1+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||
github.com/docker/cli v24.0.5+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible // indirect
|
||||
github.com/docker/docker-credential-helpers v0.8.0 // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/felixge/fgprof v0.9.3 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/go-logr/logr v1.2.3 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.10 // indirect
|
||||
github.com/go-logr/logr v1.2.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-toolsmith/astcast v1.1.0 // indirect
|
||||
github.com/go-toolsmith/astcopy v1.1.0 // indirect
|
||||
@@ -46,25 +50,24 @@ require (
|
||||
github.com/go-toolsmith/pkgload v1.2.2 // indirect
|
||||
github.com/go-toolsmith/strparse v1.1.0 // indirect
|
||||
github.com/go-toolsmith/typep v1.1.0 // indirect
|
||||
github.com/gofrs/flock v0.8.1 // indirect
|
||||
github.com/gofrs/uuid/v5 v5.0.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.0.0 // indirect
|
||||
github.com/golang/glog v1.1.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-containerregistry v0.13.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
|
||||
github.com/google/go-containerregistry v0.16.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 // indirect
|
||||
github.com/klauspost/compress v1.16.0 // indirect
|
||||
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||
github.com/klauspost/compress v1.16.7 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
@@ -74,30 +77,34 @@ require (
|
||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||
github.com/rs/cors v1.8.3 // indirect
|
||||
github.com/rs/cors v1.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/cobra v1.6.1 // indirect
|
||||
github.com/spf13/cobra v1.7.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
go.opentelemetry.io/otel v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.14.0 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.7.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230213192124-5e25df0256eb // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 // indirect
|
||||
github.com/tetratelabs/wazero v1.4.0 // indirect
|
||||
github.com/vbatts/tar-split v0.11.5 // indirect
|
||||
go.opentelemetry.io/otel v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.16.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.25.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sync v0.3.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
193
go.sum
193
go.sum
@@ -40,15 +40,17 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/bufbuild/buf v1.15.1 h1:v7sK2uMEsGX4Z2hvu+xiMheH3C3AKBGfxPBgdUZYDQ8=
|
||||
github.com/bufbuild/buf v1.15.1/go.mod h1:TQeGKam1QMfHy/xsSnnMpxN3JK5HBb6aNvZj4m52gkE=
|
||||
github.com/bufbuild/connect-go v1.5.2 h1:G4EZd5gF1U1ZhhbVJXplbuUnfKpBZ5j5izqIwu2g2W8=
|
||||
github.com/bufbuild/connect-go v1.5.2/go.mod h1:GmMJYR6orFqD0Y6ZgX8pwQ8j9baizDrIQMm1/a6LnHk=
|
||||
github.com/bufbuild/protocompile v0.5.1 h1:mixz5lJX4Hiz4FpqFREJHIXLfaLBntfaJv1h+/jS+Qg=
|
||||
github.com/bufbuild/protocompile v0.5.1/go.mod h1:G5iLmavmF4NsYtpZFvE3B/zFch2GIY8+wjsYLR/lc40=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
|
||||
github.com/bufbuild/buf v1.26.0 h1:rB8pErEkxdUNhVZ3+ClFEoJRaa+o2FjEecCWL2S3Qs4=
|
||||
github.com/bufbuild/buf v1.26.0/go.mod h1:UMPncXMWgrmIM+0QpwTEwjNr2SA0z2YIVZZsmNflvB4=
|
||||
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
|
||||
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
|
||||
github.com/bufbuild/connect-opentelemetry-go v0.4.0 h1:6JAn10SNqlQ/URhvRNGrIlczKw1wEXknBUUtmWqOiak=
|
||||
github.com/bufbuild/connect-opentelemetry-go v0.4.0/go.mod h1:nwPXYoDOoc2DGyKE/6pT1Q9MPSi2Et2e6BieMD0l6WU=
|
||||
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
|
||||
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
@@ -57,7 +59,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.12.1 h1:+7nYmHJb0tEkcRaAW+MHqoKaJYZmkikupxCqVtmPuY0=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
|
||||
github.com/containerd/stargz-snapshotter/estargz v0.14.3/go.mod h1:KY//uOCIkSuNAHhJogcZtrNHdKrA99/FCCRjE3HD36o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
@@ -66,14 +69,14 @@ github.com/cristalhq/acmd v0.11.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgB
|
||||
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/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM=
|
||||
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v23.0.1+incompatible h1:vjgvJZxprTTE1A37nm+CLNAdwu6xZekyoiVlUZEINcY=
|
||||
github.com/docker/docker v23.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||
github.com/docker/cli v24.0.5+incompatible h1:WeBimjvS0eKdH4Ygx+ihVq1Q++xg36M/rMi4aXAvodc=
|
||||
github.com/docker/cli v24.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8=
|
||||
github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -91,16 +94,16 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-critic/go-critic v0.7.0 h1:tqbKzB8pqi0NsRZ+1pyU4aweAF7A7QN0Pi4Q02+rYnQ=
|
||||
github.com/go-critic/go-critic v0.7.0/go.mod h1:moYzd7GdVXE2C2hYTwd7h0CPcqlUeclsyBRwMa38v64=
|
||||
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
|
||||
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-critic/go-critic v0.8.2 h1:mekhZ9jw5NBEj3I8o/EywXw5zBfGAJuMo4VVVjtxF80=
|
||||
github.com/go-critic/go-critic v0.8.2/go.mod h1:nZPlrtVfOuLOe8GpvWTfcMzfkG0QVZWAziAeXpivfQo=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
|
||||
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
|
||||
@@ -122,7 +125,6 @@ github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJ
|
||||
github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus=
|
||||
github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig=
|
||||
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/uuid/v5 v5.0.0 h1:p544++a97kEL+svbcFbCQVM9KFu0Yo25UoISXGNNH9M=
|
||||
github.com/gofrs/uuid/v5 v5.0.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
@@ -130,8 +132,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
|
||||
github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -173,8 +175,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTKqQwjErKVHJC8k=
|
||||
github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo=
|
||||
github.com/google/go-containerregistry v0.16.1 h1:rUEt426sR6nyrL3gt+18ibRcvYpKYdpsa5ZW7MA08dQ=
|
||||
github.com/google/go-containerregistry v0.16.1/go.mod h1:u0qB2l7mvtWVR5kNcbFIhFY1hLbf8eeGapA+vbFDCtQ=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -189,8 +191,8 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
|
||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
||||
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb h1:oqpb3Cwpc7EOml5PVGMYbSGmwNui2R7i8IW83gs4W0c=
|
||||
github.com/google/pprof v0.0.0-20230808223545-4887780b67fb/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
@@ -198,8 +200,10 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 h1:dygLcbEBA+t/P7ck6a8AkXv6juQ4cK0RHBoh32jxhHM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2/go.mod h1:Ap9RLCIJVtgQg1/BBgVEfypOAySvvlcpcVQkSzJCH4Y=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
@@ -207,7 +211,6 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jdxcode/netrc v0.0.0-20221124155335-4616370d1a84 h1:2uT3aivO7NVpUPGcQX7RbHijHMyWix/yCnIrCWc+5co=
|
||||
@@ -217,10 +220,10 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
|
||||
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -233,15 +236,15 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
|
||||
github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
@@ -266,18 +269,18 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rs/cors v1.8.3 h1:O+qNyWn7Z+F9M0ILBHgMVPuB1xTOucVd5gtaYyXBpRo=
|
||||
github.com/rs/cors v1.8.3/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
|
||||
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
@@ -294,11 +297,14 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/vbatts/tar-split v0.11.2 h1:Via6XqJr0hceW4wff3QRzD5gAk/tatMw/4ZA7cTlIME=
|
||||
github.com/tetratelabs/wazero v1.4.0 h1:9/MirYvmkJ/zSUOygKY/ia3t+e+RqIZXKbylIby1WYk=
|
||||
github.com/tetratelabs/wazero v1.4.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
|
||||
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -309,19 +315,22 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opentelemetry.io/otel v1.14.0 h1:/79Huy8wbf5DnIPhemGB+zEPVwnN6fuQybr/SRXa6hM=
|
||||
go.opentelemetry.io/otel v1.14.0/go.mod h1:o4buv+dJzx8rohcUeRmWUZhqupFvzWis188WlggnNeU=
|
||||
go.opentelemetry.io/otel/sdk v1.14.0 h1:PDCppFRDq8A1jL9v6KMI6dYesaq+DFcDZvjsoGvxGzY=
|
||||
go.opentelemetry.io/otel/sdk v1.14.0/go.mod h1:bwIC5TjrNG6QDCHNWvW4HLHtUQ4I+VQDsnjhvyZCALM=
|
||||
go.opentelemetry.io/otel/trace v1.14.0 h1:wp2Mmvj41tDsyAJXiWDWpfNsOiIyd38fy85pyKcFq/M=
|
||||
go.opentelemetry.io/otel/trace v1.14.0/go.mod h1:8avnQLK+CG77yNLUae4ea2JDQ6iT+gozhnZjy/rw9G8=
|
||||
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
|
||||
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
|
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
|
||||
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
|
||||
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
|
||||
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
|
||||
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE=
|
||||
go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4=
|
||||
go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI=
|
||||
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
|
||||
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c=
|
||||
go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@@ -329,8 +338,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -341,12 +350,12 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833 h1:SChBja7BCQewoTAU7IgvucQKMIXrEpFxNMs0spT3/5s=
|
||||
golang.org/x/exp v0.0.0-20230307190834-24139beb5833/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230213192124-5e25df0256eb h1:WGs/bGIWYyAY5PVgGGMXqGGCxSJz4fpoUExb/vgqNCU=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230213192124-5e25df0256eb/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819 h1:qdPQGUfaPzFgEbhQCulaXk+s0ETcox/vQd+f9YCIuxY=
|
||||
golang.org/x/exp/typeparams v0.0.0-20230809150735-7b3493d9a819/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -370,8 +379,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
|
||||
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
|
||||
golang.org/x/mod v0.12.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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -403,8 +412,8 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY
|
||||
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -424,8 +433,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -465,19 +474,19 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.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.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -531,8 +540,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -599,8 +608,12 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
|
||||
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923 h1:znp6mq/drrY+6khTAlJUDNFFcDGV2ENLYKpMq8SyCds=
|
||||
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw=
|
||||
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577 h1:Tyk/35yqszRCvaragTn5NnkY6IiKk/XvHzEWepo71N0=
|
||||
google.golang.org/genproto v0.0.0-20230807174057-1744710a1577/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577 h1:xv8KoglAClYGkprUSmDTKaILtzfD8XzG9NYVXMprjKo=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230807174057-1744710a1577/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577 h1:wukfNtZmZUurLN/atp2hiIeTKn7QJWIQdHzqmsOnAOk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230807174057-1744710a1577/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@@ -617,8 +630,8 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
|
||||
google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
|
||||
google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg=
|
||||
google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0 h1:rNBFJjBCOgVr9pWD7rs/knKL4FRTKgpZmsRfV214zcA=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.3.0/go.mod h1:Dk1tviKTvMCz5tvh7t+fh94dhmQVHuCt2OzJB3CTW9Y=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
@@ -633,8 +646,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
8
integration-tests/.eslintrc.yml
Normal file
8
integration-tests/.eslintrc.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
env:
|
||||
browser: true
|
||||
es2021: true
|
||||
extends: 'eslint:recommended'
|
||||
parserOptions:
|
||||
ecmaVersion: 12
|
||||
sourceType: module
|
||||
rules: {}
|
||||
2
integration-tests/.mocharc.yml
Normal file
2
integration-tests/.mocharc.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
require:
|
||||
- mochaSetup.mjs
|
||||
@@ -1,6 +1,5 @@
|
||||
cypress:
|
||||
npm install
|
||||
./cypressRun.sh "general"
|
||||
./cypressRun.sh "hiddenNav"
|
||||
default:
|
||||
npm install --no-fund
|
||||
./node_modules/.bin/mocha
|
||||
|
||||
.PHONY: cypress container
|
||||
.PHONY: default
|
||||
|
||||
@@ -1 +1 @@
|
||||
# OliveTin-integration-tests
|
||||
# OliveTin-integration-tests
|
||||
|
||||
8
integration-tests/Vagrantfile
vendored
8
integration-tests/Vagrantfile
vendored
@@ -2,7 +2,7 @@
|
||||
# (eg, snapshot builds on GitHub)
|
||||
|
||||
|
||||
Vagrant.configure("2") do |config|
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "generic/centos8"
|
||||
config.vm.provision "shell", inline: "mkdir /etc/OliveTin && chmod o+w /etc/OliveTin/", privileged: true
|
||||
config.vm.provision "file", source: "configs/config.general.yaml/.", destination: "/etc/OliveTin/config.yaml"
|
||||
@@ -13,19 +13,19 @@ Vagrant.configure("2") do |config|
|
||||
|
||||
config.vm.define :f36 do |f36|
|
||||
f36.vm.box = "generic/fedora36"
|
||||
f36.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_rpm/.", destination: "."
|
||||
f36.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.rpm", destination: "$HOME/"
|
||||
f36.vm.provision "shell", inline: "rpm -U OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
end
|
||||
|
||||
config.vm.define :debian do |debian|
|
||||
debian.vm.box = "generic/debian10"
|
||||
debian.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_deb/.", destination: "."
|
||||
debian.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
|
||||
debian.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin"
|
||||
end
|
||||
|
||||
config.vm.define :ubuntu do |ubuntu|
|
||||
ubuntu.vm.box = "generic/ubuntu2110"
|
||||
ubuntu.vm.provision "file", source: "/opt/OliveTin-vagrant/linux_amd64_deb/.", destination: "."
|
||||
ubuntu.vm.provision "file", source: "/opt/OliveTin-snapshot/OliveTin_linux_amd64.deb", destination: "$HOME/"
|
||||
ubuntu.vm.provision "shell", inline: "dpkg --force-confold -i OliveTin* && systemctl enable --now OliveTin && systemctl disable --now firewalld"
|
||||
end
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping Google.com
|
||||
shell: ping google.com -c 1
|
||||
icon: ping
|
||||
|
||||
|
||||
- title: restart lightdm
|
||||
icon: poop
|
||||
shell: ssh root@overseer 'service lightdm restart'
|
||||
@@ -32,4 +32,3 @@ actions:
|
||||
- title: Restart Plex
|
||||
icon: smile
|
||||
shell: docker restart plex
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
showFooter: false
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping example.com
|
||||
shell: ping example.com -c 1
|
||||
icon: ping
|
||||
@@ -1,14 +1,14 @@
|
||||
#
|
||||
# Integration Test Config: General
|
||||
#
|
||||
#
|
||||
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
showNavigation: false
|
||||
checkForUpdates: false
|
||||
checkForUpdates: false
|
||||
|
||||
# Actions (buttons) to show up on the WebUI:
|
||||
actions:
|
||||
actions:
|
||||
- title: Ping example.com
|
||||
shell: ping example.com -c 1
|
||||
icon: ping
|
||||
25
integration-tests/configs/multipleDropdowns/config.yaml
Normal file
25
integration-tests/configs/multipleDropdowns/config.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
listenAddressSingleHTTPFrontend: 0.0.0.0:1337
|
||||
|
||||
logLevel: "DEBUG"
|
||||
checkForUpdates: false
|
||||
|
||||
actions:
|
||||
- title: Ping Google.com
|
||||
shell: ping google.com -c 1
|
||||
icon: ping
|
||||
|
||||
- title: Test multiple dropdowns
|
||||
shell: echo {{ salutation }} {{ person }}
|
||||
icon: ping
|
||||
arguments:
|
||||
- name: salutation
|
||||
choices:
|
||||
- value: Hello
|
||||
- value: Goodbye
|
||||
|
||||
- name: person
|
||||
choices:
|
||||
- value: Alice
|
||||
- value: Bob
|
||||
- value: Dave
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:1337",
|
||||
"screenshotsFolder": "results/screenshots/",
|
||||
"videosFolder": "results/videos/"
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
describe('Homepage rendering', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
});
|
||||
|
||||
it("Footer contains promo", () => {
|
||||
cy.get('footer').contains("OliveTin")
|
||||
})
|
||||
|
||||
it('Default buttons are rendered', () => {
|
||||
cy.get("#root-group button").should('have.length', 6)
|
||||
})
|
||||
|
||||
it('Switcher navigation is visible', () => {
|
||||
cy.get('#section-switcher').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.false
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
describe('Hidden Footer', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
cy.wait(500)
|
||||
});
|
||||
|
||||
it('Footer is hidden', () => {
|
||||
cy.get('footer').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.true
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
describe('Hidden Nav', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit("/")
|
||||
cy.wait(500)
|
||||
});
|
||||
|
||||
it("Footer contains promo", () => {
|
||||
cy.get('footer').contains("OliveTin")
|
||||
})
|
||||
|
||||
it('Switcher navigation is hidden', () => {
|
||||
cy.get('#section-switcher').then($el => {
|
||||
expect(Cypress.dom.isHidden($el)).to.be.true
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -o xtrace
|
||||
|
||||
echo "Running config $1"
|
||||
|
||||
cp -f ./configs/config.$1.yaml ./configs/config.yaml
|
||||
docker start olivetin
|
||||
NO_COLOR=1 ./node_modules/.bin/cypress run --headless -s cypress/integration/$1/* || true
|
||||
docker kill olivetin
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# args:
|
||||
# $1: The Vagrant VM to test against. If blank and only one VM is provisioned, it will use that.
|
||||
|
||||
IP=$(vagrant ssh-config $1 | grep HostName | awk '{print $2}')
|
||||
BASE_URL="http://$IP:1337/"
|
||||
|
||||
echo "IP: $IP, BaseURL: $BASE_URL"
|
||||
|
||||
# Only run the general test, as we cannot easily switch out configs in VMs yet.
|
||||
./node_modules/.bin/cypress run --headless -c baseUrl=$BASE_URL -s cypress/integration/general/*
|
||||
18
integration-tests/mochaSetup.mjs
Normal file
18
integration-tests/mochaSetup.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Options } from 'selenium-webdriver/chrome.js'
|
||||
import { Builder, Browser } from 'selenium-webdriver'
|
||||
import getRunner from './runner.mjs'
|
||||
|
||||
export async function mochaGlobalSetup () {
|
||||
const options = new Options()
|
||||
options.addArguments('--headless')
|
||||
|
||||
global.webdriver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build()
|
||||
|
||||
global.runner = getRunner()
|
||||
|
||||
console.log("Runner constructor: " + global.runner.constructor.name)
|
||||
}
|
||||
|
||||
export async function mochaGlobalTeardown () {
|
||||
await global.webdriver.quit()
|
||||
}
|
||||
3628
integration-tests/package-lock.json
generated
3628
integration-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,22 @@
|
||||
{
|
||||
"name": "olivetin-integration-tests",
|
||||
"version": "1.0.0",
|
||||
"repository": "https://github.com/OliveTin/OliveTin-integration-tests",
|
||||
"description": "The cypress WebUI tests",
|
||||
"repository": "https://github.com/OliveTin/OliveTin",
|
||||
"description": "The integration-tests for OliveTin's webui.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.10",
|
||||
"eslint": "^8.51.0",
|
||||
"mocha": "^10.2.0",
|
||||
"selenium-webdriver": "4.12.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"cypress": "^8.3.0"
|
||||
"wait-on": "^7.2.0"
|
||||
}
|
||||
}
|
||||
|
||||
61
integration-tests/runner.mjs
Normal file
61
integration-tests/runner.mjs
Normal file
@@ -0,0 +1,61 @@
|
||||
import process from 'node:process'
|
||||
import waitOn from 'wait-on'
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
let ot = null
|
||||
|
||||
export default function getRunner () {
|
||||
const type = process.env.OLIVETIN_TEST_RUNNER
|
||||
|
||||
console.log('OLIVETIN_TEST_RUNNER env value is: ', type)
|
||||
|
||||
switch (type) {
|
||||
case 'local':
|
||||
return new OliveTinTestRunnerLocalProcess()
|
||||
case 'vm':
|
||||
return null
|
||||
case 'container':
|
||||
return null
|
||||
default:
|
||||
return new OliveTinTestRunnerLocalProcess()
|
||||
}
|
||||
}
|
||||
|
||||
class OliveTinTestRunnerLocalProcess {
|
||||
async start (cfg) {
|
||||
ot = spawn('./../OliveTin', ['-configdir', 'configs/' + cfg + '/'])
|
||||
|
||||
const logStdout = process.env.OLIVETIN_TEST_RUNNER_LOG_STDOUT === '1'
|
||||
|
||||
if (logStdout) {
|
||||
ot.stdout.on('data', (data) => {
|
||||
console.log(`stdout: ${data}`)
|
||||
})
|
||||
|
||||
ot.stderr.on('data', (data) => {
|
||||
console.error(`stderr: ${data}`)
|
||||
})
|
||||
}
|
||||
|
||||
ot.on('close', (code) => {
|
||||
if (code != null) {
|
||||
console.log(`child process exited with code ${code}`)
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
this.server = await startSomeServer({port: process.env.TEST_PORT});
|
||||
console.log(`server running on port ${this.server.port}`);
|
||||
*/
|
||||
|
||||
await waitOn({
|
||||
'resources': ['http://localhost:1337/']
|
||||
})
|
||||
|
||||
return ot
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await ot.kill()
|
||||
}
|
||||
}
|
||||
35
integration-tests/test/general.mjs
Normal file
35
integration-tests/test/general.mjs
Normal file
@@ -0,0 +1,35 @@
|
||||
import {expect} from 'chai';
|
||||
import {By} from 'selenium-webdriver';
|
||||
|
||||
describe('config: general', function () {
|
||||
before(async function () {
|
||||
await runner.start('general')
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
});
|
||||
|
||||
it('Page title', async function () {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
let title = await webdriver.getTitle();
|
||||
expect(title).to.be.equal("OliveTin")
|
||||
})
|
||||
|
||||
it('Footer contains promo', async function () {
|
||||
let ftr = await webdriver.findElement(By.tagName('footer')).getText()
|
||||
|
||||
expect(ftr).to.contain("Documentation")
|
||||
})
|
||||
|
||||
it('Default buttons are rendered', async function() {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
// await webdriver.manage().setTimeouts({ implicit: 2000 });
|
||||
|
||||
let buttons = await webdriver.findElement(By.id('root-group')).findElements(By.tagName('button'))
|
||||
|
||||
expect(buttons).to.have.length(6);
|
||||
})
|
||||
})
|
||||
21
integration-tests/test/hiddenFooter.mjs
Normal file
21
integration-tests/test/hiddenFooter.mjs
Normal file
@@ -0,0 +1,21 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import { By } from 'selenium-webdriver';
|
||||
|
||||
describe('config: hiddenFooter', function () {
|
||||
before(async function () {
|
||||
await runner.start('hiddenFooter')
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
});
|
||||
|
||||
it('Check that footer is hidden', async () => {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
let footer = await webdriver.findElement(By.tagName('footer'))
|
||||
|
||||
expect(await footer.isDisplayed()).to.be.false
|
||||
})
|
||||
})
|
||||
20
integration-tests/test/hiddenNav.mjs
Normal file
20
integration-tests/test/hiddenNav.mjs
Normal file
@@ -0,0 +1,20 @@
|
||||
import {expect} from 'chai';
|
||||
import {By} from 'selenium-webdriver';
|
||||
|
||||
describe('config: hiddenNav', function () {
|
||||
before(async function () {
|
||||
await runner.start('hiddenNav')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
it('nav is hidden', async () => {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
|
||||
const toggler = await webdriver.findElement(By.id('sidebar-toggle-wrapper'))
|
||||
|
||||
expect(await toggler.isDisplayed()).to.be.false
|
||||
})
|
||||
})
|
||||
34
integration-tests/test/multipleDropdowns.js
Normal file
34
integration-tests/test/multipleDropdowns.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { expect } from 'chai'
|
||||
import { By, until } from 'selenium-webdriver'
|
||||
import fs from 'node:fs'
|
||||
|
||||
describe('config: multipleDropdowns', function () {
|
||||
before(async function () {
|
||||
await runner.start('multipleDropdowns')
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
await runner.stop()
|
||||
})
|
||||
|
||||
it('Multiple dropdowns are possible', async function() {
|
||||
await webdriver.get('http://localhost:1337')
|
||||
await webdriver.manage().setTimeouts({ implicit: 2000 });
|
||||
|
||||
const button = await webdriver.findElement(By.id('actionButton_bdc45101bbd12c1397557790d9f3e059')).findElement(By.tagName('button'));
|
||||
|
||||
expect(button).to.not.be.undefined;
|
||||
|
||||
await button.click()
|
||||
|
||||
const dialog = await webdriver.findElement(By.id('argument-popup'));
|
||||
|
||||
await webdriver.wait(until.elementIsVisible(dialog), 2000)
|
||||
|
||||
const selects = await dialog.findElements(By.tagName('select'))
|
||||
|
||||
expect(selects).to.have.length(2)
|
||||
expect(await selects[0].findElements(By.tagName('option'))).to.have.length(2)
|
||||
expect(await selects[1].findElements(By.tagName('option'))).to.have.length(3)
|
||||
})
|
||||
})
|
||||
@@ -25,7 +25,7 @@ func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.A
|
||||
"User": user.Username,
|
||||
"Action": action.Title,
|
||||
"ACL": acl.Name,
|
||||
}).Debug("isAllowedExec - Matched ACL")
|
||||
}).Trace("isAllowedExec - Matched ACL")
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func IsAllowedExec(cfg *config.Config, user *AuthenticatedUser, action *config.A
|
||||
log.WithFields(log.Fields{
|
||||
"User": user.Username,
|
||||
"Action": action.Title,
|
||||
}).Debug("isAllowedExec - No ACLs matched")
|
||||
}).Trace("isAllowedExec - No ACLs matched")
|
||||
|
||||
return cfg.DefaultPermissions.Exec
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func IsAllowedView(cfg *config.Config, user *AuthenticatedUser, action *config.A
|
||||
"User": user.Username,
|
||||
"Action": action.Title,
|
||||
"ACL": acl.Name,
|
||||
}).Debug("isAllowedView - Matched ACL")
|
||||
}).Trace("isAllowedView - Matched ACL")
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -56,7 +56,7 @@ func IsAllowedView(cfg *config.Config, user *AuthenticatedUser, action *config.A
|
||||
log.WithFields(log.Fields{
|
||||
"User": user.Username,
|
||||
"Action": action.Title,
|
||||
}).Debug("isAllowedView - No ACLs matched")
|
||||
}).Trace("isAllowedView - No ACLs matched")
|
||||
|
||||
return cfg.DefaultPermissions.View
|
||||
}
|
||||
@@ -75,7 +75,10 @@ func getMetdataKeyOrEmpty(md metadata.MD, key string) string {
|
||||
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser {
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
|
||||
ret := &AuthenticatedUser{}
|
||||
ret := &AuthenticatedUser{
|
||||
Username: "guest",
|
||||
Usergroup: "guest",
|
||||
}
|
||||
|
||||
if ok {
|
||||
ret.Username = getMetdataKeyOrEmpty(md, "username")
|
||||
@@ -87,7 +90,7 @@ func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser
|
||||
log.WithFields(log.Fields{
|
||||
"username": ret.Username,
|
||||
"usergroup": ret.Usergroup,
|
||||
}).Infof("UserFromContext")
|
||||
}).Debugf("UserFromContext")
|
||||
|
||||
return ret
|
||||
}
|
||||
@@ -107,8 +110,10 @@ func buildUserAcls(cfg *config.Config, user *AuthenticatedUser) {
|
||||
}
|
||||
}
|
||||
|
||||
func isACLRelevant(cfg *config.Config, actionAcls []string, acl config.AccessControlList, user *AuthenticatedUser) bool {
|
||||
func isACLRelevantToAction(cfg *config.Config, actionAcls []string, acl config.AccessControlList, user *AuthenticatedUser) bool {
|
||||
if !slices.Contains(user.acls, acl.Name) {
|
||||
// If the user does not have this ACL, then it is not relevant
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -127,7 +132,7 @@ func getRelevantAcls(cfg *config.Config, actionAcls []string, user *Authenticate
|
||||
var ret []*config.AccessControlList
|
||||
|
||||
for _, acl := range cfg.AccessControlLists {
|
||||
if isACLRelevant(cfg, actionAcls, acl, user) {
|
||||
if isACLRelevantToAction(cfg, actionAcls, acl, user) {
|
||||
ret = append(ret, &acl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,22 @@ package config
|
||||
// Action represents the core functionality of OliveTin - commands that show up
|
||||
// as buttons in the UI.
|
||||
type Action struct {
|
||||
ID string
|
||||
Title string
|
||||
Icon string
|
||||
Shell string
|
||||
CSS map[string]string `mapstructure:"omitempty"`
|
||||
Timeout int
|
||||
Acls []string
|
||||
ExecOnStartup bool
|
||||
ExecOnCron []string
|
||||
Arguments []ActionArgument
|
||||
ID string
|
||||
Title string
|
||||
TitleAlias string
|
||||
Icon string
|
||||
Shell string
|
||||
ShellAfterCompleted string
|
||||
CSS map[string]string `mapstructure:"omitempty"`
|
||||
Timeout int
|
||||
Acls []string
|
||||
ExecOnStartup bool
|
||||
ExecOnCron []string
|
||||
ExecOnFileCreatedInDir []string
|
||||
ExecOnFileChangedInDir []string
|
||||
MaxConcurrent int
|
||||
Arguments []ActionArgument
|
||||
PopupOnStart bool
|
||||
}
|
||||
|
||||
// ActionArgument objects appear on Actions.
|
||||
@@ -73,9 +79,10 @@ type Config struct {
|
||||
ShowNavigation bool
|
||||
ShowNewVersions bool
|
||||
AuthJwtCookieName string
|
||||
AuthJwtSecret string
|
||||
AuthJwtSecret string // mutually exclusive with pub key config fields
|
||||
AuthJwtClaimUsername string
|
||||
AuthJwtClaimUserGroup string
|
||||
AuthJwtPubKeyPath string // will read pub key from file on disk
|
||||
AuthHttpHeaderUsername string
|
||||
AuthHttpHeaderUserGroup string
|
||||
DefaultPermissions PermissionsList
|
||||
|
||||
@@ -13,6 +13,17 @@ func (cfg *Config) FindAction(actionTitle string) *Action {
|
||||
|
||||
// FindArg will return an arg if there is a match on Name
|
||||
func (action *Action) FindArg(name string) *ActionArgument {
|
||||
if name == "stdout" || name == "exitCode" {
|
||||
return &ActionArgument{
|
||||
Name: name,
|
||||
Type: "very_dangerous_raw_string",
|
||||
}
|
||||
}
|
||||
|
||||
return action.findArg(name)
|
||||
}
|
||||
|
||||
func (action *Action) findArg(name string) *ActionArgument {
|
||||
for _, arg := range action.Arguments {
|
||||
if arg.Name == name {
|
||||
return &arg
|
||||
|
||||
@@ -30,6 +30,10 @@ func (action *Action) sanitize() {
|
||||
|
||||
action.Icon = lookupHTMLIcon(action.Icon)
|
||||
|
||||
if action.MaxConcurrent < 1 {
|
||||
action.MaxConcurrent = 1
|
||||
}
|
||||
|
||||
for idx := range action.Arguments {
|
||||
action.Arguments[idx].sanitize()
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ var (
|
||||
|
||||
func parseActionArguments(rawShellCommand string, values map[string]string, action *config.Action) (string, error) {
|
||||
log.WithFields(log.Fields{
|
||||
"cmd": rawShellCommand,
|
||||
}).Infof("Before Parse Args")
|
||||
"actionTitle": action.Title,
|
||||
"cmd": rawShellCommand,
|
||||
}).Infof("Action parse args - Before")
|
||||
|
||||
r := regexp.MustCompile("{{ *?([a-zA-Z0-9_]+?) *?}}")
|
||||
matches := r.FindAllStringSubmatch(rawShellCommand, -1)
|
||||
@@ -51,8 +52,9 @@ func parseActionArguments(rawShellCommand string, values map[string]string, acti
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"cmd": rawShellCommand,
|
||||
}).Infof("After Parse Args")
|
||||
"actionTitle": action.Title,
|
||||
"cmd": rawShellCommand,
|
||||
}).Infof("Action parse args - After")
|
||||
|
||||
return rawShellCommand, nil
|
||||
}
|
||||
|
||||
@@ -1,109 +1,190 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Executor represents a helper class for executing commands. It's main method
|
||||
// is ExecRequest
|
||||
type Executor struct {
|
||||
Logs map[string]*InternalLogEntry
|
||||
|
||||
listeners []listener
|
||||
|
||||
chainOfCommand []executorStepFunc
|
||||
}
|
||||
|
||||
// ExecutionRequest is a request to execute an action. It's passed to an
|
||||
// Executor. They're created from the grpcapi.
|
||||
type ExecutionRequest struct {
|
||||
ActionName string
|
||||
Action *config.Action
|
||||
Arguments map[string]string
|
||||
UUID string
|
||||
Tags []string
|
||||
action *config.Action
|
||||
Cfg *config.Config
|
||||
AuthenticatedUser *acl.AuthenticatedUser
|
||||
logEntry *InternalLogEntry
|
||||
finalParsedCommand string
|
||||
executor *Executor
|
||||
}
|
||||
|
||||
// InternalLogEntry objects are created by an Executor, and represent the final
|
||||
// state of execution (even if the command is not executed). It's designed to be
|
||||
// easily serializable.
|
||||
type InternalLogEntry struct {
|
||||
Datetime string
|
||||
Stdout string
|
||||
Stderr string
|
||||
TimedOut bool
|
||||
ExitCode int32
|
||||
Tags []string
|
||||
DatetimeStarted string
|
||||
DatetimeFinished string
|
||||
Stdout string
|
||||
Stderr string
|
||||
StdoutBuffer io.ReadCloser
|
||||
StderrBuffer io.ReadCloser
|
||||
TimedOut bool
|
||||
Blocked bool
|
||||
ExitCode int32
|
||||
Tags []string
|
||||
ExecutionStarted bool
|
||||
ExecutionFinished bool
|
||||
|
||||
/*
|
||||
The following two properties are obviously on Action normally, but it's useful
|
||||
The following 3 properties are obviously on Action normally, but it's useful
|
||||
that logs are lightweight (so we don't need to have an action associated to
|
||||
logs, etc. Therefore, we duplicate those values here.
|
||||
*/
|
||||
ActionTitle string
|
||||
ActionIcon string
|
||||
UUID string
|
||||
}
|
||||
|
||||
type executorStepFunc func(*ExecutionRequest) bool
|
||||
|
||||
// Executor represents a helper class for executing commands. It's main method
|
||||
// is ExecRequest
|
||||
type Executor struct {
|
||||
Logs []InternalLogEntry
|
||||
|
||||
chainOfCommand []executorStepFunc
|
||||
}
|
||||
|
||||
// ExecRequest processes an ExecutionRequest
|
||||
func (e *Executor) ExecRequest(req *ExecutionRequest) *pb.StartActionResponse {
|
||||
req.logEntry = &InternalLogEntry{
|
||||
Datetime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
ActionTitle: req.ActionName,
|
||||
Stdout: "",
|
||||
Stderr: "",
|
||||
ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
|
||||
}
|
||||
|
||||
for _, step := range e.chainOfCommand {
|
||||
if !step(req) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
e.Logs = append(e.Logs, *req.logEntry)
|
||||
|
||||
return &pb.StartActionResponse{
|
||||
LogEntry: &pb.LogEntry{
|
||||
ActionTitle: req.logEntry.ActionTitle,
|
||||
ActionIcon: req.logEntry.ActionIcon,
|
||||
Datetime: req.logEntry.Datetime,
|
||||
Stderr: req.logEntry.Stderr,
|
||||
Stdout: req.logEntry.Stdout,
|
||||
TimedOut: req.logEntry.TimedOut,
|
||||
ExitCode: req.logEntry.ExitCode,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultExecutor returns an Executor, with a sensible "chain of command" for
|
||||
// executing actions.
|
||||
func DefaultExecutor() *Executor {
|
||||
e := Executor{}
|
||||
e.Logs = make(map[string]*InternalLogEntry)
|
||||
|
||||
e.chainOfCommand = []executorStepFunc{
|
||||
stepLogRequested,
|
||||
stepFindAction,
|
||||
stepConcurrencyCheck,
|
||||
stepACLCheck,
|
||||
stepParseArgs,
|
||||
stepLogStart,
|
||||
stepExec,
|
||||
stepExecAfter,
|
||||
stepLogFinish,
|
||||
}
|
||||
|
||||
return &e
|
||||
}
|
||||
|
||||
type listener interface {
|
||||
OnExecutionStarted(actionName string)
|
||||
OnExecutionFinished(logEntry *InternalLogEntry)
|
||||
}
|
||||
|
||||
func (e *Executor) AddListener(m listener) {
|
||||
e.listeners = append(e.listeners, m)
|
||||
}
|
||||
|
||||
// ExecRequest processes an ExecutionRequest
|
||||
func (e *Executor) ExecRequest(req *ExecutionRequest) (*sync.WaitGroup, string) {
|
||||
req.executor = e
|
||||
|
||||
// req.UUID is now set by the client, so that they can track the request
|
||||
// from start to finish. This means that a malicious client could send
|
||||
// duplicate UUIDs (or just random strings), but this is the only way.
|
||||
|
||||
req.logEntry = &InternalLogEntry{
|
||||
DatetimeStarted: time.Now().Format("2006-01-02 15:04:05"),
|
||||
ActionTitle: req.ActionName,
|
||||
UUID: req.UUID,
|
||||
Stdout: "",
|
||||
Stderr: "",
|
||||
ExitCode: -1337, // If an Action is not actually executed, this is the default exit code.
|
||||
ExecutionStarted: false,
|
||||
ExecutionFinished: false,
|
||||
}
|
||||
|
||||
e.Logs[req.UUID] = req.logEntry
|
||||
|
||||
for _, listener := range e.listeners {
|
||||
listener.OnExecutionStarted(req.ActionName)
|
||||
}
|
||||
|
||||
wg := new(sync.WaitGroup)
|
||||
wg.Add(1)
|
||||
|
||||
go func() {
|
||||
e.execChain(req)
|
||||
defer wg.Done()
|
||||
}()
|
||||
|
||||
return wg, req.UUID
|
||||
}
|
||||
|
||||
func (e *Executor) execChain(req *ExecutionRequest) {
|
||||
for _, step := range e.chainOfCommand {
|
||||
if !step(req) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
req.logEntry.ExecutionFinished = true
|
||||
|
||||
// This isn't a step, because we want to notify all listeners, irrespective
|
||||
// of how many steps were actually executed.
|
||||
notifyListeners(req)
|
||||
}
|
||||
|
||||
func getConcurrentCount(req *ExecutionRequest) int {
|
||||
concurrentCount := 0
|
||||
|
||||
for _, log := range req.executor.Logs {
|
||||
if log.ActionTitle == req.ActionName && !log.ExecutionFinished {
|
||||
concurrentCount += 1
|
||||
}
|
||||
}
|
||||
|
||||
return concurrentCount
|
||||
}
|
||||
|
||||
func stepConcurrencyCheck(req *ExecutionRequest) bool {
|
||||
concurrentCount := getConcurrentCount(req)
|
||||
|
||||
// Note that the current execution is counted int the logs, so when checking we +1
|
||||
if concurrentCount >= (req.Action.MaxConcurrent + 1) {
|
||||
msg := fmt.Sprintf("Blocked from executing. This would mean this action is running %d times concurrently, but this action has maxExecutions set to %d.", concurrentCount, req.Action.MaxConcurrent)
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"actionTitle": req.ActionName,
|
||||
}).Warnf(msg)
|
||||
|
||||
req.logEntry.Stdout = msg
|
||||
req.logEntry.Blocked = true
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func stepFindAction(req *ExecutionRequest) bool {
|
||||
if req.Action != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
actualAction := req.Cfg.FindAction(req.ActionName)
|
||||
|
||||
if actualAction == nil {
|
||||
@@ -116,20 +197,20 @@ func stepFindAction(req *ExecutionRequest) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
req.action = actualAction
|
||||
req.Action = actualAction
|
||||
req.logEntry.ActionIcon = actualAction.Icon
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func stepACLCheck(req *ExecutionRequest) bool {
|
||||
return acl.IsAllowedExec(req.Cfg, req.AuthenticatedUser, req.action)
|
||||
return acl.IsAllowedExec(req.Cfg, req.AuthenticatedUser, req.Action)
|
||||
}
|
||||
|
||||
func stepParseArgs(req *ExecutionRequest) bool {
|
||||
var err error
|
||||
|
||||
req.finalParsedCommand, err = parseActionArguments(req.action.Shell, req.Arguments, req.action)
|
||||
req.finalParsedCommand, err = parseActionArguments(req.Action.Shell, req.Arguments, req.Action)
|
||||
|
||||
if err != nil {
|
||||
req.logEntry.Stdout = err.Error()
|
||||
@@ -142,10 +223,18 @@ func stepParseArgs(req *ExecutionRequest) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func stepLogRequested(req *ExecutionRequest) bool {
|
||||
log.WithFields(log.Fields{
|
||||
"actionTitle": req.ActionName,
|
||||
}).Infof("Action requested")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func stepLogStart(req *ExecutionRequest) bool {
|
||||
log.WithFields(log.Fields{
|
||||
"title": req.action.Title,
|
||||
"timeout": req.action.Timeout,
|
||||
"actionTitle": req.Action.Title,
|
||||
"timeout": req.Action.Timeout,
|
||||
}).Infof("Action starting")
|
||||
|
||||
return true
|
||||
@@ -153,16 +242,22 @@ func stepLogStart(req *ExecutionRequest) bool {
|
||||
|
||||
func stepLogFinish(req *ExecutionRequest) bool {
|
||||
log.WithFields(log.Fields{
|
||||
"title": req.action.Title,
|
||||
"stdout": req.logEntry.Stdout,
|
||||
"stderr": req.logEntry.Stderr,
|
||||
"timedOut": req.logEntry.TimedOut,
|
||||
"exit": req.logEntry.ExitCode,
|
||||
"actionTitle": req.Action.Title,
|
||||
"stdout": req.logEntry.Stdout,
|
||||
"stderr": req.logEntry.Stderr,
|
||||
"timedOut": req.logEntry.TimedOut,
|
||||
"exit": req.logEntry.ExitCode,
|
||||
}).Infof("Action finished")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func notifyListeners(req *ExecutionRequest) {
|
||||
for _, listener := range req.executor.listeners {
|
||||
listener.OnExecutionFinished(req.logEntry)
|
||||
}
|
||||
}
|
||||
|
||||
func wrapCommandInShell(ctx context.Context, finalParsedCommand string) *exec.Cmd {
|
||||
if runtime.GOOS == "windows" {
|
||||
return exec.CommandContext(ctx, "cmd", "/C", finalParsedCommand)
|
||||
@@ -172,7 +267,7 @@ func wrapCommandInShell(ctx context.Context, finalParsedCommand string) *exec.Cm
|
||||
}
|
||||
|
||||
func stepExec(req *ExecutionRequest) bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.action.Timeout)*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Action.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var stdout bytes.Buffer
|
||||
@@ -181,8 +276,17 @@ func stepExec(req *ExecutionRequest) bool {
|
||||
cmd := wrapCommandInShell(ctx, req.finalParsedCommand)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
req.logEntry.StdoutBuffer, _ = cmd.StdoutPipe()
|
||||
req.logEntry.StderrBuffer, _ = cmd.StderrPipe()
|
||||
|
||||
runerr := cmd.Run()
|
||||
req.logEntry.ExecutionStarted = true
|
||||
|
||||
runerr := cmd.Start()
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
// req.logEntry.Stdout = req.logEntry.StdoutBuffer.String()
|
||||
// req.logEntry.Stderr = req.logEntry.StderrBuffer.String()
|
||||
|
||||
req.logEntry.ExitCode = int32(cmd.ProcessState.ExitCode())
|
||||
req.logEntry.Stdout = stdout.String()
|
||||
@@ -197,6 +301,51 @@ func stepExec(req *ExecutionRequest) bool {
|
||||
}
|
||||
|
||||
req.logEntry.Tags = req.Tags
|
||||
req.logEntry.DatetimeFinished = time.Now().Format("2006-01-02 15:04:05")
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func stepExecAfter(req *ExecutionRequest) bool {
|
||||
if req.Action.ShellAfterCompleted == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Action.Timeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
args := map[string]string{
|
||||
"stdout": req.logEntry.Stdout,
|
||||
"exitCode": fmt.Sprintf("%v", req.logEntry.ExitCode),
|
||||
}
|
||||
|
||||
finalParsedCommand, _ := parseActionArguments(req.Action.ShellAfterCompleted, args, req.Action)
|
||||
|
||||
cmd := wrapCommandInShell(ctx, finalParsedCommand)
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
req.logEntry.StdoutBuffer, _ = cmd.StdoutPipe()
|
||||
req.logEntry.StderrBuffer, _ = cmd.StderrPipe()
|
||||
|
||||
runerr := cmd.Start()
|
||||
|
||||
cmd.Wait()
|
||||
|
||||
req.logEntry.Stdout += "---\n" + stdout.String()
|
||||
req.logEntry.Stderr += "---\n" + stderr.String()
|
||||
|
||||
if runerr != nil {
|
||||
req.logEntry.Stderr = runerr.Error() + "\n\n" + req.logEntry.Stderr
|
||||
}
|
||||
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
req.logEntry.Stderr += "Your shellAfterCommand command timed out."
|
||||
}
|
||||
|
||||
req.logEntry.Stdout += fmt.Sprintf("Your shellAfterCommand exited with code %v", cmd.ProcessState.ExitCode())
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ func TestCreateExecutorAndExec(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
e.ExecRequest(&req)
|
||||
|
||||
assert.NotNil(t, e, "Create an executor")
|
||||
|
||||
assert.NotNil(t, e.ExecRequest(&req), "Execute a request")
|
||||
wg, _ := e.ExecRequest(&req)
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, int32(0), req.logEntry.ExitCode, "Exit code is zero")
|
||||
}
|
||||
|
||||
@@ -59,7 +59,8 @@ func TestExecNonExistant(t *testing.T) {
|
||||
Cfg: cfg,
|
||||
}
|
||||
|
||||
e.ExecRequest(&req)
|
||||
wg, _ := e.ExecRequest(&req)
|
||||
wg.Wait()
|
||||
|
||||
assert.Equal(t, int32(-1337), req.logEntry.ExitCode, "Log entry is set to an internal error code")
|
||||
assert.Equal(t, "", req.logEntry.ActionIcon, "Log entry icon wasnt found")
|
||||
|
||||
@@ -3,10 +3,13 @@ package grpcapi
|
||||
import (
|
||||
ctx "context"
|
||||
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
acl "github.com/OliveTin/OliveTin/internal/acl"
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
@@ -19,7 +22,8 @@ var (
|
||||
)
|
||||
|
||||
type oliveTinAPI struct {
|
||||
pb.UnimplementedOliveTinApiServer
|
||||
// Uncomment this if you want to allow undefined methods during dev.
|
||||
// pb.UnimplementedOliveTinApiServiceServer
|
||||
|
||||
executor *executor.Executor
|
||||
}
|
||||
@@ -27,22 +31,178 @@ type oliveTinAPI struct {
|
||||
func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) {
|
||||
args := make(map[string]string)
|
||||
|
||||
log.Debugf("SA %v", req)
|
||||
|
||||
for _, arg := range req.Arguments {
|
||||
args[arg.Name] = arg.Value
|
||||
}
|
||||
|
||||
execReq := executor.ExecutionRequest{
|
||||
ActionName: req.ActionName,
|
||||
UUID: req.Uuid,
|
||||
Arguments: args,
|
||||
AuthenticatedUser: acl.UserFromContext(ctx, cfg),
|
||||
Cfg: cfg,
|
||||
}
|
||||
|
||||
return api.executor.ExecRequest(&execReq), nil
|
||||
_, uuid := api.executor.ExecRequest(&execReq)
|
||||
|
||||
return &pb.StartActionResponse{
|
||||
ExecutionUuid: uuid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *pb.StartActionAndWaitRequest) (*pb.StartActionAndWaitResponse, error) {
|
||||
args := make(map[string]string)
|
||||
|
||||
execReq := executor.ExecutionRequest{
|
||||
ActionName: req.ActionName,
|
||||
UUID: uuid.NewString(),
|
||||
Arguments: args,
|
||||
AuthenticatedUser: acl.UserFromContext(ctx, cfg),
|
||||
Cfg: cfg,
|
||||
}
|
||||
|
||||
wg, _ := api.executor.ExecRequest(&execReq)
|
||||
wg.Wait()
|
||||
|
||||
internalLogEntry, ok := api.executor.Logs[execReq.UUID]
|
||||
|
||||
if ok {
|
||||
return &pb.StartActionAndWaitResponse{
|
||||
LogEntry: internalLogEntryToPb(internalLogEntry),
|
||||
}, nil
|
||||
} else {
|
||||
return nil, errors.New("Execution not found!")
|
||||
}
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) StartActionByAlias(ctx ctx.Context, req *pb.StartActionByAliasRequest) (*pb.StartActionByAliasResponse, error) {
|
||||
args := make(map[string]string)
|
||||
|
||||
action := findActionByAlias(req.ActionAlias)
|
||||
|
||||
if action == nil {
|
||||
log.Warnf("ByAlias action alias not found: %v, cannot start execution.", req.ActionAlias)
|
||||
return &pb.StartActionByAliasResponse{
|
||||
ExecutionUuid: "",
|
||||
}, errors.New("ByAlias action alias not found")
|
||||
}
|
||||
|
||||
execReq := executor.ExecutionRequest{
|
||||
ActionName: action.Title,
|
||||
Action: action,
|
||||
UUID: uuid.NewString(),
|
||||
Arguments: args,
|
||||
AuthenticatedUser: &acl.AuthenticatedUser{
|
||||
Username: "webhook",
|
||||
Usergroup: "webhook",
|
||||
},
|
||||
Cfg: cfg,
|
||||
}
|
||||
|
||||
_, uuid := api.executor.ExecRequest(&execReq)
|
||||
|
||||
return &pb.StartActionByAliasResponse{
|
||||
ExecutionUuid: uuid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) StartActionByAliasAndWait(ctx ctx.Context, req *pb.StartActionByAliasAndWaitRequest) (*pb.StartActionByAliasAndWaitResponse, error) {
|
||||
args := make(map[string]string)
|
||||
|
||||
action := findActionByAlias(req.ActionAlias)
|
||||
|
||||
if action == nil {
|
||||
log.Warnf("ByAlias action alias not found: %v, cannot start execution.", req.ActionAlias)
|
||||
|
||||
return &pb.StartActionByAliasAndWaitResponse{}, errors.New("ByAlias action alias not found")
|
||||
}
|
||||
|
||||
execReq := executor.ExecutionRequest{
|
||||
ActionName: action.Title,
|
||||
Action: action,
|
||||
UUID: uuid.NewString(),
|
||||
Arguments: args,
|
||||
AuthenticatedUser: &acl.AuthenticatedUser{
|
||||
Username: "webhook",
|
||||
Usergroup: "webhook",
|
||||
},
|
||||
Cfg: cfg,
|
||||
}
|
||||
|
||||
wg, _ := api.executor.ExecRequest(&execReq)
|
||||
wg.Wait()
|
||||
|
||||
internalLogEntry, ok := api.executor.Logs[execReq.UUID]
|
||||
|
||||
if ok {
|
||||
return &pb.StartActionByAliasAndWaitResponse{
|
||||
LogEntry: internalLogEntryToPb(internalLogEntry),
|
||||
}, nil
|
||||
} else {
|
||||
return nil, errors.New("Execution not found!")
|
||||
}
|
||||
}
|
||||
|
||||
func internalLogEntryToPb(logEntry *executor.InternalLogEntry) *pb.LogEntry {
|
||||
return &pb.LogEntry{
|
||||
ActionTitle: logEntry.ActionTitle,
|
||||
ActionIcon: logEntry.ActionIcon,
|
||||
DatetimeStarted: logEntry.DatetimeStarted,
|
||||
DatetimeFinished: logEntry.DatetimeFinished,
|
||||
Stdout: logEntry.Stdout,
|
||||
Stderr: logEntry.Stderr,
|
||||
TimedOut: logEntry.TimedOut,
|
||||
Blocked: logEntry.Blocked,
|
||||
ExitCode: logEntry.ExitCode,
|
||||
Tags: logEntry.Tags,
|
||||
ExecutionUuid: logEntry.UUID,
|
||||
ExecutionStarted: logEntry.ExecutionStarted,
|
||||
ExecutionFinished: logEntry.ExecutionFinished,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *pb.ExecutionStatusRequest) (*pb.ExecutionStatusResponse, error) {
|
||||
res := &pb.ExecutionStatusResponse{}
|
||||
|
||||
logEntry, ok := api.executor.Logs[req.ExecutionUuid]
|
||||
|
||||
if !ok {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.LogEntry = internalLogEntryToPb(logEntry)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
/**
|
||||
func (api *oliveTinAPI) WatchExecution(req *pb.WatchExecutionRequest, srv pb.OliveTinApi_WatchExecutionServer) error {
|
||||
log.Infof("Watch")
|
||||
|
||||
if logEntry, ok := api.executor.Logs[req.ExecutionUuid]; !ok {
|
||||
log.Errorf("Execution not found: %v", req.ExecutionUuid)
|
||||
|
||||
return nil
|
||||
} else {
|
||||
if logEntry.ExecutionStarted {
|
||||
for !logEntry.ExecutionCompleted {
|
||||
tmp := make([]byte, 256)
|
||||
|
||||
red, err := io.ReadAtLeast(logEntry.StdoutBuffer, tmp, 1)
|
||||
|
||||
log.Infof("%v %v", red, err)
|
||||
|
||||
srv.Send(&pb.WatchExecutionUpdate{
|
||||
Update: string(tmp),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashboardComponentsRequest) (*pb.GetDashboardComponentsResponse, error) {
|
||||
user := acl.UserFromContext(ctx, cfg)
|
||||
|
||||
@@ -52,7 +212,7 @@ func (api *oliveTinAPI) GetDashboardComponents(ctx ctx.Context, req *pb.GetDashb
|
||||
log.Warn("Zero actions found - check that you have some actions defined, with a view permission")
|
||||
}
|
||||
|
||||
log.Debugf("GetDashboardComponents: %v", res)
|
||||
log.Tracef("GetDashboardComponents: %v", res)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -62,19 +222,19 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *pb.GetLogsRequest) (*pb.Ge
|
||||
|
||||
// TODO Limit to 10 entries or something to prevent browser lag.
|
||||
|
||||
for _, logEntry := range api.executor.Logs {
|
||||
ret.Logs = append(ret.Logs, &pb.LogEntry{
|
||||
ActionTitle: logEntry.ActionTitle,
|
||||
ActionIcon: logEntry.ActionIcon,
|
||||
Datetime: logEntry.Datetime,
|
||||
Stdout: logEntry.Stdout,
|
||||
Stderr: logEntry.Stderr,
|
||||
TimedOut: logEntry.TimedOut,
|
||||
ExitCode: logEntry.ExitCode,
|
||||
Tags: logEntry.Tags,
|
||||
})
|
||||
for uuid, logEntry := range api.executor.Logs {
|
||||
pbLogEntry := internalLogEntryToPb(logEntry)
|
||||
pbLogEntry.ExecutionUuid = uuid
|
||||
|
||||
ret.Logs = append(ret.Logs, pbLogEntry)
|
||||
}
|
||||
|
||||
sorter := func(i, j int) bool {
|
||||
return ret.Logs[i].DatetimeStarted < ret.Logs[j].DatetimeStarted
|
||||
}
|
||||
|
||||
sort.Slice(ret.Logs, sorter)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -130,7 +290,7 @@ func Start(globalConfig *config.Config, ex *executor.Executor) {
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer()
|
||||
pb.RegisterOliveTinApiServer(grpcServer, newServer(ex))
|
||||
pb.RegisterOliveTinApiServiceServer(grpcServer, newServer(ex))
|
||||
|
||||
err = grpcServer.Serve(lis)
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ func actionsCfgToPb(cfgActions []config.Action, user *acl.AuthenticatedUser) *pb
|
||||
|
||||
func actionCfgToPb(action config.Action, user *acl.AuthenticatedUser) *pb.Action {
|
||||
btn := pb.Action{
|
||||
Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))),
|
||||
Title: action.Title,
|
||||
Icon: action.Icon,
|
||||
CanExec: acl.IsAllowedExec(cfg, user, &action),
|
||||
Id: fmt.Sprintf("%x", md5.Sum([]byte(action.Title))),
|
||||
Title: action.Title,
|
||||
Icon: action.Icon,
|
||||
CanExec: acl.IsAllowedExec(cfg, user, &action),
|
||||
PopupOnStart: action.PopupOnStart,
|
||||
}
|
||||
|
||||
for _, cfgArg := range action.Arguments {
|
||||
@@ -61,3 +62,15 @@ func buildChoices(choices []config.ActionArgumentChoice) []*pb.ActionArgumentCho
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func findActionByAlias(alias string) *config.Action {
|
||||
for _, action := range cfg.Actions {
|
||||
if action.TitleAlias != "" {
|
||||
if action.TitleAlias == alias {
|
||||
return &action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func init() {
|
||||
|
||||
lis = bufconn.Listen(bufSize)
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterOliveTinApiServer(s, newServer(ex))
|
||||
pb.RegisterOliveTinApiServiceServer(s, newServer(ex))
|
||||
|
||||
go func() {
|
||||
if err := s.Serve(lis); err != nil {
|
||||
@@ -39,7 +39,7 @@ func bufDialer(context.Context, string) (net.Conn, error) {
|
||||
return lis.Dial()
|
||||
}
|
||||
|
||||
func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*grpc.ClientConn, pb.OliveTinApiClient) {
|
||||
func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*grpc.ClientConn, pb.OliveTinApiServiceClient) {
|
||||
cfg = injectedConfig
|
||||
|
||||
ctx := context.Background()
|
||||
@@ -50,7 +50,7 @@ func getNewTestServerAndClient(t *testing.T, injectedConfig *config.Config) (*gr
|
||||
t.Fatalf("Failed to dial bufnet: %v", err)
|
||||
}
|
||||
|
||||
client := pb.NewOliveTinApiClient(conn)
|
||||
client := pb.NewOliveTinApiServiceClient(conn)
|
||||
|
||||
return conn, client
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ func parseHttpHeaderForAuth(req *http.Request) (string, string) {
|
||||
username, ok := req.Header[cfg.AuthHttpHeaderUsername]
|
||||
|
||||
if !ok {
|
||||
log.Warnf("Config has AuthHttpHeaderUsername set to %v, but it was not found", cfg.AuthHttpHeaderUsername)
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
@@ -30,10 +32,16 @@ func parseHttpHeaderForAuth(req *http.Request) (string, string) {
|
||||
usergroup, ok := req.Header[cfg.AuthHttpHeaderUserGroup]
|
||||
|
||||
if ok {
|
||||
log.Debugf("HTTP Header Auth found a username and usergroup")
|
||||
|
||||
return username[0], usergroup[0]
|
||||
} else {
|
||||
log.Warnf("Config has AuthHttpHeaderUserGroup set to %v, but it was not found", cfg.AuthHttpHeaderUserGroup)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("HTTP Header Auth found a username, but usergroup is not being used")
|
||||
|
||||
return username[0], ""
|
||||
}
|
||||
|
||||
@@ -54,11 +62,15 @@ func parseRequestMetadata(ctx context.Context, req *http.Request) metadata.MD {
|
||||
"usergroup", usergroup,
|
||||
)
|
||||
|
||||
log.Debugf("jwt usable claims: %+v", md)
|
||||
log.Debugf("api request metadata: %+v", md)
|
||||
|
||||
return md
|
||||
}
|
||||
|
||||
func SetGlobalRestConfig(config *config.Config) {
|
||||
cfg = config
|
||||
}
|
||||
|
||||
func startRestAPIServer(globalConfig *config.Config) error {
|
||||
cfg = globalConfig
|
||||
|
||||
@@ -76,7 +88,7 @@ func startRestAPIServer(globalConfig *config.Config) error {
|
||||
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
|
||||
Marshaler: &runtime.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
UseProtoNames: false, // eg: canExec for js instead of can_exec from protobuf
|
||||
EmitUnpopulated: true,
|
||||
},
|
||||
},
|
||||
@@ -84,7 +96,7 @@ func startRestAPIServer(globalConfig *config.Config) error {
|
||||
)
|
||||
opts := []grpc.DialOption{grpc.WithInsecure()}
|
||||
|
||||
err := gw.RegisterOliveTinApiHandlerFromEndpoint(ctx, mux, cfg.ListenAddressGrpcActions, opts)
|
||||
err := gw.RegisterOliveTinApiServiceHandlerFromEndpoint(ctx, mux, cfg.ListenAddressGrpcActions, opts)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Could not register REST API Handler %v", err)
|
||||
|
||||
@@ -1,14 +1,58 @@
|
||||
package httpservers
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
func parseJwtToken(cookieValue string) (*jwt.Token, error) {
|
||||
var (
|
||||
pubKeyBytes []byte = nil
|
||||
pubKey *rsa.PublicKey
|
||||
)
|
||||
|
||||
func readPublicKey() error {
|
||||
if pubKeyBytes != nil {
|
||||
return nil // Already read.
|
||||
}
|
||||
|
||||
pubKeyBytes, err := os.ReadFile(cfg.AuthJwtPubKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read public key from file %s", cfg.AuthJwtPubKeyPath)
|
||||
}
|
||||
|
||||
// Since the token is RSA (which we validated at the start of this function), the return type of this function actually has to be rsa.PublicKey!
|
||||
pubKey, err = jwt.ParseRSAPublicKeyFromPEM(pubKeyBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing public key object (from %s)", cfg.AuthJwtPubKeyPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseJwtTokenWithKey(cookieValue string) (*jwt.Token, error) {
|
||||
err := readPublicKey()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"expected token algorithm '%v' but got '%v'",
|
||||
jwt.SigningMethodRS256.Name,
|
||||
token.Header)
|
||||
}
|
||||
return pubKey, nil
|
||||
})
|
||||
}
|
||||
|
||||
func parseJwtTokenWithoutKey(cookieValue string) (*jwt.Token, error) {
|
||||
return jwt.Parse(cookieValue, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
@@ -20,6 +64,14 @@ func parseJwtToken(cookieValue string) (*jwt.Token, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func parseJwtToken(cookieValue string) (*jwt.Token, error) {
|
||||
if cfg.AuthJwtPubKeyPath != "" { // activate this path only if pub key is specified
|
||||
return parseJwtTokenWithKey(cookieValue)
|
||||
} else {
|
||||
return parseJwtTokenWithoutKey(cookieValue)
|
||||
}
|
||||
}
|
||||
|
||||
func getClaimsFromJwtToken(cookieValue string) (jwt.MapClaims, error) {
|
||||
token, err := parseJwtToken(cookieValue)
|
||||
|
||||
|
||||
135
internal/httpservers/restapi_test.go
Normal file
135
internal/httpservers/restapi_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package httpservers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/OliveTin/OliveTin/internal/cors"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func createKeys() (*rsa.PrivateKey, string) {
|
||||
tmpFile, _ := os.CreateTemp(os.TempDir(), "olivetin-jwt-")
|
||||
defer os.Remove(tmpFile.Name())
|
||||
|
||||
fmt.Println("Created File: " + tmpFile.Name())
|
||||
|
||||
privateKey, _ := rsa.GenerateKey(rand.Reader, 2048)
|
||||
pubKey := &privateKey.PublicKey
|
||||
// https://stackoverflow.com/questions/13555085/save-and-load-crypto-rsa-privatekey-to-and-from-the-disk
|
||||
pkixPubKey, _ := x509.MarshalPKIXPublicKey(pubKey)
|
||||
pubPem := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: pkixPubKey,
|
||||
},
|
||||
)
|
||||
|
||||
if err := os.WriteFile(tmpFile.Name(), pubPem, 0755); err != nil {
|
||||
fmt.Printf("error when dumping pubKey: %s \n", err)
|
||||
}
|
||||
|
||||
return privateKey, tmpFile.Name()
|
||||
}
|
||||
|
||||
func testBase(t *testing.T, expire int64, expectCode int) {
|
||||
privateKey, publicKeyPath := createKeys()
|
||||
|
||||
// default config + overrides
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.AuthJwtPubKeyPath = publicKeyPath
|
||||
cfg.AuthJwtClaimUsername = "sub"
|
||||
cfg.AuthJwtClaimUserGroup = "olivetinGroup"
|
||||
cfg.AuthJwtCookieName = "authorization_token"
|
||||
SetGlobalRestConfig(cfg) // ugly, setting global var, we should pass configs as params to modules... :/
|
||||
|
||||
token := jwt.New(jwt.SigningMethodRS256)
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
claims["nbf"] = time.Now().Unix() - 1000
|
||||
claims["exp"] = time.Now().Unix() + expire
|
||||
claims["sub"] = "test"
|
||||
claims["olivetinGroup"] = "test"
|
||||
|
||||
tokenStr, _ := token.SignedString(privateKey)
|
||||
|
||||
// init mux endpoint like in restapi.go (but using dummy response handler)
|
||||
mux := runtime.NewServeMux(
|
||||
runtime.WithMetadata(parseRequestMetadata), // i am guessing this is critical middleware for authorizing request cookie
|
||||
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
|
||||
Marshaler: &runtime.JSONPb{
|
||||
MarshalOptions: protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitUnpopulated: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
)
|
||||
mux.HandlePath("GET", "/", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
|
||||
username, usergroup := parseJwtCookie(r)
|
||||
if username == "" {
|
||||
w.WriteHeader(403)
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf("username=%v, usergroup=%v", username, usergroup)))
|
||||
})
|
||||
|
||||
// make server and attach handler
|
||||
srv := &http.Server{Handler: cors.AllowCors(mux)}
|
||||
lis, _ := net.Listen("tcp", ":1337")
|
||||
|
||||
/*
|
||||
if err != nil {
|
||||
t.Errorf("Could not listen %v", err)
|
||||
}
|
||||
|
||||
if srv == nil {
|
||||
y.Errorf("srv is nil. Could not listen %v", err)
|
||||
}
|
||||
*/
|
||||
|
||||
go func() {
|
||||
if err := srv.Serve(lis); err != nil {
|
||||
t.Errorf("couldn't start server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// make http client and send request to myself
|
||||
client := &http.Client{}
|
||||
req, _ := http.NewRequest("GET", "http://localhost:1337/", nil)
|
||||
cookie := &http.Cookie{
|
||||
Name: "authorization_token",
|
||||
Value: tokenStr,
|
||||
MaxAge: 300,
|
||||
}
|
||||
req.AddCookie(cookie)
|
||||
res, err := client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
assert.Equal(t, expectCode, -1)
|
||||
} else {
|
||||
defer res.Body.Close()
|
||||
assert.Equal(t, expectCode, res.StatusCode)
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
fmt.Println(string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTSignatureVerificationSucceeds(t *testing.T) {
|
||||
// testBase(t, 1000, 200)
|
||||
}
|
||||
|
||||
func TestJWTSignatureVerificationFails(t *testing.T) {
|
||||
testBase(t, -500, 403)
|
||||
}
|
||||
@@ -10,6 +10,7 @@ away, and several other issues.
|
||||
|
||||
import (
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/OliveTin/OliveTin/internal/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
@@ -36,8 +37,13 @@ func StartSingleHTTPFrontend(cfg *config.Config) {
|
||||
apiProxy.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debugf("ws req: %q", r.URL)
|
||||
websocket.HandleWebsocket(w, r)
|
||||
})
|
||||
|
||||
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debugf("ui req: %q", r.URL)
|
||||
log.Debugf("ui req: %q", r.URL)
|
||||
webuiProxy.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ type webUISettings struct {
|
||||
func findWebuiDir() string {
|
||||
directoriesToSearch := []string{
|
||||
cfg.WebUIDir,
|
||||
"../webui/",
|
||||
"/usr/share/OliveTin/webui/",
|
||||
"/var/www/OliveTin/",
|
||||
"/var/www/olivetin/",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package httpservers
|
||||
|
||||
import (
|
||||
config "github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
func TestGetWebuiDir(t *testing.T) {
|
||||
os.Chdir("../../") // go test sets the cwd to "httpservers" by default
|
||||
|
||||
cfg = config.DefaultConfig()
|
||||
|
||||
dir := findWebuiDir()
|
||||
|
||||
assert.Equal(t, "./webui", dir, "Finding the webui dir")
|
||||
|
||||
85
internal/onfileindir/fileindir.go
Normal file
85
internal/onfileindir/fileindir.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package onfileindir
|
||||
|
||||
import (
|
||||
"github.com/OliveTin/OliveTin/internal/acl"
|
||||
"github.com/OliveTin/OliveTin/internal/config"
|
||||
"github.com/OliveTin/OliveTin/internal/executor"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func WatchFilesInDirectory(cfg *config.Config, ex *executor.Executor) {
|
||||
for _, action := range cfg.Actions {
|
||||
for _, dirname := range action.ExecOnFileChangedInDir {
|
||||
watch(dirname, action, cfg, ex, fsnotify.Write)
|
||||
}
|
||||
|
||||
for _, dirname := range action.ExecOnFileCreatedInDir {
|
||||
watch(dirname, action, cfg, ex, fsnotify.Create)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func watch(directory string, action config.Action, cfg *config.Config, ex *executor.Executor, eventType fsnotify.Op) {
|
||||
log.WithFields(log.Fields{
|
||||
"dir": directory,
|
||||
"eventType": eventType,
|
||||
}).Infof("Watching dir")
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Could not watch for files being created: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
defer watcher.Close()
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
processEvent(watcher, action, cfg, ex, eventType)
|
||||
}
|
||||
}()
|
||||
|
||||
err = watcher.Add("/tmp")
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Could not create watcher: %v", err)
|
||||
}
|
||||
|
||||
<-done
|
||||
}
|
||||
|
||||
func processEvent(watcher *fsnotify.Watcher, action config.Action, cfg *config.Config, ex *executor.Executor, eventType fsnotify.Op) {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
checkEvent(&event, action, cfg, ex, eventType)
|
||||
case err := <-watcher.Errors:
|
||||
log.Errorf("Error in fsnotify: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkEvent(event *fsnotify.Event, action config.Action, cfg *config.Config, ex *executor.Executor, eventType fsnotify.Op) {
|
||||
if event.Has(eventType) {
|
||||
req := &executor.ExecutionRequest{
|
||||
ActionName: action.Title,
|
||||
Cfg: cfg,
|
||||
Tags: []string{"fileindir"},
|
||||
Arguments: map[string]string{
|
||||
"filename": event.Name,
|
||||
},
|
||||
AuthenticatedUser: &acl.AuthenticatedUser{
|
||||
Username: "fileindir",
|
||||
},
|
||||
}
|
||||
|
||||
ex.ExecRequest(req)
|
||||
}
|
||||
}
|
||||
141
internal/websocket/websocket.go
Normal file
141
internal/websocket/websocket.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package websocket
|
||||
|
||||
import (
|
||||
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||
"github.com/OliveTin/OliveTin/internal/executor"
|
||||
ws "github.com/gorilla/websocket"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var upgrader = ws.Upgrader{
|
||||
CheckOrigin: checkOriginPermissive,
|
||||
}
|
||||
|
||||
type WebsocketClient struct {
|
||||
conn *ws.Conn
|
||||
}
|
||||
|
||||
var clients []*WebsocketClient
|
||||
|
||||
var marshalOptions = protojson.MarshalOptions{
|
||||
UseProtoNames: false, // eg: canExec for js instead of can_exec from protobuf
|
||||
EmitUnpopulated: true,
|
||||
}
|
||||
|
||||
var ExecutionListener WebsocketExecutionListener
|
||||
|
||||
type WebsocketExecutionListener struct{}
|
||||
|
||||
func (WebsocketExecutionListener) OnExecutionStarted(title string) {
|
||||
/*
|
||||
broadcast(ExecutionStarted{
|
||||
Type: "ExecutionStarted",
|
||||
Action: title,
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
The default checkOrigin function checks that the origin (browser) matches the
|
||||
request origin. However in OliveTin we expect many users to deliberately proxy
|
||||
the connection with reverse proxies.
|
||||
|
||||
So, we just permit any origin. After some searching I'm not sure if this exposes
|
||||
OliveTin to security issues, but it seems probably not. It would be possible to
|
||||
create a config option like PermitWebsocketConnectionsFrom or something, but
|
||||
I'd prefer if OliveTin works as much as possible "out of the box".
|
||||
|
||||
If this does expose OliveTin to security issues, it will be changed in the
|
||||
future obviously.
|
||||
*/
|
||||
func checkOriginPermissive(r *http.Request) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (WebsocketExecutionListener) OnExecutionFinished(logEntry *executor.InternalLogEntry) {
|
||||
le := &pb.LogEntry{
|
||||
ActionTitle: logEntry.ActionTitle,
|
||||
ActionIcon: logEntry.ActionIcon,
|
||||
DatetimeStarted: logEntry.DatetimeStarted,
|
||||
DatetimeFinished: logEntry.DatetimeFinished,
|
||||
Stdout: logEntry.Stdout,
|
||||
Stderr: logEntry.Stderr,
|
||||
TimedOut: logEntry.TimedOut,
|
||||
Blocked: logEntry.Blocked,
|
||||
ExitCode: logEntry.ExitCode,
|
||||
Tags: logEntry.Tags,
|
||||
Uuid: logEntry.UUID,
|
||||
ExecutionStarted: logEntry.ExecutionStarted,
|
||||
ExecutionFinished: logEntry.ExecutionFinished,
|
||||
}
|
||||
|
||||
broadcast("ExecutionFinished", le)
|
||||
}
|
||||
|
||||
func broadcast(messageType string, pbmsg *pb.LogEntry) {
|
||||
payload, err := marshalOptions.Marshal(pbmsg)
|
||||
|
||||
// <EVIL>
|
||||
// So, the websocket wants to encode messages using the same protomarshaller
|
||||
// as the REST API - this gives consistency instead of using encoding/json
|
||||
// and allows us to set specific marshalOptions.
|
||||
//
|
||||
// However, the protomarshaller will marshal the type, but the JavaScript at
|
||||
// the other end has no idea what type this object is - as we're just sending
|
||||
// it as JSON over the websocket.
|
||||
//
|
||||
// Therefore, we wrap the nicely marsheled bytes in a hacky JSON string
|
||||
// literal and encode that string just with a byte array cast.
|
||||
hackyMessageEnvelope := "{\"type\": \"" + messageType + "\", \"payload\": "
|
||||
|
||||
hackyMessage := []byte{}
|
||||
hackyMessage = append(hackyMessage, []byte(hackyMessageEnvelope)...)
|
||||
hackyMessage = append(hackyMessage, payload...)
|
||||
hackyMessage = append(hackyMessage, []byte("}")...)
|
||||
// </EVIL>
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("websocket marshal error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, client := range clients {
|
||||
client.conn.WriteMessage(1, hackyMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *WebsocketClient) messageLoop() {
|
||||
for {
|
||||
mt, message, err := c.conn.ReadMessage()
|
||||
|
||||
if err != nil {
|
||||
log.Debugf("err: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
log.Tracef("websocket recv: %s %d", message, mt)
|
||||
}
|
||||
}
|
||||
|
||||
func HandleWebsocket(w http.ResponseWriter, r *http.Request) bool {
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Warnf("Websocket issue: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// defer c.Close()
|
||||
|
||||
wsclient := &WebsocketClient{
|
||||
conn: c,
|
||||
}
|
||||
|
||||
clients = append(clients, wsclient)
|
||||
|
||||
go wsclient.messageLoop()
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -48,7 +48,7 @@ stop()
|
||||
|
||||
status() {
|
||||
PID=$(pidof OliveTin)
|
||||
RETVAL=$?
|
||||
RETVAL=$?
|
||||
|
||||
if [ $RETVAL -eq 1 ] ; then
|
||||
echo "OliveTin is stopped"
|
||||
|
||||
@@ -4,4 +4,4 @@ name=$RC_SVCNAME
|
||||
description="OliveTin"
|
||||
command="/usr/local/bin/OliveTin"
|
||||
pidfile="/run/${RC_SVCNAME}.pid"
|
||||
command_background=true
|
||||
command_background=true
|
||||
|
||||
132
webui/index.html
132
webui/index.html
@@ -16,32 +16,32 @@
|
||||
|
||||
<body>
|
||||
<main title = "main content">
|
||||
<div id = "perma-widget" hidden>
|
||||
<div id = "perma-widget">
|
||||
<div id = "sidebar-toggle-wrapper">
|
||||
<label for = "sidebar-toggler" id = "sidebar-toggler-button" title = "Toggle sidebar" tabindex = "0">☰</label>
|
||||
<label for = "hide-sidebar-checkbox" id = "sidebar-toggler-button" title = "Toggle sidebar" tabindex = "0">☰</label>
|
||||
</div>
|
||||
<h1 id = "page-title">OliveTin</h1>
|
||||
</div>
|
||||
|
||||
|
||||
<div id = "content-sidebar">
|
||||
<input type = "checkbox" id = "sidebar-toggler" hidden checked />
|
||||
<input type = "checkbox" id = "hide-sidebar-checkbox" hidden checked />
|
||||
<aside>
|
||||
<ul>
|
||||
<li>Foo</li>
|
||||
<li>Bar</li>
|
||||
<li><a id = "showActions">Actions</a></li>
|
||||
<li><a id = "showLogs">Logs</a></li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
<fieldset id = "section-switcher" title = "Sections">
|
||||
<button id = "showActions">Actions</button>
|
||||
<button id = "showLogs">Logs</button>
|
||||
</fieldset>
|
||||
|
||||
<section id = "contentLogs" title = "Logs" hidden>
|
||||
<div class = "toolbar">
|
||||
<input placeholder = "Search for action name" id = "logSearchBox" />
|
||||
<button id = "searchLogsClear" title = "Clear search filter" disabled>X</button>
|
||||
</div>
|
||||
<table title = "Logs">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr title = "untitled">
|
||||
<th>Timestamp</th>
|
||||
<th>Log</th>
|
||||
<th>Exit Code</th>
|
||||
@@ -62,47 +62,97 @@
|
||||
</main>
|
||||
|
||||
<footer title = "footer">
|
||||
<p><img title = "application icon" src = "OliveTinLogo.png" height = "1em" class = "logo" /> OliveTin</p>
|
||||
<p>
|
||||
<a href = "https://docs.olivetin.app" target = "_new">Documentation</a> |
|
||||
<a href = "https://github.com/OliveTin/OliveTin/issues/new/choose" target = "_new">Raise an issue on GitHub</a> |
|
||||
<span id = "currentVersion">Version: ?</p>
|
||||
<p><img title = "application icon" src = "OliveTinLogo.png" alt = "OliveTin logo" height = "1em" class = "logo" /> OliveTin</p>
|
||||
<p>
|
||||
<a href = "https://docs.olivetin.app" target = "_new">Documentation</a> |
|
||||
<a href = "https://github.com/OliveTin/OliveTin/issues/new/choose" target = "_new">Raise an issue on GitHub</a> |
|
||||
<span>Version: <span id = "currentVersion">?</span></span> |
|
||||
<span>Server connection:
|
||||
<span id = "serverConnectionRest">REST</span>,
|
||||
<span id = "serverConnectionWebSocket">WebSocket</span>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<a id = "available-version" href = "http://olivetin.app" target = "_blank" hidden>?</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<dialog title = "Execution Results" id = "execution-results-popup">
|
||||
<div class = "action-header">
|
||||
<span class = "icon" role = "icon"></span>
|
||||
|
||||
<h2>Log:
|
||||
<span class = "title">?</span>
|
||||
</h2>
|
||||
</div>
|
||||
<p>
|
||||
<strong>Started: </strong><span class = "datetimeStarted">unknown</span>.
|
||||
<strong>Finished: </strong><span class = "datetimeFinished">unknown</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Exit Code: </strong><span class = "exitCode">unknown</span>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Status: </strong><span class = "status">unknown</span>
|
||||
</p>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>stdout</summary>
|
||||
<pre class = "stdout">
|
||||
?
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>stderr</summary>
|
||||
<pre class = "stderr">
|
||||
?
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<form method = "dialog">
|
||||
<button name = "cancel">Close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<template id = "tplArgumentForm">
|
||||
<form class = "action-arguments">
|
||||
<div class = "wrapper">
|
||||
<div>
|
||||
<span class = "icon" role = "icon"></span>
|
||||
<h2>Argument form</h2>
|
||||
</div>
|
||||
<dialog title = "Arguments" id = "argument-popup">
|
||||
<form class = "action-arguments">
|
||||
<div class = "wrapper">
|
||||
<div class = "action-header">
|
||||
<span class = "icon" role = "icon"></span>
|
||||
<h2>Argument form</h2>
|
||||
</div>
|
||||
|
||||
<div class = "arguments"></div>
|
||||
<div class = "arguments"></div>
|
||||
|
||||
<div class = "buttons">
|
||||
<input name = "start" type = "submit" value = "Start">
|
||||
<button name = "cancel">Cancel</button>
|
||||
<div class = "buttons">
|
||||
<input name = "start" type = "submit" value = "Start">
|
||||
<button name = "cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form>
|
||||
</form>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<template id = "tplActionButton">
|
||||
<button>
|
||||
<span role = "icon" title = "button icon" class = "icon">💩</span>
|
||||
<p role = "title" class = "title">Untitled Button</p>
|
||||
<span role = "icon" title = "action button icon" class = "icon">💩</span>
|
||||
<span role = "title" class = "title">Untitled Button</span>
|
||||
</button>
|
||||
|
||||
<div class = "action-button-footer">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id = "tplLogRow">
|
||||
<tr class = "log-row">
|
||||
<td class = "timestamp">?</td>
|
||||
<td class = "timestamp">?</td>
|
||||
<td>
|
||||
<span class = "icon" role = "icon"></span>
|
||||
<span class = "content">?</span>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>stdout</summary>
|
||||
<pre class = "stdout">
|
||||
@@ -124,20 +174,34 @@
|
||||
</template>
|
||||
|
||||
<script type = "text/javascript">
|
||||
/**
|
||||
/**
|
||||
This is the bootstrap code, which relies on very simple, old javascript
|
||||
to at least display a helpful error message if we can't use OliveTin.
|
||||
*/
|
||||
function showBigError (type, friendlyType, message) {
|
||||
clearInterval(window.buttonInterval)
|
||||
clearBigErrors(type)
|
||||
|
||||
console.error('Error ' + type + ': ', message)
|
||||
|
||||
const domErr = document.createElement('div')
|
||||
domErr.classList.add('error')
|
||||
domErr.classList.add(type)
|
||||
domErr.innerHTML = '<h1>Error ' + friendlyType + '</h1><p>' + message + "</p><p><a href = 'http://docs.olivetin.app/err-" + type + ".html' target = 'blank'/>" + type + " error in OliveTin Documentation</a></p>"
|
||||
|
||||
document.getElementById('root-group').appendChild(domErr)
|
||||
document.body.prepend(domErr)
|
||||
}
|
||||
|
||||
function clearBigErrors(additionalClass) {
|
||||
let selector = 'div.error'
|
||||
|
||||
if (additionalClass != null) {
|
||||
selector += '.' + additionalClass
|
||||
}
|
||||
|
||||
for (const oldError of document.querySelectorAll(selector).values()) {
|
||||
window.old = oldError;
|
||||
oldError.remove();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { marshalLogsJsonToHtml } from './marshaller.js'
|
||||
import './ArgumentForm.js'
|
||||
import './ExecutionButton.js'
|
||||
|
||||
class ActionButton extends window.HTMLElement {
|
||||
constructFromJson (json) {
|
||||
@@ -18,14 +18,18 @@ class ActionButton extends window.HTMLElement {
|
||||
this.setAttribute('role', 'none')
|
||||
this.btn.title = json.title
|
||||
this.btn.onclick = () => {
|
||||
console.log(json.arguments)
|
||||
if (json.arguments.length > 0) {
|
||||
for (const oldArgumentForm of document.querySelectorAll('argument-form')) {
|
||||
oldArgumentForm.remove()
|
||||
}
|
||||
|
||||
const frm = document.createElement('argument-form')
|
||||
frm.setup(json, (args) => {
|
||||
this.startAction(args)
|
||||
})
|
||||
|
||||
document.body.appendChild(frm)
|
||||
frm.querySelector('dialog').showModal()
|
||||
} else {
|
||||
this.startAction()
|
||||
}
|
||||
@@ -33,7 +37,8 @@ class ActionButton extends window.HTMLElement {
|
||||
|
||||
this.updateFromJson(json)
|
||||
|
||||
this.updateDom()
|
||||
this.domTitle.innerText = this.btn.title
|
||||
this.domIcon.innerHTML = this.unicodeIcon
|
||||
|
||||
this.setAttribute('id', 'actionButton_' + json.id)
|
||||
}
|
||||
@@ -52,21 +57,36 @@ class ActionButton extends window.HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
getUniqueId () {
|
||||
if (window.isSecureContext) {
|
||||
return window.crypto.randomUUID()
|
||||
} else {
|
||||
return Date.now().toString()
|
||||
}
|
||||
}
|
||||
|
||||
startAction (actionArgs) {
|
||||
this.btn.disabled = true
|
||||
this.isWaiting = true
|
||||
this.updateDom()
|
||||
// this.btn.disabled = true
|
||||
// this.isWaiting = true
|
||||
// this.updateDom()
|
||||
this.btn.classList = [] // Removes old animation classes
|
||||
|
||||
if (actionArgs === undefined) {
|
||||
actionArgs = []
|
||||
}
|
||||
|
||||
// UUIDs are create client side, so that we can setup a "execution-button"
|
||||
// to track the execution before we send the request to the server.
|
||||
const startActionArgs = {
|
||||
actionName: this.btn.title,
|
||||
arguments: actionArgs
|
||||
arguments: actionArgs,
|
||||
uuid: this.getUniqueId()
|
||||
}
|
||||
|
||||
const btnExecution = document.createElement('execution-button')
|
||||
btnExecution.constructFromJson(startActionArgs.uuid)
|
||||
this.querySelector('.action-button-footer').prepend(btnExecution)
|
||||
|
||||
window.fetch(this.actionCallUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -81,45 +101,12 @@ class ActionButton extends window.HTMLElement {
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
marshalLogsJsonToHtml({ logs: [json.logEntry] })
|
||||
|
||||
if (json.logEntry.timedOut) {
|
||||
this.onActionResult('action-timeout', 'Timed out')
|
||||
} else if (json.logEntry.exitCode === -1337) {
|
||||
this.onActionError('Error')
|
||||
} else if (json.logEntry.exitCode !== 0) {
|
||||
this.onActionResult('action-nonzero-exit', 'Exit code ' + json.logEntry.exitCode)
|
||||
} else {
|
||||
this.onActionResult('action-success', 'Success!')
|
||||
}
|
||||
// The button used to wait for the action to finish, but now it is fire & forget
|
||||
}).catch(err => {
|
||||
this.onActionError(err)
|
||||
btnExecution.onActionError(err)
|
||||
})
|
||||
}
|
||||
|
||||
onActionResult (cssClass, temporaryStatusMessage) {
|
||||
this.btn.disabled = false
|
||||
this.temporaryStatusMessage = '[ ' + temporaryStatusMessage + ' ]'
|
||||
this.updateDom()
|
||||
this.btn.classList.add(cssClass)
|
||||
|
||||
setTimeout(() => {
|
||||
this.btn.classList.remove(cssClass)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
onActionError (err) {
|
||||
console.error('callback error', err)
|
||||
this.btn.disabled = false
|
||||
this.isWaiting = false
|
||||
this.updateDom()
|
||||
this.btn.classList.add('action-failed')
|
||||
|
||||
setTimeout(() => {
|
||||
this.btn.classList.remove('action-failed')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
constructDomFromTemplate () {
|
||||
const tpl = document.getElementById('tplActionButton')
|
||||
const content = tpl.content.cloneNode(true)
|
||||
@@ -135,27 +122,6 @@ class ActionButton extends window.HTMLElement {
|
||||
this.domTitle = this.btn.querySelector('.title')
|
||||
this.domIcon = this.btn.querySelector('.icon')
|
||||
}
|
||||
|
||||
updateDom () {
|
||||
if (this.temporaryStatusMessage != null) {
|
||||
this.domTitle.innerText = this.temporaryStatusMessage
|
||||
this.domTitle.classList.add('temporary-status-message')
|
||||
this.isWaiting = false
|
||||
this.disabled = false
|
||||
|
||||
setTimeout(() => {
|
||||
this.temporaryStatusMessage = null
|
||||
this.domTitle.classList.remove('temporary-status-message')
|
||||
this.updateDom()
|
||||
}, 2000)
|
||||
} else if (this.isWaiting) {
|
||||
this.domTitle.innerText = 'Waiting...'
|
||||
} else {
|
||||
this.domTitle.innerText = this.btn.title
|
||||
}
|
||||
|
||||
this.domIcon.innerHTML = this.unicodeIcon
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('action-button', ActionButton)
|
||||
|
||||
@@ -113,7 +113,6 @@ class ArgumentForm extends window.HTMLElement {
|
||||
throw new Error(res.statusText)
|
||||
}
|
||||
}).then((json) => {
|
||||
console.log(json.valid)
|
||||
if (json.valid) {
|
||||
domEl.setCustomValidity('')
|
||||
} else {
|
||||
|
||||
99
webui/js/ExecutionButton.js
Normal file
99
webui/js/ExecutionButton.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { ExecutionDialog } from './ExecutionDialog.js'
|
||||
|
||||
class ExecutionButton extends window.HTMLElement {
|
||||
constructFromJson (json) {
|
||||
this.executionUuid = json
|
||||
this.ellapsed = 0
|
||||
|
||||
this.appendChild(document.createElement('button'))
|
||||
this.isWaiting = true
|
||||
|
||||
this.setAttribute('id', 'execution-' + json)
|
||||
|
||||
this.btn = this.querySelector('button')
|
||||
this.btn.innerText = 'Executing...'
|
||||
this.btn.onclick = () => {
|
||||
this.show()
|
||||
}
|
||||
}
|
||||
|
||||
show () {
|
||||
if (window.executionDialog === undefined) {
|
||||
window.executionDialog = new ExecutionDialog()
|
||||
}
|
||||
|
||||
const executionStatusArgs = {
|
||||
executionUuid: this.executionUuid
|
||||
}
|
||||
|
||||
window.executionDialog.constructFromJson(this.executionUuid)
|
||||
window.executionDialog.show()
|
||||
|
||||
window.fetch(window.restBaseUrl + 'ExecutionStatus', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(executionStatusArgs)
|
||||
}).then((res) => {
|
||||
if (res.ok) {
|
||||
return res.json()
|
||||
} else {
|
||||
throw new Error(res.statusText)
|
||||
}
|
||||
}
|
||||
).then((json) => {
|
||||
window.executionDialog.renderResult(json)
|
||||
}).catch(err => {
|
||||
window.executionDialog.renderError(err)
|
||||
})
|
||||
}
|
||||
|
||||
onFinished (LogEntry) {
|
||||
if (LogEntry.timedOut) {
|
||||
this.onActionResult('action-timeout', 'Timed out')
|
||||
} else if (LogEntry.blocked) {
|
||||
this.onActionResult('action-blocked', 'Blocked!')
|
||||
} else if (LogEntry.exitCode !== 0) {
|
||||
this.onActionResult('action-nonzero-exit', 'Exit code ' + LogEntry.exitCode)
|
||||
} else {
|
||||
console.log(LogEntry)
|
||||
this.ellapsed = Math.ceil(new Date(LogEntry.datetimeFinished) - new Date(LogEntry.datetimeStarted)) / 1000
|
||||
this.onActionResult('action-success', 'Success!')
|
||||
}
|
||||
}
|
||||
|
||||
onActionResult (cssClass, temporaryStatusMessage) {
|
||||
this.temporaryStatusMessage = '[' + temporaryStatusMessage + ']'
|
||||
this.updateDom()
|
||||
this.btn.classList.add(cssClass)
|
||||
}
|
||||
|
||||
onActionError (err) {
|
||||
console.error('callback error', err)
|
||||
this.isWaiting = false
|
||||
this.updateDom()
|
||||
this.btn.classList.add('action-failed')
|
||||
}
|
||||
|
||||
updateDom () {
|
||||
if (this.temporaryStatusMessage != null) {
|
||||
this.btn.innerText = this.temporaryStatusMessage
|
||||
this.btn.classList.add('temporary-status-message')
|
||||
this.isWaiting = false
|
||||
|
||||
setTimeout(() => {
|
||||
this.temporaryStatusMessage = null
|
||||
this.btn.classList.remove('temporary-status-message')
|
||||
this.updateDom()
|
||||
}, 2000)
|
||||
} else if (this.isWaiting) {
|
||||
this.btn.innerText = 'Waiting...'
|
||||
} else {
|
||||
this.btn.innerText = this.ellapsed + 's'
|
||||
this.btn.title = this.ellapsed + ' seconds'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('execution-button', ExecutionButton)
|
||||
66
webui/js/ExecutionDialog.js
Normal file
66
webui/js/ExecutionDialog.js
Normal file
@@ -0,0 +1,66 @@
|
||||
// This ExecutionDialog is NOT a custom HTML element, but rather just picks up
|
||||
// the <dialog /> element out of index.html and just re-uses that - as only
|
||||
// one dialog can be shown at a time.
|
||||
export class ExecutionDialog {
|
||||
constructFromJson (json) {
|
||||
this.executionUuid = json
|
||||
|
||||
this.dlg = document.querySelector('dialog#execution-results-popup')
|
||||
|
||||
this.domIcon = this.dlg.querySelector('.icon')
|
||||
this.domTitle = this.dlg.querySelector('.title')
|
||||
this.domStdout = this.dlg.querySelector('.stdout')
|
||||
this.domStderr = this.dlg.querySelector('.stderr')
|
||||
this.domDatetimeStarted = this.dlg.querySelector('.datetimeStarted')
|
||||
this.domDatetimeFinished = this.dlg.querySelector('.datetimeFinished')
|
||||
this.domExitCode = this.dlg.querySelector('.exitCode')
|
||||
this.domStatus = this.dlg.querySelector('.status')
|
||||
}
|
||||
|
||||
show () {
|
||||
this.dlg.showModal()
|
||||
}
|
||||
|
||||
renderResult (res) {
|
||||
this.executionUuid = res.logEntry.executionUuid
|
||||
|
||||
if (res.logEntry.executionFinished) {
|
||||
this.domStatus.innerText = 'Completed'
|
||||
this.domDatetimeFinished.innerText = res.logEntry.datetimeFinished
|
||||
|
||||
if (res.logEntry.blocked) {
|
||||
this.domStatus.innerText = 'Blocked'
|
||||
}
|
||||
|
||||
if (res.logEntry.timedOut) {
|
||||
this.domExitCode.innerText = 'Timed out'
|
||||
this.domStatus.innerText = 'Timed out'
|
||||
} else {
|
||||
this.domExitCode.innerText = res.logEntry.exitCode
|
||||
}
|
||||
} else {
|
||||
this.domDatetimeFinished.innerText = 'Still running...'
|
||||
this.domExitCode.innerText = 'Still running...'
|
||||
this.domStatus.innerText = 'Still running...'
|
||||
}
|
||||
|
||||
this.domIcon.innerHTML = res.logEntry.actionIcon
|
||||
this.domTitle.innerText = res.logEntry.actionTitle
|
||||
|
||||
this.domStdout.innerText = res.logEntry.stdout
|
||||
|
||||
if (res.logEntry.stderr === '') {
|
||||
this.domStderr.parentElement.hidden = true
|
||||
this.domStderr.innerText = res.logEntry.stderr
|
||||
} else {
|
||||
this.domStderr.parentElement.hidden = false
|
||||
this.domStderr.innerText = res.logEntry.stderr
|
||||
}
|
||||
|
||||
this.domDatetimeStarted.innerText = res.logEntry.datetimeStarted
|
||||
}
|
||||
|
||||
renderError (err) {
|
||||
this.dlg.querySelector('pre').innerText = JSON.stringify(err)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export function marshalActionButtonsJsonToHtml (json) {
|
||||
const currentIterationTimestamp = Date.now()
|
||||
|
||||
for (const jsonButton of json.actions) {
|
||||
let htmlButton = document.querySelector('#actionButton_' + jsonButton.id)
|
||||
let htmlButton = document.querySelector('#execution-' + jsonButton.id)
|
||||
|
||||
if (htmlButton == null) {
|
||||
htmlButton = document.createElement('action-button')
|
||||
@@ -30,7 +30,7 @@ export function marshalActionButtonsJsonToHtml (json) {
|
||||
export function marshalLogsJsonToHtml (json) {
|
||||
for (const logEntry of json.logs) {
|
||||
const tpl = document.getElementById('tplLogRow')
|
||||
const row = tpl.content.cloneNode(true)
|
||||
const row = tpl.content.querySelector('tr').cloneNode(true)
|
||||
|
||||
if (logEntry.stdout.length === 0) {
|
||||
logEntry.stdout = '(empty)'
|
||||
@@ -50,12 +50,13 @@ export function marshalLogsJsonToHtml (json) {
|
||||
logTableExitCode += ' (timed out)'
|
||||
}
|
||||
|
||||
row.querySelector('.timestamp').innerText = logEntry.datetime
|
||||
row.querySelector('.timestamp').innerText = logEntry.datetimeStarted
|
||||
row.querySelector('.content').innerText = logEntry.actionTitle
|
||||
row.querySelector('.icon').innerHTML = logEntry.actionIcon
|
||||
row.querySelector('pre.stdout').innerText = logEntry.stdout
|
||||
row.querySelector('pre.stderr').innerText = logEntry.stderr
|
||||
row.querySelector('.exit-code').innerText = logTableExitCode
|
||||
row.setAttribute('title', logEntry.actionTitle)
|
||||
|
||||
for (const tag of logEntry.tags) {
|
||||
const domTag = document.createElement('span')
|
||||
|
||||
73
webui/js/websocket.js
Normal file
73
webui/js/websocket.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { marshalLogsJsonToHtml } from './marshaller.js'
|
||||
|
||||
window.ws = null
|
||||
|
||||
export function checkWebsocketConnection () {
|
||||
if (window.ws === null || window.ws.readyState === 3) {
|
||||
reconnectWebsocket()
|
||||
}
|
||||
}
|
||||
|
||||
function reconnectWebsocket () {
|
||||
window.websocketAvailable = false
|
||||
|
||||
let proto = 'ws:'
|
||||
|
||||
if (window.location.protocol === 'https:') {
|
||||
proto = 'wss:'
|
||||
}
|
||||
|
||||
const websocketConnectionUrl = proto + window.location.host + '/websocket'
|
||||
const ws = window.ws = new WebSocket(websocketConnectionUrl)
|
||||
|
||||
ws.addEventListener('open', websocketOnOpen)
|
||||
ws.addEventListener('message', websocketOnMessage)
|
||||
ws.addEventListener('error', websocketOnError)
|
||||
ws.addEventListener('close', websocketOnClose)
|
||||
}
|
||||
|
||||
function websocketOnOpen (evt) {
|
||||
window.websocketAvailable = true
|
||||
|
||||
window.ws.send('monitor')
|
||||
|
||||
window.refreshLoop()
|
||||
}
|
||||
|
||||
function websocketOnMessage (msg) {
|
||||
// FIXME check msg status is OK
|
||||
const j = JSON.parse(msg.data)
|
||||
|
||||
switch (j.type) {
|
||||
case 'ExecutionFinished':
|
||||
updatePageAfterFinished(j.payload)
|
||||
break
|
||||
default:
|
||||
window.showBigError('Unknown message type from server: ' + j.type)
|
||||
}
|
||||
}
|
||||
|
||||
function updatePageAfterFinished (logEntry) {
|
||||
document.querySelector('execution-button#execution-' + logEntry.uuid).onFinished(logEntry)
|
||||
|
||||
marshalLogsJsonToHtml({
|
||||
logs: [logEntry]
|
||||
})
|
||||
|
||||
// If the current execution dialog is open, update that too
|
||||
if (window.executionDialog != null && window.executionDialog.dlg.open && window.executionDialog.executionUuid === logEntry.uuid) {
|
||||
window.executionDialog.renderResult({
|
||||
logEntry: logEntry
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function websocketOnError (err) {
|
||||
window.websocketAvailable = false
|
||||
window.refreshLoop()
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
function websocketOnClose () {
|
||||
window.websocketAvailable = false
|
||||
}
|
||||
@@ -1,6 +1,28 @@
|
||||
'use strict'
|
||||
|
||||
import { marshalActionButtonsJsonToHtml, marshalLogsJsonToHtml } from './js/marshaller.js'
|
||||
import { checkWebsocketConnection } from './js/websocket.js'
|
||||
|
||||
function searchLogs (e) {
|
||||
document.getElementById('searchLogsClear').disabled = false
|
||||
|
||||
const searchText = e.target.value.toLowerCase()
|
||||
|
||||
for (const row of document.querySelectorAll('tr.log-row')) {
|
||||
const actionTitle = row.getAttribute('title').toLowerCase()
|
||||
|
||||
row.hidden = !actionTitle.includes(searchText)
|
||||
}
|
||||
}
|
||||
|
||||
function searchLogsClear () {
|
||||
for (const row of document.querySelectorAll('tr.log-row')) {
|
||||
row.hidden = false
|
||||
}
|
||||
|
||||
document.getElementById('searchLogsClear').disabled = true
|
||||
document.getElementById('logSearchBox').value = ''
|
||||
}
|
||||
|
||||
function showSection (name) {
|
||||
for (const otherName of ['Actions', 'Logs']) {
|
||||
@@ -10,6 +32,8 @@ function showSection (name) {
|
||||
|
||||
document.getElementById('show' + name).classList.add('activeSection')
|
||||
document.getElementById('content' + name).hidden = false
|
||||
|
||||
document.getElementById('hide-sidebar-checkbox').checked = true
|
||||
}
|
||||
|
||||
function setupSections () {
|
||||
@@ -19,14 +43,59 @@ function setupSections () {
|
||||
showSection('Actions')
|
||||
}
|
||||
|
||||
function setupLogSearchBox () {
|
||||
document.getElementById('logSearchBox').oninput = searchLogs
|
||||
document.getElementById('searchLogsClear').onclick = searchLogsClear
|
||||
}
|
||||
|
||||
function refreshLoop () {
|
||||
if (window.websocketAvailable) {
|
||||
// Websocket updates are streamed live, not updated on a loop.
|
||||
} else if (window.restAvailable) {
|
||||
// Fallback to rest, but try to reconnect the websocket anyway.
|
||||
|
||||
fetchGetDashboardComponents()
|
||||
fetchGetLogs()
|
||||
|
||||
checkWebsocketConnection()
|
||||
} else {
|
||||
// Still try to fetch the dashboard, if successfull window.restAvailable = true
|
||||
fetchGetDashboardComponents()
|
||||
}
|
||||
|
||||
refreshServerConnectionLabel()
|
||||
}
|
||||
|
||||
function refreshServerConnectionLabel () {
|
||||
if (window.restAvailable) {
|
||||
document.querySelector('#serverConnectionRest').classList.remove('error')
|
||||
} else {
|
||||
document.querySelector('#serverConnectionRest').classList.add('error')
|
||||
}
|
||||
|
||||
if (window.websocketAvailable) {
|
||||
document.querySelector('#serverConnectionWebSocket').classList.remove('error')
|
||||
} else {
|
||||
document.querySelector('#serverConnectionWebSocket').classList.add('error')
|
||||
}
|
||||
}
|
||||
|
||||
function fetchGetDashboardComponents () {
|
||||
window.fetch(window.restBaseUrl + 'GetDashboardComponents', {
|
||||
cors: 'cors'
|
||||
}).then(res => {
|
||||
return res.json()
|
||||
}).then(res => {
|
||||
if (!window.restAvailable) {
|
||||
window.clearBigErrors('fetch-buttons')
|
||||
}
|
||||
|
||||
window.restAvailable = true
|
||||
marshalActionButtonsJsonToHtml(res)
|
||||
}).catch(err => {
|
||||
|
||||
refreshServerConnectionLabel() // in-case it changed, update the label quicker
|
||||
}).catch((err) => { // err is 1st arg
|
||||
window.restAvailable = false
|
||||
window.showBigError('fetch-buttons', 'getting buttons', err, 'blat')
|
||||
})
|
||||
}
|
||||
@@ -55,14 +124,14 @@ function processWebuiSettingsJson (settings) {
|
||||
document.head.appendChild(themeCss)
|
||||
}
|
||||
|
||||
document.querySelector('#currentVersion').innerText = 'Version: ' + settings.CurrentVersion
|
||||
document.querySelector('#currentVersion').innerText = settings.CurrentVersion
|
||||
|
||||
if (settings.ShowNewVersions && settings.AvailableVersion !== 'none') {
|
||||
document.querySelector('#available-version').innerText = 'New Version Available: ' + settings.AvailableVersion
|
||||
document.querySelector('#available-version').hidden = false
|
||||
}
|
||||
|
||||
document.querySelector('#section-switcher').hidden = !settings.ShowNavigation
|
||||
document.querySelector('#perma-widget').hidden = !settings.ShowNavigation
|
||||
document.querySelector('footer[title="footer"]').hidden = !settings.ShowFooter
|
||||
|
||||
if (settings.PageTitle) {
|
||||
@@ -74,16 +143,18 @@ function processWebuiSettingsJson (settings) {
|
||||
|
||||
function main () {
|
||||
setupSections()
|
||||
setupLogSearchBox()
|
||||
|
||||
window.fetch('webUiSettings.json').then(res => {
|
||||
return res.json()
|
||||
}).then(res => {
|
||||
processWebuiSettingsJson(res)
|
||||
|
||||
fetchGetDashboardComponents()
|
||||
fetchGetLogs()
|
||||
window.restAvailable = true
|
||||
window.refreshLoop = refreshLoop
|
||||
window.refreshLoop()
|
||||
|
||||
window.buttonInterval = setInterval(fetchGetDashboardComponents, 3000)
|
||||
setInterval(refreshLoop, 3000)
|
||||
}).catch(err => {
|
||||
window.showBigError('fetch-webui-settings', 'getting webui settings', err)
|
||||
})
|
||||
|
||||
479
webui/package-lock.json
generated
479
webui/package-lock.json
generated
@@ -14,8 +14,8 @@
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"stylelint": "^14.5.3",
|
||||
"stylelint-config-standard": "^25.0.0"
|
||||
"stylelint": "^15.6.0",
|
||||
"stylelint-config-standard": "^33.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@@ -121,20 +121,65 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/selector-specificity": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz",
|
||||
"integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==",
|
||||
"node_modules/@csstools/css-parser-algorithms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz",
|
||||
"integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12 || ^14 || >=16"
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-tokenizer": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/css-tokenizer": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz",
|
||||
"integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/media-query-list-parser": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@csstools/css-parser-algorithms": "^2.1.1",
|
||||
"@csstools/css-tokenizer": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/selector-specificity": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz",
|
||||
"integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14 || ^16 || >=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/csstools"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"postcss": "^8.2",
|
||||
"postcss-selector-parser": "^6.0.10"
|
||||
}
|
||||
},
|
||||
@@ -231,12 +276,6 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
@@ -503,19 +542,39 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
|
||||
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
|
||||
"integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cosmiconfig/node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
@@ -541,6 +600,19 @@
|
||||
"node": ">=12.22"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.30",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -981,9 +1053,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-node/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -1534,9 +1606,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-tags": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
|
||||
"integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
|
||||
"integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -1925,9 +1997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/known-css-properties": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
|
||||
"integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==",
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz",
|
||||
"integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/levn": {
|
||||
@@ -2007,6 +2079,12 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
|
||||
@@ -2118,10 +2196,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -2365,9 +2449,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -2377,10 +2461,14 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
@@ -2417,9 +2505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
|
||||
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
@@ -2551,9 +2639,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/read-pkg/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
@@ -2708,9 +2796,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -2930,16 +3018,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stylelint": {
|
||||
"version": "14.14.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.0.tgz",
|
||||
"integrity": "sha512-yUI+4xXfPHVnueYddSQ/e1GuEA/2wVhWQbGj16AmWLtQJtn28lVxfS4b0CsWyVRPgd3Auzi0NXOthIEUhtQmmA==",
|
||||
"version": "15.6.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.6.0.tgz",
|
||||
"integrity": "sha512-Cqzpc8tvJm77KaM8qUbhpJ/UYK55Ia0whQXj4b9IId9dlPICO7J8Lyo15SZWiHxKjlvy3p5FQor/3n6i8ignXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@csstools/selector-specificity": "^2.0.2",
|
||||
"@csstools/css-parser-algorithms": "^2.1.1",
|
||||
"@csstools/css-tokenizer": "^2.1.1",
|
||||
"@csstools/media-query-list-parser": "^2.0.4",
|
||||
"@csstools/selector-specificity": "^2.2.0",
|
||||
"balanced-match": "^2.0.0",
|
||||
"colord": "^2.9.3",
|
||||
"cosmiconfig": "^7.0.1",
|
||||
"cosmiconfig": "^8.1.3",
|
||||
"css-functions-list": "^3.1.0",
|
||||
"css-tree": "^2.3.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
@@ -2947,38 +3039,38 @@
|
||||
"global-modules": "^2.0.0",
|
||||
"globby": "^11.1.0",
|
||||
"globjoin": "^0.1.4",
|
||||
"html-tags": "^3.2.0",
|
||||
"ignore": "^5.2.0",
|
||||
"html-tags": "^3.3.1",
|
||||
"ignore": "^5.2.4",
|
||||
"import-lazy": "^4.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"known-css-properties": "^0.25.0",
|
||||
"known-css-properties": "^0.27.0",
|
||||
"mathml-tag-names": "^2.1.3",
|
||||
"meow": "^9.0.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.17",
|
||||
"postcss": "^8.4.22",
|
||||
"postcss-media-query-parser": "^0.2.3",
|
||||
"postcss-resolve-nested-selector": "^0.1.1",
|
||||
"postcss-safe-parser": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.0.10",
|
||||
"postcss-selector-parser": "^6.0.11",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"style-search": "^0.1.0",
|
||||
"supports-hyperlinks": "^2.3.0",
|
||||
"supports-hyperlinks": "^3.0.0",
|
||||
"svg-tags": "^1.0.0",
|
||||
"table": "^6.8.0",
|
||||
"table": "^6.8.1",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"write-file-atomic": "^4.0.2"
|
||||
"write-file-atomic": "^5.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"stylelint": "bin/stylelint.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
"node": "^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -2986,24 +3078,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-recommended": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz",
|
||||
"integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==",
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-12.0.0.tgz",
|
||||
"integrity": "sha512-x6x8QNARrGO2sG6iURkzqL+Dp+4bJorPMMRNPScdvaUK8PsynriOcMW7AFDKqkWAS5wbue/u8fUT/4ynzcmqdQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"stylelint": "^14.4.0"
|
||||
"stylelint": "^15.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint-config-standard": {
|
||||
"version": "25.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz",
|
||||
"integrity": "sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==",
|
||||
"version": "33.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-33.0.0.tgz",
|
||||
"integrity": "sha512-eyxnLWoXImUn77+ODIuW9qXBDNM+ALN68L3wT1lN2oNspZ7D9NVGlNHb2QCUn4xDug6VZLsh0tF8NyoYzkgTzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"stylelint-config-recommended": "^7.0.0"
|
||||
"stylelint-config-recommended": "^12.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"stylelint": "^14.4.0"
|
||||
"stylelint": "^15.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stylelint/node_modules/balanced-match": {
|
||||
@@ -3013,9 +3105,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stylelint/node_modules/ignore": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
||||
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
@@ -3043,16 +3135,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/supports-hyperlinks": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
|
||||
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz",
|
||||
"integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0",
|
||||
"supports-color": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
"node": ">=14.18"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
@@ -3074,9 +3166,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/table": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz",
|
||||
"integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
|
||||
"integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.1",
|
||||
@@ -3252,9 +3344,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@@ -3267,16 +3359,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/write-file-atomic": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
|
||||
"integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.0.tgz",
|
||||
"integrity": "sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"imurmurhash": "^0.1.4",
|
||||
"signal-exit": "^3.0.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
@@ -3285,15 +3377,6 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
@@ -3389,10 +3472,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@csstools/css-parser-algorithms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.1.1.tgz",
|
||||
"integrity": "sha512-viRnRh02AgO4mwIQb2xQNJju0i+Fh9roNgmbR5xEuG7J3TGgxjnE95HnBLgsFJOJOksvcfxOUCgODcft6Y07cA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@csstools/css-tokenizer": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-2.1.1.tgz",
|
||||
"integrity": "sha512-GbrTj2Z8MCTUv+52GE0RbFGM527xuXZ0Xa5g0Z+YN573uveS4G0qi6WNOMyz3yrFM/jaILTTwJ0+umx81EzqfA==",
|
||||
"dev": true
|
||||
},
|
||||
"@csstools/media-query-list-parser": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-GyYot6jHgcSDZZ+tLSnrzkR7aJhF2ZW6d+CXH66mjy5WpAQhZD4HDke2OQ36SivGRWlZJpAz7TzbW6OKlEpxAA==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@csstools/selector-specificity": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz",
|
||||
"integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz",
|
||||
"integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
@@ -3474,12 +3577,6 @@
|
||||
"integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
@@ -3674,16 +3771,32 @@
|
||||
"dev": true
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz",
|
||||
"integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==",
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
|
||||
"integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
"path-type": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
@@ -3703,6 +3816,16 @@
|
||||
"integrity": "sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==",
|
||||
"dev": true
|
||||
},
|
||||
"css-tree": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mdn-data": "2.0.30",
|
||||
"source-map-js": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
@@ -4032,9 +4155,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -4448,9 +4571,9 @@
|
||||
}
|
||||
},
|
||||
"html-tags": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.2.0.tgz",
|
||||
"integrity": "sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==",
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
|
||||
"integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
@@ -4725,9 +4848,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"known-css-properties": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.25.0.tgz",
|
||||
"integrity": "sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==",
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.27.0.tgz",
|
||||
"integrity": "sha512-uMCj6+hZYDoffuvAJjFAPz56E9uoowFHmTkqRtRq5WyC5Q6Cu/fTZKNQpX/RbzChBYLLl3lo8CjFZBAZXq9qFg==",
|
||||
"dev": true
|
||||
},
|
||||
"levn": {
|
||||
@@ -4788,6 +4911,12 @@
|
||||
"integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==",
|
||||
"dev": true
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
|
||||
"dev": true
|
||||
},
|
||||
"meow": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
|
||||
@@ -4871,9 +5000,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true
|
||||
},
|
||||
"natural-compare": {
|
||||
@@ -5046,12 +5175,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
@@ -5076,9 +5205,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-selector-parser": {
|
||||
"version": "6.0.10",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
|
||||
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
|
||||
"version": "6.0.11",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz",
|
||||
"integrity": "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cssesc": "^3.0.0",
|
||||
@@ -5152,9 +5281,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true
|
||||
},
|
||||
"type-fest": {
|
||||
@@ -5270,9 +5399,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
@@ -5441,16 +5570,20 @@
|
||||
"dev": true
|
||||
},
|
||||
"stylelint": {
|
||||
"version": "14.14.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.0.tgz",
|
||||
"integrity": "sha512-yUI+4xXfPHVnueYddSQ/e1GuEA/2wVhWQbGj16AmWLtQJtn28lVxfS4b0CsWyVRPgd3Auzi0NXOthIEUhtQmmA==",
|
||||
"version": "15.6.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.6.0.tgz",
|
||||
"integrity": "sha512-Cqzpc8tvJm77KaM8qUbhpJ/UYK55Ia0whQXj4b9IId9dlPICO7J8Lyo15SZWiHxKjlvy3p5FQor/3n6i8ignXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@csstools/selector-specificity": "^2.0.2",
|
||||
"@csstools/css-parser-algorithms": "^2.1.1",
|
||||
"@csstools/css-tokenizer": "^2.1.1",
|
||||
"@csstools/media-query-list-parser": "^2.0.4",
|
||||
"@csstools/selector-specificity": "^2.2.0",
|
||||
"balanced-match": "^2.0.0",
|
||||
"colord": "^2.9.3",
|
||||
"cosmiconfig": "^7.0.1",
|
||||
"cosmiconfig": "^8.1.3",
|
||||
"css-functions-list": "^3.1.0",
|
||||
"css-tree": "^2.3.1",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.2.12",
|
||||
"fastest-levenshtein": "^1.0.16",
|
||||
@@ -5458,32 +5591,32 @@
|
||||
"global-modules": "^2.0.0",
|
||||
"globby": "^11.1.0",
|
||||
"globjoin": "^0.1.4",
|
||||
"html-tags": "^3.2.0",
|
||||
"ignore": "^5.2.0",
|
||||
"html-tags": "^3.3.1",
|
||||
"ignore": "^5.2.4",
|
||||
"import-lazy": "^4.0.0",
|
||||
"imurmurhash": "^0.1.4",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"known-css-properties": "^0.25.0",
|
||||
"known-css-properties": "^0.27.0",
|
||||
"mathml-tag-names": "^2.1.3",
|
||||
"meow": "^9.0.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"normalize-path": "^3.0.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss": "^8.4.17",
|
||||
"postcss": "^8.4.22",
|
||||
"postcss-media-query-parser": "^0.2.3",
|
||||
"postcss-resolve-nested-selector": "^0.1.1",
|
||||
"postcss-safe-parser": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.0.10",
|
||||
"postcss-selector-parser": "^6.0.11",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"resolve-from": "^5.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"style-search": "^0.1.0",
|
||||
"supports-hyperlinks": "^2.3.0",
|
||||
"supports-hyperlinks": "^3.0.0",
|
||||
"svg-tags": "^1.0.0",
|
||||
"table": "^6.8.0",
|
||||
"table": "^6.8.1",
|
||||
"v8-compile-cache": "^2.3.0",
|
||||
"write-file-atomic": "^4.0.2"
|
||||
"write-file-atomic": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
@@ -5493,9 +5626,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"ignore": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz",
|
||||
"integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==",
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
|
||||
"integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
|
||||
"dev": true
|
||||
},
|
||||
"resolve-from": {
|
||||
@@ -5507,19 +5640,19 @@
|
||||
}
|
||||
},
|
||||
"stylelint-config-recommended": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz",
|
||||
"integrity": "sha512-yGn84Bf/q41J4luis1AZ95gj0EQwRX8lWmGmBwkwBNSkpGSpl66XcPTulxGa/Z91aPoNGuIGBmFkcM1MejMo9Q==",
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-12.0.0.tgz",
|
||||
"integrity": "sha512-x6x8QNARrGO2sG6iURkzqL+Dp+4bJorPMMRNPScdvaUK8PsynriOcMW7AFDKqkWAS5wbue/u8fUT/4ynzcmqdQ==",
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"stylelint-config-standard": {
|
||||
"version": "25.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-25.0.0.tgz",
|
||||
"integrity": "sha512-21HnP3VSpaT1wFjFvv9VjvOGDtAviv47uTp3uFmzcN+3Lt+RYRv6oAplLaV51Kf792JSxJ6svCJh/G18E9VnCA==",
|
||||
"version": "33.0.0",
|
||||
"resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-33.0.0.tgz",
|
||||
"integrity": "sha512-eyxnLWoXImUn77+ODIuW9qXBDNM+ALN68L3wT1lN2oNspZ7D9NVGlNHb2QCUn4xDug6VZLsh0tF8NyoYzkgTzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"stylelint-config-recommended": "^7.0.0"
|
||||
"stylelint-config-recommended": "^12.0.0"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
@@ -5532,9 +5665,9 @@
|
||||
}
|
||||
},
|
||||
"supports-hyperlinks": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
|
||||
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.0.0.tgz",
|
||||
"integrity": "sha512-QBDPHyPQDRTy9ku4URNGY5Lah8PAaXs6tAAwp55sL5WCsSW7GIfdf6W5ixfziW+t7wh3GVvHyHHyQ1ESsoRvaA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^4.0.0",
|
||||
@@ -5554,9 +5687,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"table": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz",
|
||||
"integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==",
|
||||
"version": "6.8.1",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
|
||||
"integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^8.0.1",
|
||||
@@ -5700,9 +5833,9 @@
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
@@ -5712,9 +5845,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"write-file-atomic": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
|
||||
"integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.0.tgz",
|
||||
"integrity": "sha512-R7NYMnHSlV42K54lwY9lvW6MnSm1HSJqZL3xiSgi9E7//FYaI74r2G0rd+/X6VAMkHEdzxQaU5HUOXWUz5kA/w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"imurmurhash": "^0.1.4",
|
||||
@@ -5727,12 +5860,6 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"stylelint": "^14.5.3",
|
||||
"stylelint-config-standard": "^25.0.0"
|
||||
"stylelint": "^15.6.0",
|
||||
"stylelint-config-standard": "^33.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
280
webui/style.css
280
webui/style.css
@@ -7,6 +7,13 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dialog {
|
||||
box-shadow: 0 0 6px 0 #444;
|
||||
max-width: 600px;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -26,31 +33,35 @@ fieldset#root-group {
|
||||
|
||||
h1 {
|
||||
display: inline;
|
||||
padding-left: 1em;
|
||||
font-size: small;
|
||||
padding-left: .5em;
|
||||
}
|
||||
|
||||
label#sidebar-toggler-button {
|
||||
#sidebar-toggler-button {
|
||||
top: 1em;
|
||||
left: 0.5em;
|
||||
height: 2em;
|
||||
width: 2em;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
text-align: center;
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
padding: .2em;
|
||||
box-shadow: 0 0 5px 0 #444;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
label#sidebar-toggler-button:hover,
|
||||
label#sidebar-toggler-button:focus {
|
||||
#sidebar-toggler-button:hover,
|
||||
#sidebar-toggler-button:focus {
|
||||
color: black;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer,
|
||||
footer a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
aside {
|
||||
position: absolute;
|
||||
width: 180px;
|
||||
@@ -62,6 +73,7 @@ aside {
|
||||
background-color: white;
|
||||
border: 0 0 10px 0;
|
||||
box-shadow: 0 0 10px 0 #444;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
input:checked ~ aside {
|
||||
@@ -69,20 +81,24 @@ input:checked ~ aside {
|
||||
}
|
||||
|
||||
aside ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
aside ul li {
|
||||
list-style: none;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
border-bottom: 1px inset black;
|
||||
}
|
||||
|
||||
aside ul li:hover {
|
||||
aside ul li a {
|
||||
display: block;
|
||||
padding-left: 1em;
|
||||
padding-top: 0.5em;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
aside ul li a:hover {
|
||||
color: black;
|
||||
background-color: #efefef;
|
||||
cursor: pointer;
|
||||
@@ -120,7 +136,7 @@ span[role="icon"] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
form span[role="icon"],
|
||||
.action-header span[role="icon"],
|
||||
tr.log-row span[role="icon"] {
|
||||
display: inline-block;
|
||||
padding-right: 0.2em;
|
||||
@@ -128,6 +144,9 @@ tr.log-row span[role="icon"] {
|
||||
|
||||
.error {
|
||||
background-color: salmon;
|
||||
}
|
||||
|
||||
div.error {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
@@ -146,6 +165,7 @@ div.entity {
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
font-size: 1em;
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -154,10 +174,20 @@ div.entity h2 {
|
||||
grid-column: 1 / span all;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
details {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
details[open] {
|
||||
margin-top: 1em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* General Buttons */
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
input[type="submit"]
|
||||
{
|
||||
padding: 1em;
|
||||
color: black;
|
||||
text-align: center;
|
||||
@@ -167,6 +197,60 @@ input[type="submit"] {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
action-button button {
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
action-button details {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
action-button details[open] {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
action-button details summary div {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
action-button details summary div span:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.action-button-footer {
|
||||
display: flex;
|
||||
text-align: left;
|
||||
font-size: smaller;
|
||||
background-color: #efefef;
|
||||
border-top: 0;
|
||||
border-left: 1px solid #666;
|
||||
border-right: 1px solid #666;
|
||||
border-bottom: 1px solid #666;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
execution-button {
|
||||
display: inline-block;
|
||||
margin-right: .2em;
|
||||
margin-left: .2em;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
execution-button button {
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
/* Button states */
|
||||
|
||||
button:hover,
|
||||
input[type="submit"]:hover {
|
||||
box-shadow: 0 0 10px 0 #666;
|
||||
@@ -185,86 +269,33 @@ input[type="submit"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
fieldset#section-switcher {
|
||||
border: 0;
|
||||
text-align: right;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
fieldset#section-switcher button {
|
||||
padding: 1em;
|
||||
color: black;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
border: 1px solid #999;
|
||||
background-color: white;
|
||||
box-shadow: 0 0 6px 0 #aaa;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
fieldset#root-group action-button button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
fieldset#section-switcher button:first-child {
|
||||
border-radius: 1em 0 0 1em;
|
||||
}
|
||||
|
||||
fieldset#section-switcher button:last-child {
|
||||
border-radius: 0 1em 1em 0;
|
||||
}
|
||||
|
||||
button.active-section {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Button animations */
|
||||
|
||||
.action-failed {
|
||||
animation: kf-action-failed 1s;
|
||||
}
|
||||
|
||||
@keyframes kf-action-failed {
|
||||
0% { background-color: black; }
|
||||
20% { background-color: red; }
|
||||
0% { background-color: inherit; }
|
||||
transition: 1s;
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.action-success {
|
||||
animation: kf-action-success 1s;
|
||||
}
|
||||
|
||||
@keyframes kf-action-success {
|
||||
0% { background-color: black; }
|
||||
20% { background-color: limegreen; }
|
||||
0% { background-color: inherit; }
|
||||
transition: 1s;
|
||||
background-color: limegreen;
|
||||
}
|
||||
|
||||
.action-nonzero-exit {
|
||||
animation: kf-action-nonzero-exit 1s;
|
||||
}
|
||||
|
||||
@keyframes kf-action-nonzero-exit {
|
||||
0% { background-color: black; }
|
||||
20% { background-color: orange; }
|
||||
0% { background-color: inherit; }
|
||||
transition: 1s;
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.action-timeout {
|
||||
animation: kf-action-timeout 1s;
|
||||
transition: 1s;
|
||||
background-color: cyan;
|
||||
}
|
||||
|
||||
@keyframes kf-action-timeout {
|
||||
0% { background-color: black; }
|
||||
20% { background-color: cyan; }
|
||||
0% { background-color: inherit; }
|
||||
}
|
||||
|
||||
footer,
|
||||
footer a {
|
||||
color: black;
|
||||
.action-blocked {
|
||||
transition: 1s;
|
||||
background-color: purple;
|
||||
}
|
||||
|
||||
img.logo {
|
||||
@@ -273,49 +304,18 @@ img.logo {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@media (max-width: 320px) {
|
||||
fieldset {
|
||||
grid-template-columns: auto;
|
||||
grid-gap: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 1em;
|
||||
padding-top: 3em;
|
||||
}
|
||||
|
||||
summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
details {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
details[open] {
|
||||
margin-top: 1em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
form.action-arguments {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1em;
|
||||
box-shadow: 0 0 6px 0 #aaa;
|
||||
background-color: #dee3e7;
|
||||
}
|
||||
|
||||
form div.wrapper {
|
||||
border-radius: 1em;
|
||||
box-shadow: 0 0 10px 0 #444;
|
||||
background-color: white;
|
||||
border: 1px solid #999;
|
||||
text-align: left;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
@@ -327,7 +327,9 @@ label {
|
||||
|
||||
#perma-widget {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
#perma-widget label {
|
||||
@@ -353,10 +355,12 @@ form input[type="submit"]:first-child {
|
||||
|
||||
button[name="cancel"]:hover {
|
||||
background-color: salmon;
|
||||
color: black;
|
||||
}
|
||||
|
||||
input[name="start"]:hover {
|
||||
background-color: #aceaac;
|
||||
color: black;
|
||||
}
|
||||
|
||||
span.argument-description {
|
||||
@@ -393,14 +397,29 @@ span.tag {
|
||||
padding: 0.2em;
|
||||
}
|
||||
|
||||
div.toolbar {
|
||||
padding: .4em;
|
||||
text-align: left;
|
||||
background-color: #efefef;
|
||||
border: 1px solid #999;
|
||||
border-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
div.toolbar * {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
form.action-arguments {
|
||||
background-color: #333;
|
||||
dialog {
|
||||
background-color: #222;
|
||||
color: white;
|
||||
}
|
||||
|
||||
form div.wrapper {
|
||||
@@ -408,13 +427,39 @@ span.tag {
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="submit"] {
|
||||
input[type="submit"],
|
||||
#sidebar-toggler-button {
|
||||
border: 1px solid #666;
|
||||
background-color: #222;
|
||||
box-shadow: 0 0 6px 0 #444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#sidebar-toggler-button:focus,
|
||||
#sidebar-toggler-button:hover
|
||||
{
|
||||
color: white;
|
||||
}
|
||||
|
||||
aside {
|
||||
background-color: #111;
|
||||
color: white;
|
||||
}
|
||||
|
||||
footer,
|
||||
footer a {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
aside ul li a:hover {
|
||||
background-color: #666;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.action-button-footer {
|
||||
background-color: #111;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: black;
|
||||
}
|
||||
@@ -434,9 +479,8 @@ span.tag {
|
||||
tr:hover td {
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
footer,
|
||||
footer a {
|
||||
color: gray;
|
||||
|
||||
div.toolbar {
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user