Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 77517a76da | |||
| aac38d954d | |||
| d5724d2652 | |||
| eafd2f144f | |||
| 589cad54d9 | |||
| 3a8054f68b | |||
| a4026e16e8 | |||
| ee4f94ce2e | |||
| 942f05e8d8 | |||
| cc67ee94f5 | |||
| f4dd232259 | |||
| a730eb9660 | |||
| f43f4d4ee4 | |||
| bcb3c326e8 | |||
| 3e72a7c2ff | |||
| 78eceb2237 | |||
| ef5954d7cf | |||
| 66b9f8ab45 | |||
| 45cf78c256 | |||
| ed43889e3b | |||
| 77789aca73 | |||
| 6eb356aaa9 | |||
| cb6adf275d | |||
| 4bd66601cb | |||
| 98bf45c5bc | |||
| 943cfab0e5 | |||
| 1835404376 | |||
| 38c84710f1 | |||
| e73ae6cbb7 | |||
| 45fa933f12 | |||
| feaf9c35d4 | |||
| b7dba3851d | |||
| 86bdf747da | |||
| 06bf8a312f | |||
| 48b0a64c7a | |||
| 0ab9c730c7 | |||
| 15fc01d265 | |||
| 6a4c384eb6 | |||
| 904b2a4f89 | |||
| a65dcbadb7 | |||
| 5ef251d021 | |||
| 681d286f5c | |||
| bfa33f0c35 | |||
| 7a0fc84842 | |||
| 9e5b6a88e2 | |||
| 3402b050d5 | |||
| 1d2e97b513 | |||
| a8c9237c72 | |||
| 3a214590be | |||
| f46900ec28 | |||
| b1068fdaf8 | |||
| c88c1ed643 | |||
| 56cb2ab346 | |||
| b9f6828703 | |||
| 0ed8192cae | |||
| aea1b67a7f | |||
| 0def791638 | |||
| f84013ffb8 | |||
| 0db179c312 | |||
| 5a4432244c | |||
| 6b09fc4ab7 | |||
| 0cf728473b | |||
| 729b1d9ce2 | |||
| 4b658fbf1c | |||
| 83746fdce0 | |||
| 8778d67174 | |||
| becfc9e8d5 | |||
| f332b5c9b5 | |||
| 0f3093ae61 | |||
| 7306a4eeb1 | |||
| 937638dce5 | |||
| 21078e79e8 | |||
| 1c8b9f50d7 | |||
| 9cd388642c | |||
| 3bd97f98b2 | |||
| d776777b7e | |||
| 8baa5021a6 | |||
| 65d464541e | |||
| 5d883c0825 | |||
| e6fa8ced58 | |||
| cdf9005b6f | |||
| 89aa10d4b7 | |||
| caa68df169 | |||
| e2230d5adc | |||
| 2fbb65c921 | |||
| 185538b794 | |||
| 3bb2a509f2 | |||
| cf43d36500 | |||
| 7819b3fec3 | |||
| 0d6c9305c7 | |||
| 24f298b94d | |||
| f240b7011b | |||
| f027c753c1 | |||
| 5c5b4bb622 | |||
| 7da60edb1a | |||
| 23fbf361ce | |||
| 6dab94d3b4 | |||
| fd5ed091d5 | |||
| 3d198bf6c1 | |||
| 4806b63ea8 | |||
| c2ee3d50c9 | |||
| 10e4a7cfaa | |||
| f78534f529 | |||
| 050e499f8e | |||
| 29e1bbc981 | |||
| 1fa44cc920 | |||
| 23aaf99947 | |||
| 60650ddc2c | |||
| b54b419a08 | |||
| 9bc5496b0e | |||
| 2016f8d8b0 | |||
| 72c6382d73 | |||
| ae72e3f4a6 | |||
| 1ec25e6a64 | |||
| ee4f344346 | |||
| 06e05bca45 | |||
| 5635ba4619 | |||
| 32dec4e817 | |||
| 2ed41282a5 | |||
| 26cd16a403 | |||
| b243783f5d | |||
| 8ab71e2caa | |||
| 318b07d1a9 | |||
| 7ff26b822f | |||
| cacf2cd261 | |||
| 47bc4b7fc5 | |||
| 38c4d9e4b7 | |||
| 1d87d09e89 | |||
| 70645be134 | |||
| c659f103f8 | |||
| 58fef4e16a | |||
| 70e0c359da | |||
| 0ad8f32588 | |||
| 07147d1e5c | |||
| 02a40fdd8e | |||
| eb94da7b92 | |||
| 660b4c6f51 | |||
| f08518d442 | |||
| b066433933 | |||
| 52dc1ff2d1 | |||
| 10ea7cfb51 | |||
| eea9813f84 | |||
| 8ac79c707c | |||
| 4637b49075 | |||
| 147902f191 | |||
| f83b5c948e | |||
| 542c895812 | |||
| d14d140cb6 | |||
| be74d48de7 | |||
| f8a9396f6e | |||
| 009529f20e | |||
| 63f16ef575 | |||
| 9de964dac4 | |||
| 1ff0b5541d | |||
| 55326cdd41 | |||
| 9fa4911aec | |||
| 7dae54df26 | |||
| 1b101e62da | |||
| 5c5cd17fb6 | |||
| c697be659b | |||
| d469066c07 | |||
| 4380681812 | |||
| 75cf2f045a | |||
| ab5807b3ae | |||
| fe0f163090 | |||
| 38d1f99387 | |||
| 11298e83b0 | |||
| 32de313c27 | |||
| 1e1c64d6e7 |
@@ -29,9 +29,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.21.x
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Go Tests with Coverage
|
||||
@@ -66,7 +67,7 @@ jobs:
|
||||
*.cache-to=type=gha,mode=max
|
||||
- name: Run Playwright tests
|
||||
run: docker compose up --exit-code-from playwright
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -90,7 +91,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
||||
|
||||
@@ -25,7 +25,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
uses: docker/build-push-action@v5.1.0
|
||||
with:
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64/v8
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
node-version: latest
|
||||
cache: pnpm # or pnpm / yarn
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v3
|
||||
uses: actions/configure-pages@v4
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Build with VitePress
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
pnpm docs:build
|
||||
touch docs/.vitepress/dist/.nojekyll
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v2
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/.vitepress/dist
|
||||
|
||||
@@ -59,4 +59,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -52,9 +52,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
go-version: 1.21.x
|
||||
check-latest: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Run Go Tests with Coverage
|
||||
@@ -89,7 +90,7 @@ jobs:
|
||||
*.cache-to=type=gha,mode=max
|
||||
- name: Run Playwright tests
|
||||
run: docker compose up --exit-code-from playwright
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
certs
|
||||
data
|
||||
/data
|
||||
dist
|
||||
node_modules
|
||||
.cache
|
||||
|
||||
@@ -14,7 +14,7 @@ COPY package.json ./
|
||||
RUN pnpm install --offline --ignore-scripts --no-optional
|
||||
|
||||
# Copy assets and translations to build
|
||||
COPY .* *.config.ts *.config.js ./
|
||||
COPY .* *.config.ts *.config.js *.config.cts ./
|
||||
COPY assets ./assets
|
||||
COPY locales ./locales
|
||||
COPY public ./public
|
||||
@@ -22,7 +22,7 @@ COPY public ./public
|
||||
# Build assets
|
||||
RUN pnpm build
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21.3-alpine AS builder
|
||||
FROM --platform=$BUILDPLATFORM golang:1.21.5-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache ca-certificates && mkdir /dozzle
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ Here is the Docker Compose file:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 8888:8080
|
||||
|
||||
For advanced options like [authentication](https://dozzle.dev/guide/authentication), [remote hosts](https://dozzle.dev/guide/remote-hosts) or common [questions](https://dozzle.dev/guide/faq) see documentation at [dozzle.dev](https://dozzle.dev/guide/getting-started).
|
||||
|
||||
@@ -76,7 +76,7 @@ If it's not enabled please follow [this tutorial](https://github.com/containers/
|
||||
Once you have the podman remote socket you can run Dozzle on podman.
|
||||
|
||||
```
|
||||
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8080:8080 amir20/dozzle:latest
|
||||
podman run --volume=/run/user/1000/podman/podman.sock:/var/run/docker.sock -d -p 8888:8080 amir20/dozzle:latest
|
||||
```
|
||||
|
||||
## Security
|
||||
@@ -89,7 +89,7 @@ Dozzle collects anonymous user configurations using Google Analytics. Why? Dozzl
|
||||
|
||||
If you do not want to be tracked at all, see the `--no-analytics` flag below.
|
||||
|
||||
#### Environment variables and configuration
|
||||
## Environment variables and configuration
|
||||
|
||||
Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can use the CLI flags or environment variables. The table below outlines all supported options and their respective env vars.
|
||||
|
||||
@@ -107,6 +107,16 @@ Dozzle follows the [12-factor](https://12factor.net/) model. Configurations can
|
||||
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
|
||||
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
|
||||
|
||||
## Support
|
||||
|
||||
There are many ways you can support Dozzle:
|
||||
|
||||
- Use it! Write about it! Star it! If you love Dozzle, drop me a line and tell me what you love.
|
||||
- Blog about Dozzle to spread the word. If you are good at writing send PRs to improve the documentation at [dozzle.dev](https://dozzle.dev/)
|
||||
- Sponsor my work at https://www.buymeacoffee.com/amirraminfar
|
||||
|
||||
<a href="https://www.buymeacoffee.com/amirraminfar" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
@@ -26,16 +26,14 @@ declare global {
|
||||
const computedInject: typeof import('@vueuse/core')['computedInject']
|
||||
const computedWithControl: typeof import('@vueuse/core')['computedWithControl']
|
||||
const config: typeof import('./stores/config')['default']
|
||||
const containerContext: typeof import('./composables/containerContext')['containerContext']
|
||||
const containerContext: typeof import('./composable/containerContext')['containerContext']
|
||||
const controlledComputed: typeof import('@vueuse/core')['controlledComputed']
|
||||
const controlledRef: typeof import('@vueuse/core')['controlledRef']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const createEventHook: typeof import('@vueuse/core')['createEventHook']
|
||||
const createGenericProjection: typeof import('@vueuse/math')['createGenericProjection']
|
||||
const createGlobalState: typeof import('@vueuse/core')['createGlobalState']
|
||||
const createInjectionState: typeof import('@vueuse/core')['createInjectionState']
|
||||
const createPinia: typeof import('pinia')['createPinia']
|
||||
const createProjection: typeof import('@vueuse/math')['createProjection']
|
||||
const createReactiveFn: typeof import('@vueuse/core')['createReactiveFn']
|
||||
const createReusableTemplate: typeof import('@vueuse/core')['createReusableTemplate']
|
||||
const createSharedComposable: typeof import('@vueuse/core')['createSharedComposable']
|
||||
@@ -56,23 +54,21 @@ declare global {
|
||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||
const getDeep: typeof import('./utils/index')['getDeep']
|
||||
const globalShowPopup: typeof import('./composables/popup')['globalShowPopup']
|
||||
const globalShowPopup: typeof import('./composable/popup')['globalShowPopup']
|
||||
const h: typeof import('vue')['h']
|
||||
const hourStyle: typeof import('./stores/settings')['hourStyle']
|
||||
const ignorableWatch: typeof import('@vueuse/core')['ignorableWatch']
|
||||
const inject: typeof import('vue')['inject']
|
||||
const injectLocal: typeof import('@vueuse/core')['injectLocal']
|
||||
const isDefined: typeof import('@vueuse/core')['isDefined']
|
||||
const isMobile: typeof import('./composables/media')['isMobile']
|
||||
const isMobile: typeof import('./composable/media')['isMobile']
|
||||
const isObject: typeof import('./utils/index')['isObject']
|
||||
const isProxy: typeof import('vue')['isProxy']
|
||||
const isReactive: typeof import('vue')['isReactive']
|
||||
const isReadonly: typeof import('vue')['isReadonly']
|
||||
const isRef: typeof import('vue')['isRef']
|
||||
const lightTheme: typeof import('./stores/settings')['lightTheme']
|
||||
const logicAnd: typeof import('@vueuse/math')['logicAnd']
|
||||
const logicNot: typeof import('@vueuse/math')['logicNot']
|
||||
const logicOr: typeof import('@vueuse/math')['logicOr']
|
||||
const logSearchContext: typeof import('./composable/logSearchContext')['logSearchContext']
|
||||
const makeDestructurable: typeof import('@vueuse/core')['makeDestructurable']
|
||||
const mapActions: typeof import('pinia')['mapActions']
|
||||
const mapGetters: typeof import('pinia')['mapGetters']
|
||||
@@ -102,10 +98,10 @@ declare global {
|
||||
const onUnmounted: typeof import('vue')['onUnmounted']
|
||||
const onUpdated: typeof import('vue')['onUpdated']
|
||||
const pausableWatch: typeof import('@vueuse/core')['pausableWatch']
|
||||
const persistentVisibleKeys: typeof import('./composables/storage')['persistentVisibleKeys']
|
||||
const pinnedContainers: typeof import('./composables/storage')['pinnedContainers']
|
||||
const persistentVisibleKeys: typeof import('./composable/storage')['persistentVisibleKeys']
|
||||
const pinnedContainers: typeof import('./composable/storage')['pinnedContainers']
|
||||
const provide: typeof import('vue')['provide']
|
||||
const provideContainerContext: typeof import('./composables/containerContext')['provideContainerContext']
|
||||
const provideContainerContext: typeof import('./composable/containerContext')['provideContainerContext']
|
||||
const provideLocal: typeof import('@vueuse/core')['provideLocal']
|
||||
const reactify: typeof import('@vueuse/core')['reactify']
|
||||
const reactifyObject: typeof import('@vueuse/core')['reactifyObject']
|
||||
@@ -124,10 +120,10 @@ declare global {
|
||||
const resolveRef: typeof import('@vueuse/core')['resolveRef']
|
||||
const resolveUnref: typeof import('@vueuse/core')['resolveUnref']
|
||||
const search: typeof import('./stores/settings')['search']
|
||||
const sessionHost: typeof import('./composables/storage')['sessionHost']
|
||||
const sessionHost: typeof import('./composable/storage')['sessionHost']
|
||||
const setActivePinia: typeof import('pinia')['setActivePinia']
|
||||
const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']
|
||||
const setTitle: typeof import('./composables/title')['setTitle']
|
||||
const setTitle: typeof import('./composable/title')['setTitle']
|
||||
const settings: typeof import('./stores/settings')['settings']
|
||||
const shallowReactive: typeof import('vue')['shallowReactive']
|
||||
const shallowReadonly: typeof import('vue')['shallowReadonly']
|
||||
@@ -159,7 +155,6 @@ declare global {
|
||||
const unref: typeof import('vue')['unref']
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useAbs: typeof import('@vueuse/math')['useAbs']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||
@@ -177,7 +172,6 @@ declare global {
|
||||
const useAsyncQueue: typeof import('@vueuse/core')['useAsyncQueue']
|
||||
const useAsyncState: typeof import('@vueuse/core')['useAsyncState']
|
||||
const useAttrs: typeof import('vue')['useAttrs']
|
||||
const useAverage: typeof import('@vueuse/math')['useAverage']
|
||||
const useBase64: typeof import('@vueuse/core')['useBase64']
|
||||
const useBattery: typeof import('@vueuse/core')['useBattery']
|
||||
const useBluetooth: typeof import('@vueuse/core')['useBluetooth']
|
||||
@@ -185,13 +179,13 @@ declare global {
|
||||
const useBroadcastChannel: typeof import('@vueuse/core')['useBroadcastChannel']
|
||||
const useBrowserLocation: typeof import('@vueuse/core')['useBrowserLocation']
|
||||
const useCached: typeof import('@vueuse/core')['useCached']
|
||||
const useCeil: typeof import('@vueuse/math')['useCeil']
|
||||
const useClamp: typeof import('@vueuse/math')['useClamp']
|
||||
const useClipboard: typeof import('@vueuse/core')['useClipboard']
|
||||
const useClipboardItems: typeof import('@vueuse/core')['useClipboardItems']
|
||||
const useCloned: typeof import('@vueuse/core')['useCloned']
|
||||
const useColorMode: typeof import('@vueuse/core')['useColorMode']
|
||||
const useConfirmDialog: typeof import('@vueuse/core')['useConfirmDialog']
|
||||
const useContainerContext: typeof import('./composables/containerContext')['useContainerContext']
|
||||
const useContainerActions: typeof import('./composable/containerActions')['useContainerActions']
|
||||
const useContainerContext: typeof import('./composable/containerContext')['useContainerContext']
|
||||
const useContainerStore: typeof import('./stores/container')['useContainerStore']
|
||||
const useCounter: typeof import('@vueuse/core')['useCounter']
|
||||
const useCssModule: typeof import('vue')['useCssModule']
|
||||
@@ -226,7 +220,6 @@ declare global {
|
||||
const useFetch: typeof import('@vueuse/core')['useFetch']
|
||||
const useFileDialog: typeof import('@vueuse/core')['useFileDialog']
|
||||
const useFileSystemAccess: typeof import('@vueuse/core')['useFileSystemAccess']
|
||||
const useFloor: typeof import('@vueuse/math')['useFloor']
|
||||
const useFocus: typeof import('@vueuse/core')['useFocus']
|
||||
const useFocusWithin: typeof import('@vueuse/core')['useFocusWithin']
|
||||
const useFps: typeof import('@vueuse/core')['useFps']
|
||||
@@ -245,16 +238,14 @@ declare global {
|
||||
const useLastChanged: typeof import('@vueuse/core')['useLastChanged']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage']
|
||||
const useLogStream: typeof import('./composables/eventsource')['useLogStream']
|
||||
const useLogSearchContext: typeof import('./composable/logSearchContext')['useLogSearchContext']
|
||||
const useLogStream: typeof import('./composable/eventsource')['useLogStream']
|
||||
const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys']
|
||||
const useManualRefHistory: typeof import('@vueuse/core')['useManualRefHistory']
|
||||
const useMath: typeof import('@vueuse/math')['useMath']
|
||||
const useMax: typeof import('@vueuse/math')['useMax']
|
||||
const useMediaControls: typeof import('@vueuse/core')['useMediaControls']
|
||||
const useMediaQuery: typeof import('@vueuse/core')['useMediaQuery']
|
||||
const useMemoize: typeof import('@vueuse/core')['useMemoize']
|
||||
const useMemory: typeof import('@vueuse/core')['useMemory']
|
||||
const useMin: typeof import('@vueuse/math')['useMin']
|
||||
const useMounted: typeof import('@vueuse/core')['useMounted']
|
||||
const useMouse: typeof import('@vueuse/core')['useMouse']
|
||||
const useMouseInElement: typeof import('@vueuse/core')['useMouseInElement']
|
||||
@@ -274,18 +265,17 @@ declare global {
|
||||
const usePointer: typeof import('@vueuse/core')['usePointer']
|
||||
const usePointerLock: typeof import('@vueuse/core')['usePointerLock']
|
||||
const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe']
|
||||
const usePrecision: typeof import('@vueuse/math')['usePrecision']
|
||||
const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme']
|
||||
const usePreferredContrast: typeof import('@vueuse/core')['usePreferredContrast']
|
||||
const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark']
|
||||
const usePreferredLanguages: typeof import('@vueuse/core')['usePreferredLanguages']
|
||||
const usePreferredReducedMotion: typeof import('@vueuse/core')['usePreferredReducedMotion']
|
||||
const usePrevious: typeof import('@vueuse/core')['usePrevious']
|
||||
const useProjection: typeof import('@vueuse/math')['useProjection']
|
||||
const useProfileStorage: typeof import('./composable/profileStorage')['useProfileStorage']
|
||||
const useRafFn: typeof import('@vueuse/core')['useRafFn']
|
||||
const useRefHistory: typeof import('@vueuse/core')['useRefHistory']
|
||||
const useReleases: typeof import('./stores/releases')['useReleases']
|
||||
const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver']
|
||||
const useRound: typeof import('@vueuse/math')['useRound']
|
||||
const useRoute: typeof import('vue-router')['useRoute']
|
||||
const useRouter: typeof import('vue-router')['useRouter']
|
||||
const useScreenOrientation: typeof import('@vueuse/core')['useScreenOrientation']
|
||||
@@ -293,7 +283,7 @@ declare global {
|
||||
const useScriptTag: typeof import('@vueuse/core')['useScriptTag']
|
||||
const useScroll: typeof import('@vueuse/core')['useScroll']
|
||||
const useScrollLock: typeof import('@vueuse/core')['useScrollLock']
|
||||
const useSearchFilter: typeof import('./composables/search')['useSearchFilter']
|
||||
const useSearchFilter: typeof import('./composable/search')['useSearchFilter']
|
||||
const useSeoMeta: typeof import('@vueuse/head')['useSeoMeta']
|
||||
const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage']
|
||||
const useShare: typeof import('@vueuse/core')['useShare']
|
||||
@@ -305,7 +295,6 @@ declare global {
|
||||
const useStorage: typeof import('@vueuse/core')['useStorage']
|
||||
const useStorageAsync: typeof import('@vueuse/core')['useStorageAsync']
|
||||
const useStyleTag: typeof import('@vueuse/core')['useStyleTag']
|
||||
const useSum: typeof import('@vueuse/math')['useSum']
|
||||
const useSupported: typeof import('@vueuse/core')['useSupported']
|
||||
const useSwipe: typeof import('@vueuse/core')['useSwipe']
|
||||
const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList']
|
||||
@@ -323,17 +312,16 @@ declare global {
|
||||
const useTitle: typeof import('@vueuse/core')['useTitle']
|
||||
const useToNumber: typeof import('@vueuse/core')['useToNumber']
|
||||
const useToString: typeof import('@vueuse/core')['useToString']
|
||||
const useToast: typeof import('./composables/toast')['useToast']
|
||||
const useToast: typeof import('./composable/toast')['useToast']
|
||||
const useToggle: typeof import('@vueuse/core')['useToggle']
|
||||
const useTransition: typeof import('@vueuse/core')['useTransition']
|
||||
const useTrunc: typeof import('@vueuse/math')['useTrunc']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
const useVirtualList: typeof import('@vueuse/core')['useVirtualList']
|
||||
const useVisibleFilter: typeof import('./composables/visible')['useVisibleFilter']
|
||||
const useVisibleFilter: typeof import('./composable/visible')['useVisibleFilter']
|
||||
const useWakeLock: typeof import('@vueuse/core')['useWakeLock']
|
||||
const useWebNotification: typeof import('@vueuse/core')['useWebNotification']
|
||||
const useWebSocket: typeof import('@vueuse/core')['useWebSocket']
|
||||
@@ -364,6 +352,7 @@ declare global {
|
||||
declare global {
|
||||
// @ts-ignore
|
||||
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
|
||||
import('vue')
|
||||
}
|
||||
// for vue template auto import
|
||||
import { UnwrapRef } from 'vue'
|
||||
@@ -390,7 +379,7 @@ declare module 'vue' {
|
||||
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
||||
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
||||
readonly config: UnwrapRef<typeof import('./stores/config')['default']>
|
||||
readonly containerContext: UnwrapRef<typeof import('./composables/containerContext')['containerContext']>
|
||||
readonly containerContext: UnwrapRef<typeof import('./composable/containerContext')['containerContext']>
|
||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
@@ -418,14 +407,14 @@ declare module 'vue' {
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
|
||||
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
|
||||
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly hourStyle: UnwrapRef<typeof import('./stores/settings')['hourStyle']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isMobile: UnwrapRef<typeof import('./composables/media')['isMobile']>
|
||||
readonly isMobile: UnwrapRef<typeof import('./composable/media')['isMobile']>
|
||||
readonly isObject: UnwrapRef<typeof import('./utils/index')['isObject']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
@@ -461,10 +450,10 @@ declare module 'vue' {
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
|
||||
readonly pinnedContainers: UnwrapRef<typeof import('./composables/storage')['pinnedContainers']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composable/storage')['persistentVisibleKeys']>
|
||||
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideContainerContext: UnwrapRef<typeof import('./composables/containerContext')['provideContainerContext']>
|
||||
readonly provideContainerContext: UnwrapRef<typeof import('./composable/containerContext')['provideContainerContext']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
@@ -483,10 +472,10 @@ declare module 'vue' {
|
||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
|
||||
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
|
||||
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
|
||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
|
||||
readonly setTitle: UnwrapRef<typeof import('./composable/title')['setTitle']>
|
||||
readonly settings: UnwrapRef<typeof import('./stores/settings')['settings']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
@@ -543,10 +532,12 @@ declare module 'vue' {
|
||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
readonly useContainerContext: UnwrapRef<typeof import('./composables/containerContext')['useContainerContext']>
|
||||
readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']>
|
||||
readonly useContainerContext: UnwrapRef<typeof import('./composable/containerContext')['useContainerContext']>
|
||||
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
|
||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
@@ -599,7 +590,8 @@ declare module 'vue' {
|
||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
|
||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
|
||||
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
||||
readonly useLogStream: UnwrapRef<typeof import('./composable/eventsource')['useLogStream']>
|
||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
||||
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
||||
@@ -631,8 +623,10 @@ declare module 'vue' {
|
||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||
readonly useProfileStorage: UnwrapRef<typeof import('./composable/profileStorage')['useProfileStorage']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
|
||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||
@@ -641,7 +635,7 @@ declare module 'vue' {
|
||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
||||
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
||||
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
|
||||
readonly useSearchFilter: UnwrapRef<typeof import('./composable/search')['useSearchFilter']>
|
||||
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
|
||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
||||
@@ -670,7 +664,7 @@ declare module 'vue' {
|
||||
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
||||
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
||||
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
||||
readonly useToast: UnwrapRef<typeof import('./composables/toast')['useToast']>
|
||||
readonly useToast: UnwrapRef<typeof import('./composable/toast')['useToast']>
|
||||
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||
@@ -679,7 +673,7 @@ declare module 'vue' {
|
||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||
readonly useVisibleFilter: UnwrapRef<typeof import('./composables/visible')['useVisibleFilter']>
|
||||
readonly useVisibleFilter: UnwrapRef<typeof import('./composable/visible')['useVisibleFilter']>
|
||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||
@@ -730,7 +724,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly computedInject: UnwrapRef<typeof import('@vueuse/core')['computedInject']>
|
||||
readonly computedWithControl: UnwrapRef<typeof import('@vueuse/core')['computedWithControl']>
|
||||
readonly config: UnwrapRef<typeof import('./stores/config')['default']>
|
||||
readonly containerContext: UnwrapRef<typeof import('./composables/containerContext')['containerContext']>
|
||||
readonly containerContext: UnwrapRef<typeof import('./composable/containerContext')['containerContext']>
|
||||
readonly controlledComputed: UnwrapRef<typeof import('@vueuse/core')['controlledComputed']>
|
||||
readonly controlledRef: UnwrapRef<typeof import('@vueuse/core')['controlledRef']>
|
||||
readonly createApp: UnwrapRef<typeof import('vue')['createApp']>
|
||||
@@ -758,14 +752,14 @@ declare module '@vue/runtime-core' {
|
||||
readonly getCurrentInstance: UnwrapRef<typeof import('vue')['getCurrentInstance']>
|
||||
readonly getCurrentScope: UnwrapRef<typeof import('vue')['getCurrentScope']>
|
||||
readonly getDeep: UnwrapRef<typeof import('./utils/index')['getDeep']>
|
||||
readonly globalShowPopup: UnwrapRef<typeof import('./composables/popup')['globalShowPopup']>
|
||||
readonly globalShowPopup: UnwrapRef<typeof import('./composable/popup')['globalShowPopup']>
|
||||
readonly h: UnwrapRef<typeof import('vue')['h']>
|
||||
readonly hourStyle: UnwrapRef<typeof import('./stores/settings')['hourStyle']>
|
||||
readonly ignorableWatch: UnwrapRef<typeof import('@vueuse/core')['ignorableWatch']>
|
||||
readonly inject: UnwrapRef<typeof import('vue')['inject']>
|
||||
readonly injectLocal: UnwrapRef<typeof import('@vueuse/core')['injectLocal']>
|
||||
readonly isDefined: UnwrapRef<typeof import('@vueuse/core')['isDefined']>
|
||||
readonly isMobile: UnwrapRef<typeof import('./composables/media')['isMobile']>
|
||||
readonly isMobile: UnwrapRef<typeof import('./composable/media')['isMobile']>
|
||||
readonly isObject: UnwrapRef<typeof import('./utils/index')['isObject']>
|
||||
readonly isProxy: UnwrapRef<typeof import('vue')['isProxy']>
|
||||
readonly isReactive: UnwrapRef<typeof import('vue')['isReactive']>
|
||||
@@ -801,10 +795,10 @@ declare module '@vue/runtime-core' {
|
||||
readonly onUnmounted: UnwrapRef<typeof import('vue')['onUnmounted']>
|
||||
readonly onUpdated: UnwrapRef<typeof import('vue')['onUpdated']>
|
||||
readonly pausableWatch: UnwrapRef<typeof import('@vueuse/core')['pausableWatch']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composables/storage')['persistentVisibleKeys']>
|
||||
readonly pinnedContainers: UnwrapRef<typeof import('./composables/storage')['pinnedContainers']>
|
||||
readonly persistentVisibleKeys: UnwrapRef<typeof import('./composable/storage')['persistentVisibleKeys']>
|
||||
readonly pinnedContainers: UnwrapRef<typeof import('./composable/storage')['pinnedContainers']>
|
||||
readonly provide: UnwrapRef<typeof import('vue')['provide']>
|
||||
readonly provideContainerContext: UnwrapRef<typeof import('./composables/containerContext')['provideContainerContext']>
|
||||
readonly provideContainerContext: UnwrapRef<typeof import('./composable/containerContext')['provideContainerContext']>
|
||||
readonly provideLocal: UnwrapRef<typeof import('@vueuse/core')['provideLocal']>
|
||||
readonly reactify: UnwrapRef<typeof import('@vueuse/core')['reactify']>
|
||||
readonly reactifyObject: UnwrapRef<typeof import('@vueuse/core')['reactifyObject']>
|
||||
@@ -823,10 +817,10 @@ declare module '@vue/runtime-core' {
|
||||
readonly resolveRef: UnwrapRef<typeof import('@vueuse/core')['resolveRef']>
|
||||
readonly resolveUnref: UnwrapRef<typeof import('@vueuse/core')['resolveUnref']>
|
||||
readonly search: UnwrapRef<typeof import('./stores/settings')['search']>
|
||||
readonly sessionHost: UnwrapRef<typeof import('./composables/storage')['sessionHost']>
|
||||
readonly sessionHost: UnwrapRef<typeof import('./composable/storage')['sessionHost']>
|
||||
readonly setActivePinia: UnwrapRef<typeof import('pinia')['setActivePinia']>
|
||||
readonly setMapStoreSuffix: UnwrapRef<typeof import('pinia')['setMapStoreSuffix']>
|
||||
readonly setTitle: UnwrapRef<typeof import('./composables/title')['setTitle']>
|
||||
readonly setTitle: UnwrapRef<typeof import('./composable/title')['setTitle']>
|
||||
readonly settings: UnwrapRef<typeof import('./stores/settings')['settings']>
|
||||
readonly shallowReactive: UnwrapRef<typeof import('vue')['shallowReactive']>
|
||||
readonly shallowReadonly: UnwrapRef<typeof import('vue')['shallowReadonly']>
|
||||
@@ -883,10 +877,12 @@ declare module '@vue/runtime-core' {
|
||||
readonly useBrowserLocation: UnwrapRef<typeof import('@vueuse/core')['useBrowserLocation']>
|
||||
readonly useCached: UnwrapRef<typeof import('@vueuse/core')['useCached']>
|
||||
readonly useClipboard: UnwrapRef<typeof import('@vueuse/core')['useClipboard']>
|
||||
readonly useClipboardItems: UnwrapRef<typeof import('@vueuse/core')['useClipboardItems']>
|
||||
readonly useCloned: UnwrapRef<typeof import('@vueuse/core')['useCloned']>
|
||||
readonly useColorMode: UnwrapRef<typeof import('@vueuse/core')['useColorMode']>
|
||||
readonly useConfirmDialog: UnwrapRef<typeof import('@vueuse/core')['useConfirmDialog']>
|
||||
readonly useContainerContext: UnwrapRef<typeof import('./composables/containerContext')['useContainerContext']>
|
||||
readonly useContainerActions: UnwrapRef<typeof import('./composable/containerActions')['useContainerActions']>
|
||||
readonly useContainerContext: UnwrapRef<typeof import('./composable/containerContext')['useContainerContext']>
|
||||
readonly useContainerStore: UnwrapRef<typeof import('./stores/container')['useContainerStore']>
|
||||
readonly useCounter: UnwrapRef<typeof import('@vueuse/core')['useCounter']>
|
||||
readonly useCssModule: UnwrapRef<typeof import('vue')['useCssModule']>
|
||||
@@ -939,7 +935,8 @@ declare module '@vue/runtime-core' {
|
||||
readonly useLastChanged: UnwrapRef<typeof import('@vueuse/core')['useLastChanged']>
|
||||
readonly useLink: UnwrapRef<typeof import('vue-router')['useLink']>
|
||||
readonly useLocalStorage: UnwrapRef<typeof import('@vueuse/core')['useLocalStorage']>
|
||||
readonly useLogStream: UnwrapRef<typeof import('./composables/eventsource')['useLogStream']>
|
||||
readonly useLogSearchContext: UnwrapRef<typeof import('./composable/logSearchContext')['useLogSearchContext']>
|
||||
readonly useLogStream: UnwrapRef<typeof import('./composable/eventsource')['useLogStream']>
|
||||
readonly useMagicKeys: UnwrapRef<typeof import('@vueuse/core')['useMagicKeys']>
|
||||
readonly useManualRefHistory: UnwrapRef<typeof import('@vueuse/core')['useManualRefHistory']>
|
||||
readonly useMediaControls: UnwrapRef<typeof import('@vueuse/core')['useMediaControls']>
|
||||
@@ -971,8 +968,10 @@ declare module '@vue/runtime-core' {
|
||||
readonly usePreferredLanguages: UnwrapRef<typeof import('@vueuse/core')['usePreferredLanguages']>
|
||||
readonly usePreferredReducedMotion: UnwrapRef<typeof import('@vueuse/core')['usePreferredReducedMotion']>
|
||||
readonly usePrevious: UnwrapRef<typeof import('@vueuse/core')['usePrevious']>
|
||||
readonly useProfileStorage: UnwrapRef<typeof import('./composable/profileStorage')['useProfileStorage']>
|
||||
readonly useRafFn: UnwrapRef<typeof import('@vueuse/core')['useRafFn']>
|
||||
readonly useRefHistory: UnwrapRef<typeof import('@vueuse/core')['useRefHistory']>
|
||||
readonly useReleases: UnwrapRef<typeof import('./stores/releases')['useReleases']>
|
||||
readonly useResizeObserver: UnwrapRef<typeof import('@vueuse/core')['useResizeObserver']>
|
||||
readonly useRoute: UnwrapRef<typeof import('vue-router')['useRoute']>
|
||||
readonly useRouter: UnwrapRef<typeof import('vue-router')['useRouter']>
|
||||
@@ -981,7 +980,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly useScriptTag: UnwrapRef<typeof import('@vueuse/core')['useScriptTag']>
|
||||
readonly useScroll: UnwrapRef<typeof import('@vueuse/core')['useScroll']>
|
||||
readonly useScrollLock: UnwrapRef<typeof import('@vueuse/core')['useScrollLock']>
|
||||
readonly useSearchFilter: UnwrapRef<typeof import('./composables/search')['useSearchFilter']>
|
||||
readonly useSearchFilter: UnwrapRef<typeof import('./composable/search')['useSearchFilter']>
|
||||
readonly useSeoMeta: UnwrapRef<typeof import('@vueuse/head')['useSeoMeta']>
|
||||
readonly useSessionStorage: UnwrapRef<typeof import('@vueuse/core')['useSessionStorage']>
|
||||
readonly useShare: UnwrapRef<typeof import('@vueuse/core')['useShare']>
|
||||
@@ -1010,7 +1009,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly useTitle: UnwrapRef<typeof import('@vueuse/core')['useTitle']>
|
||||
readonly useToNumber: UnwrapRef<typeof import('@vueuse/core')['useToNumber']>
|
||||
readonly useToString: UnwrapRef<typeof import('@vueuse/core')['useToString']>
|
||||
readonly useToast: UnwrapRef<typeof import('./composables/toast')['useToast']>
|
||||
readonly useToast: UnwrapRef<typeof import('./composable/toast')['useToast']>
|
||||
readonly useToggle: UnwrapRef<typeof import('@vueuse/core')['useToggle']>
|
||||
readonly useTransition: UnwrapRef<typeof import('@vueuse/core')['useTransition']>
|
||||
readonly useUrlSearchParams: UnwrapRef<typeof import('@vueuse/core')['useUrlSearchParams']>
|
||||
@@ -1019,7 +1018,7 @@ declare module '@vue/runtime-core' {
|
||||
readonly useVModels: UnwrapRef<typeof import('@vueuse/core')['useVModels']>
|
||||
readonly useVibrate: UnwrapRef<typeof import('@vueuse/core')['useVibrate']>
|
||||
readonly useVirtualList: UnwrapRef<typeof import('@vueuse/core')['useVirtualList']>
|
||||
readonly useVisibleFilter: UnwrapRef<typeof import('./composables/visible')['useVisibleFilter']>
|
||||
readonly useVisibleFilter: UnwrapRef<typeof import('./composable/visible')['useVisibleFilter']>
|
||||
readonly useWakeLock: UnwrapRef<typeof import('@vueuse/core')['useWakeLock']>
|
||||
readonly useWebNotification: UnwrapRef<typeof import('@vueuse/core')['useWebNotification']>
|
||||
readonly useWebSocket: UnwrapRef<typeof import('@vueuse/core')['useWebSocket']>
|
||||
|
||||
@@ -10,10 +10,15 @@ declare module 'vue' {
|
||||
BarChart: typeof import('./components/BarChart.vue')['default']
|
||||
'Carbon:caretDown': typeof import('~icons/carbon/caret-down')['default']
|
||||
'Carbon:circleSolid': typeof import('~icons/carbon/circle-solid')['default']
|
||||
'Carbon:copyFile': typeof import('~icons/carbon/copy-file')['default']
|
||||
'Carbon:information': typeof import('~icons/carbon/information')['default']
|
||||
'Carbon:macShift': typeof import('~icons/carbon/mac-shift')['default']
|
||||
'Carbon:play': typeof import('~icons/carbon/play')['default']
|
||||
'Carbon:restart': typeof import('~icons/carbon/restart')['default']
|
||||
'Carbon:searchLocate': typeof import('~icons/carbon/search-locate')['default']
|
||||
'Carbon:star': typeof import('~icons/carbon/star')['default']
|
||||
'Carbon:starFilled': typeof import('~icons/carbon/star-filled')['default']
|
||||
'Carbon:stopFilledAlt': typeof import('~icons/carbon/stop-filled-alt')['default']
|
||||
'Carbon:warning': typeof import('~icons/carbon/warning')['default']
|
||||
'Cil:checkCircle': typeof import('~icons/cil/check-circle')['default']
|
||||
'Cil:circle': typeof import('~icons/cil/circle')['default']
|
||||
@@ -29,20 +34,23 @@ declare module 'vue' {
|
||||
DistanceTime: typeof import('./components/common/DistanceTime.vue')['default']
|
||||
DockerEventLogItem: typeof import('./components/LogViewer/DockerEventLogItem.vue')['default']
|
||||
Dropdown: typeof import('./components/common/Dropdown.vue')['default']
|
||||
DropdownMenu: typeof import('./components/common/DropdownMenu.vue')['default']
|
||||
FieldList: typeof import('./components/LogViewer/FieldList.vue')['default']
|
||||
FuzzySearchModal: typeof import('./components/FuzzySearchModal.vue')['default']
|
||||
'Ic:sharpFindInPage': typeof import('~icons/ic/sharp-find-in-page')['default']
|
||||
'Ic:sharpKeyboardReturn': typeof import('~icons/ic/sharp-keyboard-return')['default']
|
||||
InfiniteLoader: typeof import('./components/InfiniteLoader.vue')['default']
|
||||
KeyShortcut: typeof import('./components/common/KeyShortcut.vue')['default']
|
||||
Links: typeof import('./components/Links.vue')['default']
|
||||
LogActionsToolbar: typeof import('./components/LogViewer/LogActionsToolbar.vue')['default']
|
||||
LogContainer: typeof import('./components/LogViewer/LogContainer.vue')['default']
|
||||
LogDate: typeof import('./components/LogViewer/LogDate.vue')['default']
|
||||
LogEventSource: typeof import('./components/LogViewer/LogEventSource.vue')['default']
|
||||
LogLevel: typeof import('./components/LogViewer/LogLevel.vue')['default']
|
||||
LogMessageActions: typeof import('./components/LogViewer/LogMessageActions.vue')['default']
|
||||
LogStd: typeof import('./components/LogViewer/LogStd.vue')['default']
|
||||
LogViewer: typeof import('./components/LogViewer/LogViewer.vue')['default']
|
||||
LogViewerWithSource: typeof import('./components/LogViewer/LogViewerWithSource.vue')['default']
|
||||
'Mdi:announcement': typeof import('~icons/mdi/announcement')['default']
|
||||
'Mdi:arrowUp': typeof import('~icons/mdi/arrow-up')['default']
|
||||
'Mdi:check': typeof import('~icons/mdi/check')['default']
|
||||
'Mdi:chevronDoubleDown': typeof import('~icons/mdi/chevron-double-down')['default']
|
||||
@@ -52,16 +60,17 @@ declare module 'vue' {
|
||||
'Mdi:cog': typeof import('~icons/mdi/cog')['default']
|
||||
'Mdi:hamburgerMenu': typeof import('~icons/mdi/hamburger-menu')['default']
|
||||
'Mdi:keyboardEsc': typeof import('~icons/mdi/keyboard-esc')['default']
|
||||
'Mdi:logout': typeof import('~icons/mdi/logout')['default']
|
||||
'Mdi:magnify': typeof import('~icons/mdi/magnify')['default']
|
||||
MobileMenu: typeof import('./components/common/MobileMenu.vue')['default']
|
||||
'Octicon:container24': typeof import('~icons/octicon/container24')['default']
|
||||
'Octicon:download24': typeof import('~icons/octicon/download24')['default']
|
||||
'Octicon:trash24': typeof import('~icons/octicon/trash24')['default']
|
||||
PageWithLinks: typeof import('./components/PageWithLinks.vue')['default']
|
||||
'Ph:command': typeof import('~icons/ph/command')['default']
|
||||
'Ph:computerTower': typeof import('~icons/ph/computer-tower')['default']
|
||||
'Ph:controlBold': typeof import('~icons/ph/control-bold')['default']
|
||||
Popup: typeof import('./components/Popup.vue')['default']
|
||||
Releases: typeof import('./components/Releases.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
ScrollableView: typeof import('./components/ScrollableView.vue')['default']
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { createTestingPinia } from "@pinia/testing";
|
||||
import { mount } from "@vue/test-utils";
|
||||
|
||||
import FuzzySearchModal from "./FuzzySearchModal.vue";
|
||||
|
||||
import { Container } from "@/models/Container";
|
||||
import { describe, expect, test, vi } from "vitest";
|
||||
import { createI18n } from "vue-i18n";
|
||||
|
||||
// @ts-ignore
|
||||
import EventSource, { sources } from "eventsourcemock";
|
||||
|
||||
vi.mock("@/stores/config", () => ({
|
||||
__esModule: true,
|
||||
default: { base: "", hosts: [{ name: "localhost", id: "localhost" }] },
|
||||
withBase: (path: string) => path,
|
||||
}));
|
||||
|
||||
function createFuzzySearchModal() {
|
||||
global.EventSource = EventSource;
|
||||
const wrapper = mount(FuzzySearchModal, {
|
||||
global: {
|
||||
plugins: [
|
||||
createI18n({}),
|
||||
createTestingPinia({
|
||||
createSpy: vi.fn,
|
||||
initialState: {
|
||||
container: {
|
||||
containers: [
|
||||
new Container("123", new Date(), "image", "test", "command", "host", {}, "status", "running"),
|
||||
new Container("123", new Date(), "image", "foo bar", "command", "host", {}, "status", "running"),
|
||||
new Container("123", new Date(), "image", "baz", "command", "host", {}, "status", "exited"),
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @vitest-environment jsdom
|
||||
*/
|
||||
describe("<FuzzySearchModal />", () => {
|
||||
test("shows all", async () => {
|
||||
const wrapper = createFuzzySearchModal();
|
||||
expect(wrapper.findAll("li").length).toBe(3);
|
||||
});
|
||||
|
||||
test("search for foo", async () => {
|
||||
const wrapper = createFuzzySearchModal();
|
||||
await wrapper.find("input").setValue("foo");
|
||||
expect(wrapper.findAll("li").length).toBe(1);
|
||||
expect(wrapper.find("ul [data-name]").html()).toMatchInlineSnapshot(
|
||||
`"<span data-v-dc2e8c61="" data-name=""><mark>foo</mark> bar</span>"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,36 +1,43 @@
|
||||
<template>
|
||||
<div class="dropdown dropdown-open w-full">
|
||||
<div class="input input-primary flex h-auto items-center">
|
||||
<mdi:magnify class="flex h-8 w-8" />
|
||||
<mdi:magnify class="flex size-8" />
|
||||
<input
|
||||
tabindex="0"
|
||||
class="input input-ghost input-lg flex-1 px-1"
|
||||
ref="input"
|
||||
@keyup.down="selectedIndex = Math.min(selectedIndex + 1, data.length - 1)"
|
||||
@keyup.up="selectedIndex = Math.max(selectedIndex - 1, 0)"
|
||||
@keyup.enter.exact="selected(data[selectedIndex])"
|
||||
@keyup.enter.exact="selected(data[selectedIndex].item)"
|
||||
@keyup.alt.enter="addColumn(data[selectedIndex])"
|
||||
v-model="query"
|
||||
:placeholder="$t('placeholder.search-containers')"
|
||||
/>
|
||||
<mdi:keyboard-esc class="flex" />
|
||||
</div>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box !relative mt-2 w-full bg-base-lighter p-2">
|
||||
<li v-for="(item, index) in data">
|
||||
<ul tabindex="0" class="menu dropdown-content !relative mt-2 w-full rounded-box bg-base-lighter p-2">
|
||||
<li v-for="(result, index) in data">
|
||||
<a
|
||||
class="grid auto-cols-max grid-cols-[min-content,auto] gap-2 py-4"
|
||||
@click.prevent="selected(item)"
|
||||
@click.prevent="selected(result.item)"
|
||||
@mouseenter="selectedIndex = index"
|
||||
:class="index === selectedIndex ? 'focus' : ''"
|
||||
>
|
||||
<div :class="{ 'text-primary': item.state === 'running' }">
|
||||
<div :class="{ 'text-primary': result.item.state === 'running' }">
|
||||
<octicon:container-24 />
|
||||
</div>
|
||||
<div class="truncate">
|
||||
<span class="font-light">{{ item.host }}</span> / {{ item.name }}
|
||||
<template v-if="config.hosts.length > 1">
|
||||
<span class="font-light">{{ result.item.host }}</span> /
|
||||
</template>
|
||||
<span data-name v-html="matchedName(result)"></span>
|
||||
</div>
|
||||
<distance-time :date="item.created" class="ml-auto text-xs font-light" />
|
||||
<a @click.stop.prevent="addColumn(item)" :title="$t('tooltip.pin-column')" class="hover:text-secondary">
|
||||
<distance-time :date="result.item.created" class="text-xs font-light" />
|
||||
<a
|
||||
@click.stop.prevent="addColumn(result.item)"
|
||||
:title="$t('tooltip.pin-column')"
|
||||
class="hover:text-secondary"
|
||||
>
|
||||
<ic:sharp-keyboard-return v-if="index === selectedIndex" />
|
||||
<cil:columns v-else />
|
||||
</a>
|
||||
@@ -42,8 +49,9 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useFuse } from "@vueuse/integrations/useFuse";
|
||||
import { type FuseResultMatch } from "fuse.js";
|
||||
|
||||
const { maxResults: resultLimit = 5 } = defineProps<{
|
||||
const { maxResults = 5 } = defineProps<{
|
||||
maxResults?: number;
|
||||
}>();
|
||||
|
||||
@@ -58,30 +66,33 @@ const store = useContainerStore();
|
||||
const { containers } = storeToRefs(store);
|
||||
|
||||
const list = computed(() => {
|
||||
return containers.value.map(({ id, created, name, state, hostLabel: host }) => {
|
||||
return containers.value.map(({ id, created, name, state, labels, hostLabel: host }) => {
|
||||
return {
|
||||
id,
|
||||
created,
|
||||
name,
|
||||
state,
|
||||
host,
|
||||
labels: Object.entries(labels).map(([_, value]) => value),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const { results } = useFuse(query, list, {
|
||||
fuseOptions: {
|
||||
keys: ["name", "host"],
|
||||
keys: ["name", "host", "labels"],
|
||||
includeScore: true,
|
||||
useExtendedSearch: true,
|
||||
threshold: 0.3,
|
||||
includeMatches: true,
|
||||
},
|
||||
resultLimit,
|
||||
resultLimit: 10,
|
||||
matchAllWhenSearchEmpty: true,
|
||||
});
|
||||
|
||||
const data = computed(() => {
|
||||
return results.value
|
||||
.sort((a, b) => {
|
||||
.toSorted((a, b) => {
|
||||
if (a.score === b.score) {
|
||||
if (a.item.state === "running" && b.item.state !== "running") {
|
||||
return -1;
|
||||
@@ -94,8 +105,7 @@ const data = computed(() => {
|
||||
return 0;
|
||||
}
|
||||
})
|
||||
.map(({ item }) => item)
|
||||
.slice(0, resultLimit);
|
||||
.slice(0, maxResults);
|
||||
});
|
||||
|
||||
watch(query, (data) => {
|
||||
@@ -114,6 +124,28 @@ function addColumn(container: { id: string }) {
|
||||
store.appendActiveContainer(container);
|
||||
close();
|
||||
}
|
||||
|
||||
function matchedName({ item, matches = [] }: { item: { name: string }; matches?: FuseResultMatch[] }) {
|
||||
const matched = matches.find((match) => match.key === "name");
|
||||
if (matched) {
|
||||
const { indices } = matched;
|
||||
const result = [];
|
||||
let lastIndex = 0;
|
||||
for (const [start, end] of indices) {
|
||||
result.push(item.name.slice(lastIndex, start));
|
||||
result.push(`<mark>${item.name.slice(start, end + 1)}</mark>`);
|
||||
lastIndex = end + 1;
|
||||
}
|
||||
result.push(item.name.slice(lastIndex));
|
||||
return result.join("");
|
||||
} else {
|
||||
return item.name;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss"></style>
|
||||
<style scoped lang="postcss">
|
||||
:deep(mark) {
|
||||
@apply bg-transparent text-inherit underline underline-offset-2;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<dropdown class="dropdown-end" @closed="latestTag = latest?.tag ?? config.version">
|
||||
<template #trigger>
|
||||
<mdi:announcement class="size-6 -rotate-12" />
|
||||
<span
|
||||
class="absolute right-px top-0 size-2 rounded-full bg-red"
|
||||
v-if="hasUpdate && latestTag != latest?.tag"
|
||||
></span>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="w-72">
|
||||
<releases />
|
||||
</div>
|
||||
</template>
|
||||
</dropdown>
|
||||
|
||||
<dropdown class="dropdown-end" v-if="config.user">
|
||||
<template #trigger>
|
||||
<img
|
||||
class="size-6 max-w-none rounded-full p-px ring-1 ring-base-content/60"
|
||||
:src="withBase('/api/profile/avatar')"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="p-2">
|
||||
<div class="font-bold">
|
||||
{{ config.user.name }}
|
||||
</div>
|
||||
<div class="text-sm font-light">
|
||||
{{ config.user.email }}
|
||||
</div>
|
||||
</div>
|
||||
<ul class="menu mt-4 p-0">
|
||||
<li v-if="config.authProvider === 'simple'">
|
||||
<button @click.prevent="logout()" class="p-2 text-primary">{{ $t("button.logout") }}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</dropdown>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
async function logout() {
|
||||
await fetch(withBase("/api/token"), {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
location.reload();
|
||||
}
|
||||
|
||||
const { hasUpdate, latest } = useReleases();
|
||||
const latestTag = useProfileStorage("releaseSeen", config.version);
|
||||
</script>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex gap-x-2">
|
||||
<div class="relative flex w-full gap-x-2">
|
||||
<div v-if="showStd">
|
||||
<log-std :std="logEntry.std"></log-std>
|
||||
</div>
|
||||
@@ -22,6 +22,11 @@
|
||||
</ul>
|
||||
<field-list :fields="logEntry.unfilteredMessage" :expanded="expanded" :visible-keys="visibleKeys"></field-list>
|
||||
</div>
|
||||
<log-message-actions
|
||||
class="duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100"
|
||||
:message="JSON.stringify(logEntry.message)"
|
||||
:log-entry="logEntry"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
@@ -45,6 +50,7 @@ const validValues = computed(() => {
|
||||
.text-light {
|
||||
@apply text-base-content/70;
|
||||
}
|
||||
|
||||
.fields {
|
||||
&:hover {
|
||||
&::after {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="inline-flex h-4 w-4" :health="health" v-if="health" :title="health">
|
||||
<div class="inline-flex size-4" :health="health" v-if="health" :title="health">
|
||||
<cil:check-circle v-if="health == 'healthy'" />
|
||||
<cil:x-circle v-else-if="health == 'unhealthy'" />
|
||||
<cil:circle v-else />
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div class="flex flex-1 items-center gap-2 truncate">
|
||||
<container-health :health="container.health" v-if="container.health"></container-health>
|
||||
<div class="flex flex-1 items-center gap-1.5 truncate md:gap-2">
|
||||
<label class="swap swap-rotate">
|
||||
<input type="checkbox" v-model="pinned" />
|
||||
<carbon:star-filled class="swap-on text-secondary" />
|
||||
<carbon:star class="swap-off" />
|
||||
</label>
|
||||
<div class="inline-flex font-mono text-sm">
|
||||
<div v-if="config.hosts.length > 1" class="mobile-hidden font-thin">
|
||||
{{ container.hostLabel }}<span class="mx-2">/</span>
|
||||
@@ -13,24 +17,20 @@
|
||||
{{ container.swarmId }}
|
||||
</div>
|
||||
</div>
|
||||
<container-health :health="container.health" v-if="container.health"></container-health>
|
||||
<tag class="mobile-hidden font-mono" size="small">{{ container.image.replace(/@sha.*/, "") }}</tag>
|
||||
<label class="swap swap-rotate">
|
||||
<input type="checkbox" v-model="pinned" />
|
||||
<carbon:star-filled class="swap-on text-secondary" />
|
||||
<carbon:star class="swap-off" />
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { container } = useContainerContext();
|
||||
const pinned = computed({
|
||||
get: () => pinnedContainers.value.has(container.value.storageKey),
|
||||
get: () => pinnedContainers.value.has(container.value.name),
|
||||
set: (value) => {
|
||||
if (value) {
|
||||
pinnedContainers.value.add(container.value.storageKey);
|
||||
pinnedContainers.value.add(container.value.name);
|
||||
} else {
|
||||
pinnedContainers.value.delete(container.value.storageKey);
|
||||
pinnedContainers.value.delete(container.value.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="alert alert-info mt-8 w-auto text-[1rem] md:mx-auto md:w-1/2"
|
||||
v-if="nextContainer && logEntry.event === 'container-stopped'"
|
||||
>
|
||||
<carbon:information class="h-6 w-6 shrink-0 stroke-current" />
|
||||
<carbon:information class="size-6 shrink-0 stroke-current" />
|
||||
<div>
|
||||
<h3 class="text-lg font-bold">{{ $t("alert.similar-container-found.title") }}</h3>
|
||||
{{ $t("alert.similar-container-found.message", { containerId: nextContainer.id }) }}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<carbon:circle-solid class="w-2.5 text-red" v-if="streamConfig.stderr" />
|
||||
<carbon:circle-solid class="w-2.5 text-blue" v-if="streamConfig.stdout" />
|
||||
</label>
|
||||
<ul tabindex="0" class="menu dropdown-content rounded-box z-50 w-52 bg-base p-1 shadow">
|
||||
<ul tabindex="0" class="menu dropdown-content z-50 w-52 rounded-box bg-base p-1 shadow">
|
||||
<li>
|
||||
<a @click.prevent="clear()">
|
||||
<octicon:trash-24 /> {{ $t("toolbar.clear") }}
|
||||
@@ -30,7 +30,7 @@
|
||||
streamConfig.stderr = true;
|
||||
"
|
||||
>
|
||||
<div class="flex h-4 w-4 gap-0.5">
|
||||
<div class="flex size-4 gap-0.5">
|
||||
<template v-if="streamConfig.stderr && streamConfig.stdout">
|
||||
<carbon:circle-solid class="w-2 text-red" />
|
||||
<carbon:circle-solid class="w-2 text-blue" />
|
||||
@@ -46,7 +46,7 @@
|
||||
streamConfig.stderr = false;
|
||||
"
|
||||
>
|
||||
<div class="flex h-4 w-4 flex-col gap-1">
|
||||
<div class="flex size-4 flex-col gap-1">
|
||||
<carbon:circle-solid class="w-2 text-blue" v-if="!streamConfig.stderr && streamConfig.stdout" />
|
||||
</div>
|
||||
{{ $t("toolbar.show", { std: "STDOUT" }) }}
|
||||
@@ -59,23 +59,63 @@
|
||||
streamConfig.stderr = true;
|
||||
"
|
||||
>
|
||||
<div class="flex h-4 w-4 flex-col gap-1">
|
||||
<div class="flex size-4 flex-col gap-1">
|
||||
<carbon:circle-solid class="w-2 text-red" v-if="streamConfig.stderr && !streamConfig.stdout" />
|
||||
</div>
|
||||
{{ $t("toolbar.show", { std: "STDERR" }) }}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Container Actions (Enabled via config) -->
|
||||
<template v-if="enableActions">
|
||||
<li class="line"></li>
|
||||
<li>
|
||||
<button
|
||||
@click="stop()"
|
||||
:disabled="actionStates.stop || actionStates.restart"
|
||||
v-if="container.state == 'running'"
|
||||
>
|
||||
<carbon:stop-filled-alt /> {{ $t("toolbar.stop") }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="start()"
|
||||
:disabled="actionStates.start || actionStates.restart"
|
||||
v-if="container.state != 'running'"
|
||||
>
|
||||
<carbon:play /> {{ $t("toolbar.start") }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button @click="restart()" :disabled="disableRestart">
|
||||
<carbon:restart
|
||||
:class="{
|
||||
'animate-spin': actionStates.restart,
|
||||
'text-secondary': actionStates.restart,
|
||||
}"
|
||||
/>
|
||||
{{ $t("toolbar.restart") }}
|
||||
</button>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { showSearch } = useSearchFilter();
|
||||
const { base } = config;
|
||||
const { base, enableActions } = config;
|
||||
|
||||
const clear = defineEmit();
|
||||
|
||||
const { container, streamConfig } = useContainerContext();
|
||||
|
||||
// container context is provided in the parent component: <LogContainer>
|
||||
const { actionStates, start, stop, restart } = useContainerActions();
|
||||
|
||||
const disableRestart = computed(() => {
|
||||
return actionStates.stop || actionStates.start || actionStates.restart;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<scrollable-view :scrollable="scrollable" v-if="container">
|
||||
<template #header v-if="showTitle">
|
||||
<div class="mx-2 flex items-center gap-2">
|
||||
<div class="mx-2 flex items-center gap-2 md:ml-4">
|
||||
<container-title @close="$emit('close')" />
|
||||
<container-stat class="ml-auto" />
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { createTestingPinia } from "@pinia/testing";
|
||||
import { mount } from "@vue/test-utils";
|
||||
import { containerContext } from "@/composable/containerContext";
|
||||
import { useSearchFilter } from "@/composable/search";
|
||||
import { settings } from "@/stores/settings";
|
||||
// @ts-ignore
|
||||
import EventSource, { sources } from "eventsourcemock";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { computed, nextTick } from "vue";
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import LogEventSource from "./LogEventSource.vue";
|
||||
import LogViewer from "./LogViewer.vue";
|
||||
import { settings } from "@/stores/settings";
|
||||
import { useSearchFilter } from "@/composables/search";
|
||||
import { vi, describe, expect, beforeEach, test, afterEach } from "vitest";
|
||||
import { computed, nextTick } from "vue";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import { containerContext } from "@/composables/containerContext";
|
||||
|
||||
vi.mock("@/stores/config", () => ({
|
||||
__esModule: true,
|
||||
@@ -68,7 +69,7 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
return mount(LogEventSource, {
|
||||
global: {
|
||||
plugins: [router, createTestingPinia({ createSpy: vi.fn })],
|
||||
plugins: [router, createTestingPinia({ createSpy: vi.fn }), createI18n({})],
|
||||
components: {
|
||||
LogViewer,
|
||||
},
|
||||
@@ -139,19 +140,6 @@ describe("<LogEventSource />", () => {
|
||||
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should render messages with color", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources[sourceUrl].emitOpen();
|
||||
sources[sourceUrl].emitMessage({
|
||||
data: '{"ts":1560336942459,"m":"\\u001b[30mblack\\u001b[37mwhite", "id":1}',
|
||||
});
|
||||
|
||||
vi.runAllTimers();
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.find("ul.events").html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should render messages with html entities", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources[sourceUrl].emitOpen();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :data-level="level" :data-position="position" class="mt-1.5 h-2.5 w-2.5 flex-none rounded-lg"></div>
|
||||
<div :data-level="level" :data-position="position" class="mt-1.5 size-2.5 flex-none rounded-lg"></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { Position } from "@/models/LogEntry";
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="flex gap-2">
|
||||
<div
|
||||
class="flex min-w-[0.98rem] items-start justify-end align-bottom hover:cursor-pointer"
|
||||
v-if="isSupported && message.trim() != ''"
|
||||
:title="t('log_actions.copy_log')"
|
||||
>
|
||||
<span
|
||||
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
||||
@click="copyLogMessageToClipBoard()"
|
||||
>
|
||||
<carbon:copy-file />
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex min-w-[0.98rem] items-start justify-end align-bottom hover:cursor-pointer"
|
||||
:title="t('log_actions.jump_to_context')"
|
||||
v-if="isSearching()"
|
||||
>
|
||||
<a
|
||||
class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700"
|
||||
@click="handleJumpLineSelected($event, logEntry)"
|
||||
:href="`#${logEntry.id}`"
|
||||
>
|
||||
<carbon:search-locate />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { LogEntry, JSONObject } from "@/models/LogEntry";
|
||||
|
||||
const { message, logEntry } = defineProps<{
|
||||
message: string;
|
||||
logEntry: LogEntry<string | JSONObject>;
|
||||
}>();
|
||||
|
||||
const { showToast } = useToast();
|
||||
const { copy, isSupported, copied } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
const { isSearching } = useSearchFilter();
|
||||
const { handleJumpLineSelected } = useLogSearchContext();
|
||||
|
||||
async function copyLogMessageToClipBoard() {
|
||||
await copy(message);
|
||||
|
||||
if (copied.value) {
|
||||
showToast(
|
||||
{
|
||||
title: t("toasts.copied.title"),
|
||||
message: t("toasts.copied.message"),
|
||||
type: "info",
|
||||
},
|
||||
{ expire: 2000 },
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -5,17 +5,9 @@
|
||||
:key="item.id"
|
||||
:data-key="item.id"
|
||||
:class="{ 'border border-secondary': toRaw(item) === toRaw(lastSelectedItem) }"
|
||||
class="group/entry"
|
||||
>
|
||||
<a
|
||||
class="jump-context tooltip-primary tooltip tooltip-right"
|
||||
v-if="isSearching()"
|
||||
data-tip="Jump to Context"
|
||||
@click="handleJumpLineSelected($event, item)"
|
||||
:href="`#${item.id}`"
|
||||
>
|
||||
<ic:sharp-find-in-page />
|
||||
</a>
|
||||
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys.value" />
|
||||
<component :is="item.getComponent()" :log-entry="item" :visible-keys="visibleKeys" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
@@ -32,22 +24,16 @@ const props = defineProps<{
|
||||
|
||||
const { container } = useContainerContext();
|
||||
|
||||
let visibleKeys = persistentVisibleKeys(container);
|
||||
const visibleKeys = persistentVisibleKeys(container);
|
||||
|
||||
const { filteredPayload } = useVisibleFilter(visibleKeys);
|
||||
const { filteredMessages, resetSearch, isSearching } = useSearchFilter();
|
||||
const { filteredMessages } = useSearchFilter();
|
||||
|
||||
const { messages } = toRefs(props);
|
||||
const visible = filteredPayload(messages);
|
||||
const filtered = filteredMessages(visible);
|
||||
|
||||
let lastSelectedItem: LogEntry<string | JSONObject> | undefined = $ref(undefined);
|
||||
|
||||
function handleJumpLineSelected(e: Event, item: LogEntry<string | JSONObject>) {
|
||||
lastSelectedItem = item;
|
||||
resetSearch();
|
||||
}
|
||||
|
||||
const { lastSelectedItem } = useLogSearchContext();
|
||||
const routeHash = useRouteHash();
|
||||
watch(
|
||||
routeHash,
|
||||
@@ -72,13 +58,13 @@ watch(
|
||||
monospace;
|
||||
|
||||
> li {
|
||||
@apply flex break-words px-4 py-1 last:snap-end odd:bg-gray-400/[0.07];
|
||||
@apply flex break-words px-2 py-1 last:snap-end odd:bg-gray-400/[0.07] md:px-4;
|
||||
&:last-child {
|
||||
scroll-margin-block-end: 5rem;
|
||||
}
|
||||
|
||||
.jump-context {
|
||||
@apply mr-2 flex items-center font-sans text-secondary hover:text-secondary-focus;
|
||||
@apply mr-2 flex items-center font-sans text-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,26 @@
|
||||
<template>
|
||||
<div class="flex items-start gap-x-2">
|
||||
<div class="relative flex w-full items-start gap-x-2">
|
||||
<log-std :std="logEntry.std" v-if="showStd" />
|
||||
<log-date :date="logEntry.date" v-if="showTimestamp" />
|
||||
<log-level class="flex" :level="logEntry.level" :position="logEntry.position" />
|
||||
<div class="whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap" v-html="colorize(logEntry.message)"></div>
|
||||
<div
|
||||
class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"
|
||||
v-html="markSearch(logEntry.message)"
|
||||
></div>
|
||||
<log-message-actions
|
||||
class="duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100"
|
||||
:message="decodeXML(logEntry.message)"
|
||||
:log-entry="logEntry"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { SimpleLogEntry } from "@/models/LogEntry";
|
||||
import AnsiConvertor from "ansi-to-html";
|
||||
import { decodeXML } from "entities";
|
||||
|
||||
const ansiConvertor = new AnsiConvertor({ escapeXML: false, fg: "var(--text-color)" });
|
||||
defineProps<{
|
||||
logEntry: SimpleLogEntry;
|
||||
}>();
|
||||
|
||||
const { markSearch } = useSearchFilter();
|
||||
const colorize = (value: string) => markSearch(ansiConvertor.toHtml(value));
|
||||
</script>
|
||||
|
||||
@@ -1,105 +1,105 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render dates with 12 hour style 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
"<ul data-v-2e92daca="" class="events group py-4 medium">
|
||||
<li data-v-2e92daca="" data-key="1" class="group/entry">
|
||||
<div data-v-2e92daca="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
<div data-v-961504e7="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm" size="small">
|
||||
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
|
||||
<div class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"><test>foo bar</test></div>
|
||||
<div class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\"><test>foo bar</test></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render dates with 24 hour style 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
"<ul data-v-2e92daca="" class="events group py-4 medium">
|
||||
<li data-v-2e92daca="" data-key="1" class="group/entry">
|
||||
<div data-v-2e92daca="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42</time></div>
|
||||
<div data-v-961504e7="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm" size="small">
|
||||
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42</time></div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
|
||||
<div class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"><test>foo bar</test></div>
|
||||
<div class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\"><test>foo bar</test></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render messages 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
"<ul data-v-2e92daca="" class="events group py-4 medium">
|
||||
<li data-v-2e92daca="" data-key="1" class="group/entry">
|
||||
<div data-v-2e92daca="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
<div data-v-961504e7="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm" size="small">
|
||||
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\">This is a message.</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render messages with color 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
|
||||
<div class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap">This is a message.</div>
|
||||
<div class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\"><span style=\\"color:#000\\">black<span style=\\"color:#AAA\\">white</span></span></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render messages with filter 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"2\\" class=\\"\\"><a data-v-2e92daca=\\"\\" class=\\"jump-context tooltip-primary tooltip tooltip-right\\" data-tip=\\"Jump to Context\\" href=\\"#2\\"><svg data-v-2e92daca=\\"\\" viewBox=\\"0 0 24 24\\" width=\\"1.2em\\" height=\\"1.2em\\">
|
||||
<path fill=\\"currentColor\\" d=\\"M20 19.59V8l-6-6H4v20l15.57-.02l-4.81-4.81c-.8.52-1.74.83-2.76.83c-2.76 0-5-2.24-5-5s2.24-5 5-5s5 2.24 5 5c0 1.02-.31 1.96-.83 2.75L20 19.59zM9 13c0 1.66 1.34 3 3 3s3-1.34 3-3s-1.34-3-3-3s-3 1.34-3 3z\\"></path>
|
||||
</svg></a>
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
"<ul data-v-2e92daca="" class="events group py-4 medium">
|
||||
<li data-v-2e92daca="" data-key="2" class="group/entry">
|
||||
<div data-v-2e92daca="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
<div data-v-961504e7="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm" size="small">
|
||||
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
|
||||
<div class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"><mark>test</mark> bar</div>
|
||||
<div class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
<div class="flex min-w-[0.98rem] items-start justify-end align-bottom hover:cursor-pointer" title="log_actions.jump_to_context"><a class="rounded bg-slate-800/60 px-1.5 py-1 text-primary hover:bg-slate-700" href="#2"><svg viewBox="0 0 32 32" width="1.2em" height="1.2em">
|
||||
<path fill="currentColor" d="m30 28.586l-4.688-4.688a8.028 8.028 0 1 0-1.415 1.414L28.586 30zM19 25a6 6 0 1 1 6-6a6.007 6.007 0 0 1-6 6M2 12h8v2H2zM2 2h16v2H2zm0 5h16v2H2z"></path>
|
||||
</svg></a></div>
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\"><mark>test</mark> bar</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > render html correctly > should render messages with html entities 1`] = `
|
||||
"<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\">
|
||||
<li data-v-2e92daca=\\"\\" data-key=\\"1\\" class=\\"\\">
|
||||
<!--v-if-->
|
||||
<div data-v-2e92daca=\\"\\" class=\\"flex items-start gap-x-2\\" visible-keys=\\"\\">
|
||||
"<ul data-v-2e92daca="" class="events group py-4 medium">
|
||||
<li data-v-2e92daca="" data-key="1" class="group/entry">
|
||||
<div data-v-2e92daca="" class="relative flex w-full items-start gap-x-2" visible-keys="">
|
||||
<!--v-if-->
|
||||
<div data-v-961504e7=\\"\\" class=\\"inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm\\" size=\\"small\\">
|
||||
<div class=\\"inline-flex gap-2 whitespace-nowrap text-blue\\"><time datetime=\\"2019-06-12T10:55:42.459Z\\" class=\\"mobile-hidden\\">06/12/2019</time><time datetime=\\"2019-06-12T10:55:42.459Z\\">10:55:42 AM</time></div>
|
||||
<div data-v-961504e7="" class="inline-flex items-center justify-center rounded bg-base-lighter px-2 py-[0.2em] text-sm" size="small">
|
||||
<div class="inline-flex gap-2 whitespace-nowrap text-blue"><time datetime="2019-06-12T10:55:42.459Z" class="mobile-hidden">06/12/2019</time><time datetime="2019-06-12T10:55:42.459Z">10:55:42 AM</time></div>
|
||||
</div>
|
||||
<div data-v-e625cddd="" class="mt-1.5 size-2.5 flex-none rounded-lg flex"></div>
|
||||
<div class="whitespace-pre-wrap [word-break:break-word] group-[.disable-wrap]:whitespace-nowrap"><test>foo bar</test></div>
|
||||
<div class="flex gap-2 duration-250 absolute -right-1 opacity-0 transition-opacity delay-150 group-hover/entry:opacity-100">
|
||||
<!--v-if-->
|
||||
<!--v-if-->
|
||||
</div>
|
||||
<div data-v-e625cddd=\\"\\" class=\\"mt-1.5 h-2.5 w-2.5 flex-none rounded-lg flex\\"></div>
|
||||
<div class=\\"whitespace-pre-wrap group-[.disable-wrap]:whitespace-nowrap\\"><test>foo bar</test></div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > renders correctly 1`] = `
|
||||
"<div class=\\"flex min-h-[1px] justify-center\\"><span class=\\"loading loading-bars loading-md mt-4 text-primary\\" style=\\"display: none;\\"></span></div>
|
||||
<ul data-v-2e92daca=\\"\\" class=\\"events group py-4 medium\\"></ul>"
|
||||
"<div class="flex min-h-[1px] justify-center"><span class="loading loading-bars loading-md mt-4 text-primary" style="display: none;"></span></div>
|
||||
<ul data-v-2e92daca="" class="events group py-4 medium"></ul>"
|
||||
`;
|
||||
|
||||
exports[`<LogEventSource /> > should parse messages 1`] = `
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-8 px-4 py-4 md:px-8">
|
||||
<section>
|
||||
<links />
|
||||
</section>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { globalShowPopup } from "@/composables/popup";
|
||||
import { globalShowPopup } from "@/composable/popup";
|
||||
|
||||
let glopbalShow = globalShowPopup();
|
||||
let show = ref(glopbalShow.value);
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<ul class="space-y-4 p-2">
|
||||
<li v-for="release in releases" v-if="releases?.length">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<carbon:warning class="self-center stroke-orange" v-if="release.breaking > 0" />
|
||||
<a :href="release.htmlUrl" class="link-primary text-lg font-bold" target="_blank" rel="noreferrer noopener">
|
||||
{{ release.name }}
|
||||
</a>
|
||||
<span class="ml-1 text-xs"><distance-time :date="new Date(release.createdAt)" /></span>
|
||||
<tag class="ml-auto bg-red px-1 py-1 text-xs" v-if="release.tag === latest?.tag">
|
||||
{{ $t("releases.latest") }}
|
||||
</tag>
|
||||
</div>
|
||||
<div class="text-sm text-base-content/80">
|
||||
{{ summary(release) }}
|
||||
</div>
|
||||
</li>
|
||||
<li v-else>
|
||||
<div class="text-sm text-base-content/80">
|
||||
{{ $t("releases.no_releases") }}
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const { releases, latest } = useReleases();
|
||||
const { t } = useI18n();
|
||||
|
||||
function summary(release: { features: number; bugFixes: number; breaking: number }) {
|
||||
if (release.features > 0 && release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.three_parts", {
|
||||
first: t("releases.breaking", { count: release.breaking }),
|
||||
second: t("releases.features", { count: release.features }),
|
||||
third: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.bugFixes > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.features", { count: release.features }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0 && release.breaking > 0) {
|
||||
return t("releases.two_parts", {
|
||||
first: t("releases.bugFixes", { count: release.bugFixes }),
|
||||
second: t("releases.breaking", { count: release.breaking }),
|
||||
});
|
||||
}
|
||||
|
||||
if (release.features > 0) {
|
||||
return t("releases.features", { count: release.features });
|
||||
}
|
||||
|
||||
if (release.bugFixes > 0) {
|
||||
return t("releases.bugFixes", { count: release.bugFixes });
|
||||
}
|
||||
|
||||
if (release.breaking > 0) {
|
||||
return t("releases.breaking", { count: release.breaking });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -8,7 +8,7 @@
|
||||
</header>
|
||||
<main :data-scrolling="scrollable ? true : undefined" class="snap-y overflow-auto">
|
||||
<div class="invisible mr-28 text-right md:visible" v-show="paused">
|
||||
<scroll-progress :indeterminate="loading" :auto-hide="!loading" class="z-2 !fixed top-16" />
|
||||
<scroll-progress :indeterminate="loading" :auto-hide="!loading" class="!fixed top-16 z-10" />
|
||||
</div>
|
||||
<div ref="scrollableContent">
|
||||
<slot :setLoading="setLoading"></slot>
|
||||
@@ -20,8 +20,8 @@
|
||||
<div class="mr-16 text-right">
|
||||
<transition name="fade">
|
||||
<button
|
||||
class="fixed bottom-8 rounded bg-primary p-3 text-primary-content shadow transition-colors hover:bg-primary-focus"
|
||||
:class="hasMore ? 'animate-bounce-fast bg-secondary text-secondary-content hover:bg-secondary-focus' : ''"
|
||||
class="btn btn-primary fixed bottom-8 rounded p-3 text-primary-content shadow transition-colors"
|
||||
:class="hasMore ? 'btn-secondary animate-bounce-fast text-secondary-content' : ''"
|
||||
@click="scrollToBottom()"
|
||||
v-show="paused"
|
||||
>
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
<template>
|
||||
<div
|
||||
class="fixed -right-px -top-px z-10 flex w-96 items-center gap-4 rounded-bl border border-secondary/20 bg-base-darker p-4 shadow"
|
||||
v-show="showSearch"
|
||||
v-if="search"
|
||||
>
|
||||
<div class="input input-primary flex h-auto items-center">
|
||||
<mdi:magnify />
|
||||
<input
|
||||
class="input flex-1"
|
||||
type="text"
|
||||
placeholder="Find / RegEx"
|
||||
ref="input"
|
||||
v-model="searchFilter"
|
||||
@keyup.esc="resetSearch()"
|
||||
/>
|
||||
<transition name="slide">
|
||||
<div class="fixed right-px top-12 z-10 flex items-center p-4" v-show="showSearch" v-if="search">
|
||||
<div class="input input-primary flex h-auto items-center !shadow-lg">
|
||||
<mdi:magnify />
|
||||
<input
|
||||
class="input input-ghost w-72 flex-1"
|
||||
type="text"
|
||||
placeholder="Find / RegEx"
|
||||
ref="input"
|
||||
v-model="searchFilter"
|
||||
@keyup.esc="resetSearch()"
|
||||
/>
|
||||
<a class="btn btn-circle btn-xs" @click="resetSearch()"> <mdi:close /></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-circle btn-xs" @click="resetSearch()"> <mdi:close /></a>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -32,5 +29,31 @@ onKeyStroke("f", (e) => {
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
onKeyStroke(
|
||||
"f",
|
||||
(e) => {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.stopPropagation();
|
||||
resetSearch();
|
||||
}
|
||||
},
|
||||
{ target: input.value },
|
||||
);
|
||||
});
|
||||
|
||||
onUnmounted(() => resetSearch());
|
||||
</script>
|
||||
|
||||
<style lang="postcss" scoped>
|
||||
.slide-enter-active,
|
||||
.slide-leave-active {
|
||||
transition: all 200ms cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.slide-enter-from,
|
||||
.slide-leave-to {
|
||||
transform: translateY(-150px);
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<div class="truncate">
|
||||
{{ item.name }}<span class="font-light opacity-70" v-if="item.isSwarm">{{ item.swarmId }}</span>
|
||||
</div>
|
||||
<container-health :health="item.health"></container-health>
|
||||
<span
|
||||
class="pin"
|
||||
@click.stop.prevent="store.appendActiveContainer(item)"
|
||||
@@ -44,8 +45,6 @@
|
||||
>
|
||||
<cil:columns />
|
||||
</span>
|
||||
|
||||
<container-health :health="item.health"></container-health>
|
||||
</router-link>
|
||||
<template #content>
|
||||
<container-popup :container="item"></container-popup>
|
||||
@@ -63,7 +62,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Container } from "@/models/Container";
|
||||
import { sessionHost } from "@/composables/storage";
|
||||
import { sessionHost } from "@/composable/storage";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -93,7 +92,7 @@ const sortedContainers = computed(() =>
|
||||
const groupedContainers = computed(() =>
|
||||
sortedContainers.value.reduce(
|
||||
(acc, item) => {
|
||||
if (debouncedIds.value.has(item.storageKey)) {
|
||||
if (debouncedIds.value.has(item.name)) {
|
||||
acc.pinned.push(item);
|
||||
} else {
|
||||
acc.unpinned.push(item);
|
||||
@@ -145,7 +144,7 @@ const activeContainersById = computed(() =>
|
||||
@apply text-[0.95rem];
|
||||
}
|
||||
.containers a {
|
||||
@apply auto-cols-[auto_max-content];
|
||||
@apply auto-cols-[auto_max-content_max-content];
|
||||
.pin {
|
||||
display: none;
|
||||
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
>
|
||||
<mdi:cog />
|
||||
</router-link>
|
||||
<a :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured" class="btn btn-circle btn-sm">
|
||||
<mdi:logout />
|
||||
</a>
|
||||
</div>
|
||||
<a
|
||||
class="input input-sm mt-4 inline-flex cursor-pointer items-center gap-2 font-light hover:border-primary"
|
||||
@@ -40,5 +37,5 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { base, secured, hostname } = config;
|
||||
const { hostname } = config;
|
||||
</script>
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import formatDistanceToNow from "date-fns/formatDistanceToNow";
|
||||
import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict";
|
||||
import { formatDistanceToNow } from "date-fns/formatDistanceToNow";
|
||||
import { formatDistanceToNowStrict } from "date-fns/formatDistanceToNowStrict";
|
||||
|
||||
const {
|
||||
date,
|
||||
|
||||
@@ -1,44 +1,25 @@
|
||||
<template>
|
||||
<details class="dropdown" ref="details" v-on-click-outside="close">
|
||||
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
|
||||
<slot name="trigger"> {{ values[modelValue] ?? defaultLabel }} <carbon:caret-down /></slot>
|
||||
</summary>
|
||||
<ul class="menu dropdown-content rounded-box z-50 w-52 bg-base p-2 shadow">
|
||||
<slot>
|
||||
<li v-for="item in options">
|
||||
<a @click="modelValue = item.value">
|
||||
<mdi:check class="w-4" v-if="modelValue == item.value" />
|
||||
<div v-else class="w-4"></div>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
</slot>
|
||||
</ul>
|
||||
</details>
|
||||
<div class="dropdown">
|
||||
<label tabindex="0" class="btn btn-circle btn-sm" @mousedown="checkAndCloseDropDown($event)" @blur="closed()">
|
||||
<slot name="trigger"></slot>
|
||||
</label>
|
||||
<div
|
||||
tabindex="0"
|
||||
class="min-w-52 dropdown-content rounded-box z-50 mt-1 border border-base-content/20 bg-base p-2 shadow"
|
||||
>
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { vOnClickOutside } from "@vueuse/components";
|
||||
type DropdownItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
const { options = [], defaultLabel = "" } = defineProps<{ options?: DropdownItem[]; defaultLabel?: string }>();
|
||||
const { modelValue } = defineModels<{
|
||||
modelValue: string;
|
||||
}>();
|
||||
|
||||
const values = computed(() =>
|
||||
options.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.value] = curr.label;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
);
|
||||
|
||||
const details = ref<HTMLElement | null>(null);
|
||||
const close = () => details.value?.removeAttribute("open");
|
||||
watch(modelValue, () => close());
|
||||
<script setup lang="ts">
|
||||
const closed = defineEmit();
|
||||
function checkAndCloseDropDown(e: MouseEvent) {
|
||||
const target = e.currentTarget as HTMLElement;
|
||||
if (target?.matches(":focus")) {
|
||||
setTimeout(() => target.blur(), 0);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<details class="dropdown" ref="details" v-on-click-outside="close">
|
||||
<summary class="btn btn-primary flex-nowrap" v-bind="$attrs">
|
||||
<slot name="trigger"> {{ values[modelValue] ?? defaultLabel }} <carbon:caret-down /></slot>
|
||||
</summary>
|
||||
<ul class="menu dropdown-content rounded-box z-50 mt-1 w-52 border border-base-content/20 bg-base p-2 shadow">
|
||||
<slot>
|
||||
<li v-for="item in options">
|
||||
<a @click="modelValue = item.value">
|
||||
<mdi:check class="w-4" v-if="modelValue == item.value" />
|
||||
<div v-else class="w-4"></div>
|
||||
{{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
</slot>
|
||||
</ul>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { vOnClickOutside } from "@vueuse/components";
|
||||
type DropdownItem = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
const { options = [], defaultLabel = "" } = defineProps<{ options?: DropdownItem[]; defaultLabel?: string }>();
|
||||
const { modelValue } = defineModels<{
|
||||
modelValue: string;
|
||||
}>();
|
||||
|
||||
const values = computed(() =>
|
||||
options.reduce(
|
||||
(acc, curr) => {
|
||||
acc[curr.value] = curr.label;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
),
|
||||
);
|
||||
|
||||
const details = ref<HTMLElement | null>(null);
|
||||
const close = () => details.value?.removeAttribute("open");
|
||||
watch(modelValue, () => close());
|
||||
</script>
|
||||
@@ -4,8 +4,8 @@
|
||||
<carbon:mac-shift />
|
||||
</template>
|
||||
<template v-if="modifiers.includes('meta')">
|
||||
<ph:command v-if="isMac" class="h-4 w-4" />
|
||||
<ph:control-bold v-else class="h-4 w-4" />
|
||||
<ph:command v-if="isMac" class="size-4" />
|
||||
<ph:control-bold v-else class="size-4" />
|
||||
</template>
|
||||
<kbd class="uppercase">{{ char }}</kbd>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<a class="btn btn-circle flex" @click="$emit('search')" :title="$t('tooltip.search')">
|
||||
<mdi:magnify class="h-5 w-5" />
|
||||
<mdi:magnify class="size-5" />
|
||||
</a>
|
||||
<label class="btn btn-circle swap swap-rotate" data-testid="hamburger">
|
||||
<input type="checkbox" v-model="show" />
|
||||
@@ -20,9 +20,9 @@
|
||||
</div>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-show="show">
|
||||
<div v-show="show" class="h-[calc(100vh-60px)] overflow-auto">
|
||||
<div class="mt-4 flex items-center justify-center gap-2">
|
||||
<dropdown
|
||||
<dropdown-menu
|
||||
v-model="sessionHost"
|
||||
:options="hosts"
|
||||
defaultLabel="Hosts"
|
||||
@@ -32,14 +32,11 @@
|
||||
<router-link :to="{ name: 'settings' }" class="btn btn-outline btn-sm">
|
||||
<mdi:cog /> {{ $t("button.settings") }}
|
||||
</router-link>
|
||||
<a class="btn btn-outline btn-sm" :href="`${base}/logout`" :title="$t('button.logout')" v-if="secured">
|
||||
<mdi:logout /> {{ $t("button.logout") }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<ul class="menu">
|
||||
<li class="menu-title">{{ $t("label.containers") }}</li>
|
||||
<li v-for="item in sortedContainers" :key="item.id">
|
||||
<li v-for="item in sortedContainers" :key="item.id" :class="item.state">
|
||||
<router-link
|
||||
:to="{ name: 'container-id', params: { id: item.id } }"
|
||||
active-class="active-primary"
|
||||
@@ -56,8 +53,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const { base, secured } = config;
|
||||
import { sessionHost } from "@/composables/storage";
|
||||
import { sessionHost } from "@/composable/storage";
|
||||
const store = useContainerStore();
|
||||
const route = useRoute();
|
||||
const { visibleContainers } = storeToRefs(store);
|
||||
@@ -85,6 +81,10 @@ const sortedContainers = computed(() =>
|
||||
const hosts = computed(() => config.hosts.map(({ id, name }) => ({ value: id, label: name })));
|
||||
</script>
|
||||
<style scoped lang="postcss">
|
||||
li.exited {
|
||||
@apply opacity-50;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
@apply transition-opacity;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<label class="label inline-flex cursor-pointer gap-4 font-normal">
|
||||
<input type="checkbox" class="toggle toggle-primary hover:bg-primary-focus" v-model="modelValue" />
|
||||
<input type="checkbox" class="toggle toggle-primary" v-model="modelValue" />
|
||||
<slot />
|
||||
</label>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
type ContainerActions = "start" | "stop" | "restart";
|
||||
export const useContainerActions = () => {
|
||||
const { container } = useContainerContext();
|
||||
const { showToast } = useToast();
|
||||
|
||||
const actionStates = reactive({
|
||||
stop: false,
|
||||
restart: false,
|
||||
start: false,
|
||||
});
|
||||
|
||||
async function actionHandler(action: ContainerActions) {
|
||||
const actionUrl = `/api/actions/${action}/${container.value.host}/${container.value.id}`;
|
||||
|
||||
const errors = {
|
||||
404: "container not found",
|
||||
500: "unable to complete action",
|
||||
400: "invalid action",
|
||||
} as Record<number, string>;
|
||||
|
||||
const defaultError = "something went wrong";
|
||||
const toastTitle = "Action Failed";
|
||||
|
||||
actionStates[action] = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(withBase(actionUrl), { method: "POST" });
|
||||
if (!response.ok) {
|
||||
const message = errors[response.status] ?? defaultError;
|
||||
showToast({ type: "error", message, title: toastTitle });
|
||||
}
|
||||
} catch (error) {
|
||||
showToast({ type: "error", message: defaultError, title: toastTitle });
|
||||
}
|
||||
|
||||
actionStates[action] = false;
|
||||
}
|
||||
|
||||
return {
|
||||
actionStates,
|
||||
start: () => actionHandler("start"),
|
||||
stop: () => actionHandler("stop"),
|
||||
restart: () => actionHandler("restart"),
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { JSONObject, LogEntry } from "@/models/LogEntry";
|
||||
|
||||
const lastSelectedItem = ref<LogEntry<string | JSONObject> | undefined>(undefined);
|
||||
export const useLogSearchContext = () => {
|
||||
const { resetSearch } = useSearchFilter();
|
||||
|
||||
function handleJumpLineSelected(e: Event, item: LogEntry<string | JSONObject>) {
|
||||
lastSelectedItem.value = item;
|
||||
resetSearch();
|
||||
}
|
||||
|
||||
return {
|
||||
lastSelectedItem,
|
||||
handleJumpLineSelected,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Profile } from "@/stores/config";
|
||||
|
||||
export function useProfileStorage<K extends keyof Profile>(key: K, defaultValue: NonNullable<Profile[K]>) {
|
||||
const storageKey = "DOZZLE_" + key.toUpperCase();
|
||||
const storage = useStorage<NonNullable<Profile[K]>>(storageKey, defaultValue, undefined, {
|
||||
writeDefaults: false,
|
||||
mergeDefaults: true,
|
||||
});
|
||||
|
||||
if (config.profile?.[key]) {
|
||||
if (storage.value instanceof Set && config.profile[key] instanceof Array) {
|
||||
storage.value = new Set([...(config.profile[key] as Iterable<any>)]) as unknown as NonNullable<Profile[K]>;
|
||||
} else if (config.profile[key] instanceof Array) {
|
||||
storage.value = config.profile[key] as NonNullable<Profile[K]>;
|
||||
} else if (config.profile[key] instanceof Object) {
|
||||
Object.assign(storage.value, config.profile[key]);
|
||||
} else {
|
||||
storage.value = config.profile[key] as NonNullable<Profile[K]>;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.user) {
|
||||
watch(
|
||||
storage,
|
||||
(value) => {
|
||||
fetch(withBase("/api/profile"), {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify({ [key]: value }, (_, value) => (value instanceof Set ? [...value] : value)),
|
||||
});
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { Container } from "@/models/Container";
|
||||
|
||||
const DOZZLE_HOST = "DOZZLE_HOST";
|
||||
export const sessionHost = useSessionStorage<string | null>(DOZZLE_HOST, null);
|
||||
|
||||
if (config.hosts.length === 1 && !sessionHost.value) {
|
||||
sessionHost.value = config.hosts[0].id;
|
||||
}
|
||||
|
||||
export function persistentVisibleKeys(container: Ref<Container>): Ref<string[][]> {
|
||||
const storage = useProfileStorage("visibleKeys", {});
|
||||
return computed(() => {
|
||||
if (!(container.value.storageKey in storage.value)) {
|
||||
// Returning a temporary ref here to avoid writing an empty array to storage
|
||||
const visibleKeys = ref<string[][]>([]);
|
||||
watchOnce(visibleKeys, () => (storage.value[container.value.storageKey] = visibleKeys.value), { deep: true });
|
||||
return visibleKeys.value;
|
||||
}
|
||||
|
||||
return storage.value[container.value.storageKey];
|
||||
});
|
||||
}
|
||||
|
||||
export const pinnedContainers = useProfileStorage("pinned", new Set<string>());
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComplexLogEntry, type JSONObject, type LogEntry } from "@/models/LogEntry";
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
|
||||
export function useVisibleFilter(visibleKeys: ComputedRef<Ref<string[][]>>) {
|
||||
export function useVisibleFilter(visibleKeys: Ref<string[][]>) {
|
||||
function filteredPayload(messages: Ref<LogEntry<string | JSONObject>[]>) {
|
||||
return computed(() => {
|
||||
return messages.value.map((d) => {
|
||||
if (d instanceof ComplexLogEntry) {
|
||||
return ComplexLogEntry.fromLogEvent(d, visibleKeys.value);
|
||||
return ComplexLogEntry.fromLogEvent(d, visibleKeys);
|
||||
} else {
|
||||
return d;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { Container } from "@/models/Container";
|
||||
|
||||
const DOZZLE_HOST = "DOZZLE_HOST";
|
||||
export const sessionHost = useSessionStorage<string | null>(DOZZLE_HOST, null);
|
||||
|
||||
if (config.hosts.length === 1 && !sessionHost.value) {
|
||||
sessionHost.value = config.hosts[0].id;
|
||||
}
|
||||
|
||||
export function persistentVisibleKeys(container: Ref<Container>) {
|
||||
return computed(() => useStorage(container.value.storageKey, []));
|
||||
}
|
||||
|
||||
const DOZZLE_PINNED_CONTAINERS = "DOZZLE_PINNED_CONTAINERS";
|
||||
export const pinnedContainers = useStorage(DOZZLE_PINNED_CONTAINERS, new Set<string>());
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="!authorizationNeeded">
|
||||
<div>
|
||||
<mobile-menu v-if="isMobile" @search="showFuzzySearch"></mobile-menu>
|
||||
<splitpanes @resized="onResized($event)">
|
||||
<pane min-size="10" :size="menuWidth" v-if="!isMobile && !collapseNav">
|
||||
@@ -25,12 +25,12 @@
|
||||
</pane>
|
||||
</splitpanes>
|
||||
<label
|
||||
class="btn btn-circle swap swap-rotate fixed bottom-8 left-4"
|
||||
:class="{ '!-left-3': collapseNav }"
|
||||
class="btn btn-circle swap btn-neutral swap-rotate fixed -left-12 bottom-4 w-16 transition-all hover:-left-4"
|
||||
:class="{ '!-left-6': collapseNav }"
|
||||
v-if="!isMobile"
|
||||
>
|
||||
<input type="checkbox" v-model="collapseNav" />
|
||||
<mdi:chevron-right class="swap-on text-secondary" />
|
||||
<mdi:chevron-right class="swap-on" />
|
||||
<mdi:chevron-left class="swap-off" />
|
||||
</label>
|
||||
</div>
|
||||
@@ -47,13 +47,18 @@
|
||||
class="alert max-w-xl"
|
||||
v-for="toast in toasts"
|
||||
:key="toast.id"
|
||||
:class="{ 'alert-error': toast.type === 'error', 'alert-info': toast.type === 'info' }"
|
||||
:class="{
|
||||
'alert-error': toast.type === 'error',
|
||||
'alert-info': toast.type === 'info',
|
||||
'alert-warning': toast.type === 'warning',
|
||||
}"
|
||||
>
|
||||
<carbon:information class="h-6 w-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
|
||||
<carbon:warning class="h-6 w-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
|
||||
<carbon:information class="size-6 shrink-0 stroke-current" v-if="toast.type === 'info'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'error'" />
|
||||
<carbon:warning class="size-6 shrink-0 stroke-current" v-else-if="toast.type === 'warning'" />
|
||||
<div>
|
||||
<h3 class="text-lg font-bold" v-if="toast.title">{{ toast.title }}</h3>
|
||||
{{ toast.message }}
|
||||
<div v-html="toast.message" class="[&>a]:underline"></div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-circle btn-xs" @click="removeToast(toast.id)"><mdi:close /></button>
|
||||
@@ -66,7 +71,6 @@
|
||||
// @ts-ignore - splitpanes types are not available
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
import { collapseNav } from "@/stores/settings";
|
||||
const { authorizationNeeded } = config;
|
||||
|
||||
const containerStore = useContainerStore();
|
||||
const { activeContainers } = storeToRefs(containerStore);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="hero min-h-screen">
|
||||
<div class="hero min-h-dvh">
|
||||
<div class="hero-content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
@import "splitpanes/dist/splitpanes.css";
|
||||
|
||||
@define-mixin light {
|
||||
--base-lighter-color: 0 0% 100%;
|
||||
--base-color: 0 0% 97%;
|
||||
--base-darker-color: 0 0% 90%;
|
||||
--base-content-color: 0 0% 21%;
|
||||
--primary-color: 171 100% 41%;
|
||||
--primary-focus-color: 171 100% 31%;
|
||||
--secondary-color: 34 95% 53%;
|
||||
--secondary-focus-color: 34 95% 43%;
|
||||
--base-lighter-color: 100% 0 0;
|
||||
--base-color: 97% 0 0;
|
||||
--base-darker-color: 90% 0 0;
|
||||
--base-content-color: 33% 0 0;
|
||||
--primary-color: 79.96% 0.143 176.65;
|
||||
--secondary-color: 82.67% 0.15380739746702807 77.82;
|
||||
}
|
||||
|
||||
@define-mixin dark {
|
||||
--base-lighter-color: 0 0% 14%;
|
||||
--base-color: 0 0% 7%;
|
||||
--base-darker-color: 0 0% 4%;
|
||||
--base-content-color: 0 0% 86%;
|
||||
--primary-color: 171 100% 41%;
|
||||
--primary-focus-color: 171 100% 31%;
|
||||
--secondary-color: 44 100% 77%;
|
||||
--secondary-focus-color: 44 100% 67%;
|
||||
--base-lighter-color: 25% 0 0;
|
||||
--base-color: 18% 0 0;
|
||||
--base-darker-color: 11% 0 0;
|
||||
--base-content-color: 89.23% 0 0;
|
||||
--primary-color: 70.96% 0.143 176.65;
|
||||
--secondary-color: 91.36% 0.11098 90.1243;
|
||||
}
|
||||
|
||||
@tailwind base;
|
||||
@@ -29,19 +25,20 @@
|
||||
@layer base {
|
||||
:root {
|
||||
--p: var(--primary-color);
|
||||
--pf: var(--primary-focus-color);
|
||||
--pc: 0 0% 100%;
|
||||
--pc: 89.23% 0;
|
||||
--s: var(--secondary-color);
|
||||
--sf: var(--secondary-focus-color);
|
||||
--sc: 0 0% 4%;
|
||||
--sc: 14.57% 0 0;
|
||||
--b1: var(--base-lighter-color);
|
||||
--b2: var(--base-color);
|
||||
--b3: var(--base-darker-color);
|
||||
--bc: var(--base-content-color);
|
||||
--in: 207 90% 54%;
|
||||
--inc: 207 90% 94%;
|
||||
--er: 4 90% 58%;
|
||||
--erc: 4 90% 98%;
|
||||
--in: 65% 0.171 249.5;
|
||||
--inc: 100% 0 0;
|
||||
--er: 64% 0.218 28.85;
|
||||
--erc: 100% 0 0;
|
||||
--wa: 70% 0.186 48.13;
|
||||
--wac: 100% 0 0;
|
||||
--n: var(--base-lighter-color);
|
||||
}
|
||||
html[data-theme="dark"] {
|
||||
@mixin dark;
|
||||
@@ -77,7 +74,7 @@
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
mark {
|
||||
.events mark {
|
||||
@apply inline-block rounded-sm bg-secondary;
|
||||
animation: pops 200ms ease-out;
|
||||
}
|
||||
@@ -94,7 +91,10 @@
|
||||
|
||||
@layer components {
|
||||
.input {
|
||||
@apply focus:outline-none;
|
||||
@apply !outline-none;
|
||||
&.input-ghost {
|
||||
@apply focus:border-transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -118,4 +118,8 @@
|
||||
--nc: var(--pc);
|
||||
@apply active;
|
||||
}
|
||||
|
||||
.link-primary {
|
||||
@apply underline-offset-4 hover:underline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// import "./styles.scss";
|
||||
import "./main.css";
|
||||
import { createApp, App as VueApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
@@ -18,7 +18,7 @@ describe("Container", () => {
|
||||
];
|
||||
|
||||
test.each(names)("name %s should be %s and %s", (name, expectedName, expectedSwarmId) => {
|
||||
const c = new Container("id", new Date(), "image", name!, "command", "host", "status", "created");
|
||||
const c = new Container("id", new Date(), "image", name!, "command", "host", {}, "status", "created");
|
||||
expect(c.name).toBe(expectedName);
|
||||
expect(c.swarmId).toBe(expectedSwarmId);
|
||||
});
|
||||
|
||||
@@ -31,6 +31,7 @@ export class Container {
|
||||
public readonly name: string,
|
||||
public readonly command: string,
|
||||
public readonly host: string,
|
||||
public readonly labels = {} as Record<string, string>,
|
||||
public status: string,
|
||||
public state: ContainerState,
|
||||
public health?: ContainerHealth,
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<template>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<p class="py-6 text-2xl font-bold">{{ $t("error.page-not-found") }}</p>
|
||||
<page-with-links>
|
||||
<div class="hero min-h-screen bg-base-200">
|
||||
<div class="hero-content text-center">
|
||||
<div class="max-w-md">
|
||||
<p class="py-6 text-2xl font-bold">{{ $t("error.page-not-found") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</page-with-links>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<template>
|
||||
<div class="flex flex-col gap-16 px-4 pt-8 md:px-8">
|
||||
<section v-if="config.user">
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<div v-if="config.authProvider === 'simple'">
|
||||
<button @click.prevent="logout()" class="link-primary">{{ $t("button.logout") }}</button>
|
||||
</div>
|
||||
<div>
|
||||
{{ config.user.name ? config.user.name : config.user.email }}
|
||||
</div>
|
||||
<img class="h-10 w-10 rounded-full p-1 ring-2 ring-base-content/50" :src="config.user.avatar" />
|
||||
</div>
|
||||
</section>
|
||||
<page-with-links class="gap-16">
|
||||
<section>
|
||||
<div class="stats grid bg-base-lighter shadow">
|
||||
<div class="stat">
|
||||
@@ -36,7 +25,7 @@
|
||||
<section>
|
||||
<container-table :containers="runningContainers"></container-table>
|
||||
</section>
|
||||
</div>
|
||||
</page-with-links>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -76,14 +65,6 @@ watchEffect(() => {
|
||||
setTitle(t("title.dashboard", { count: runningContainers.length }));
|
||||
}
|
||||
});
|
||||
|
||||
async function logout() {
|
||||
await fetch(withBase("/api/token"), {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
location.reload();
|
||||
}
|
||||
</script>
|
||||
<style lang="postcss" scoped>
|
||||
:deep(tr td) {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="mt-10 flex flex-col gap-8 px-4 md:px-8">
|
||||
<page-with-links>
|
||||
<section>
|
||||
<div class="has-underline">
|
||||
<h2>{{ $t("settings.about") }}</h2>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span v-html="$t('settings.using-version', { version: currentVersion })"></span>
|
||||
<span v-html="$t('settings.using-version', { version: config.version })"></span>
|
||||
<div
|
||||
v-if="hasUpdate"
|
||||
v-html="$t('settings.update-available', { nextVersion: nextRelease.name, href: nextRelease.html_url })"
|
||||
v-html="$t('settings.update-available', { nextVersion: latest?.name, href: latest?.htmlUrl })"
|
||||
></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<dropdown
|
||||
<dropdown-menu
|
||||
v-model="hourStyle"
|
||||
:options="[
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
@@ -45,7 +45,7 @@
|
||||
{{ $t("settings.12-24-format") }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<dropdown
|
||||
<dropdown-menu
|
||||
v-model="size"
|
||||
:options="[
|
||||
{ label: 'Small', value: 'small' },
|
||||
@@ -56,7 +56,7 @@
|
||||
{{ $t("settings.font-size") }}
|
||||
</div>
|
||||
<div class="flex items-center gap-6">
|
||||
<dropdown
|
||||
<dropdown-menu
|
||||
v-model="lightTheme"
|
||||
:options="[
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
@@ -85,50 +85,27 @@
|
||||
<toggle v-model="automaticRedirect">{{ $t("settings.automatic-redirect") }}</toggle>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</page-with-links>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
search,
|
||||
lightTheme,
|
||||
smallerScrollbars,
|
||||
showTimestamp,
|
||||
showStd,
|
||||
hourStyle,
|
||||
showAllContainers,
|
||||
size,
|
||||
softWrap,
|
||||
automaticRedirect,
|
||||
hourStyle,
|
||||
lightTheme,
|
||||
search,
|
||||
showAllContainers,
|
||||
showStd,
|
||||
showTimestamp,
|
||||
size,
|
||||
smallerScrollbars,
|
||||
softWrap,
|
||||
} from "@/stores/settings";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
setTitle(t("title.settings"));
|
||||
|
||||
const currentVersion = config.version;
|
||||
let nextRelease = $ref({ html_url: "", name: "" });
|
||||
let hasUpdate = $ref(false);
|
||||
|
||||
async function fetchNextRelease() {
|
||||
if (!["dev", "master"].includes(currentVersion)) {
|
||||
const response = await fetch("https://api.github.com/repos/amir20/dozzle/releases/latest");
|
||||
if (response.ok) {
|
||||
const release = await response.json();
|
||||
hasUpdate =
|
||||
release.tag_name.slice(1).localeCompare(currentVersion, undefined, { numeric: true, sensitivity: "base" }) > 0;
|
||||
nextRelease = release;
|
||||
}
|
||||
} else {
|
||||
hasUpdate = true;
|
||||
nextRelease = {
|
||||
html_url: "",
|
||||
name: "master",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fetchNextRelease();
|
||||
const { latest, hasUpdate } = useReleases();
|
||||
</script>
|
||||
<style lang="postcss" scoped>
|
||||
.has-underline {
|
||||
|
||||
@@ -2,22 +2,27 @@ import { type Settings } from "@/stores/settings";
|
||||
|
||||
const text = document.querySelector("script#config__json")?.textContent || "{}";
|
||||
|
||||
interface Config {
|
||||
export interface Config {
|
||||
version: string;
|
||||
base: string;
|
||||
authorizationNeeded: boolean;
|
||||
secured: boolean;
|
||||
maxLogs: number;
|
||||
hostname: string;
|
||||
hosts: { name: string; id: string }[];
|
||||
authProvider: "simple" | "none" | "forward-proxy";
|
||||
enableActions: boolean;
|
||||
user?: {
|
||||
username: string;
|
||||
email: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
};
|
||||
serverSettings?: Settings;
|
||||
profile?: Profile;
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
settings?: Settings;
|
||||
pinned?: Set<string>;
|
||||
visibleKeys?: { [key: string]: string[][] };
|
||||
releaseSeen?: string;
|
||||
}
|
||||
|
||||
const pageConfig = JSON.parse(text);
|
||||
|
||||
@@ -124,6 +124,7 @@ export const useContainerStore = defineStore("container", () => {
|
||||
c.name,
|
||||
c.command,
|
||||
c.host,
|
||||
c.labels,
|
||||
c.status,
|
||||
c.state,
|
||||
c.health,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
const { data: releases } = useFetch(withBase("/api/releases")).get().json<
|
||||
{
|
||||
name: string;
|
||||
mentionsCount: number;
|
||||
createdAt: string;
|
||||
body: string;
|
||||
tag: string;
|
||||
htmlUrl: string;
|
||||
latest: boolean;
|
||||
features: number;
|
||||
bugFixes: number;
|
||||
breaking: number;
|
||||
}[]
|
||||
>();
|
||||
|
||||
const hasUpdate = computed(() => {
|
||||
if (!releases.value?.length) return false;
|
||||
return releases.value[0].tag !== config.version;
|
||||
});
|
||||
|
||||
const latest = computed(() => releases.value?.find((release) => release.latest));
|
||||
|
||||
export function useReleases() {
|
||||
return {
|
||||
hasUpdate,
|
||||
latest,
|
||||
releases,
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { toRefs } from "@vueuse/core";
|
||||
const DOZZLE_SETTINGS_KEY = "DOZZLE_SETTINGS";
|
||||
|
||||
export type Settings = {
|
||||
search: boolean;
|
||||
@@ -30,17 +29,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
automaticRedirect: true,
|
||||
};
|
||||
|
||||
export const settings = useStorage(DOZZLE_SETTINGS_KEY, DEFAULT_SETTINGS);
|
||||
settings.value = { ...DEFAULT_SETTINGS, ...settings.value, ...config.serverSettings };
|
||||
|
||||
if (config.user) {
|
||||
watch(settings, (value) => {
|
||||
fetch(withBase("/api/profile/settings"), {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(value),
|
||||
});
|
||||
});
|
||||
}
|
||||
export const settings = useProfileStorage("settings", DEFAULT_SETTINGS);
|
||||
|
||||
export const {
|
||||
collapseNav,
|
||||
|
||||
@@ -14,6 +14,7 @@ export type ContainerJson = {
|
||||
readonly status: string;
|
||||
readonly state: ContainerState;
|
||||
readonly host: string;
|
||||
readonly labels: Record<string, string>;
|
||||
readonly health?: ContainerHealth;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,17 +12,14 @@ services:
|
||||
- 8080:8080
|
||||
build:
|
||||
context: .
|
||||
auth:
|
||||
container_name: auth
|
||||
simple-auth:
|
||||
container_name: simple-auth
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./e2e/data:/data
|
||||
environment:
|
||||
- DOZZLE_FILTER=name=auth
|
||||
- DOZZLE_USERNAME=foo
|
||||
- DOZZLE_PASSWORD=bar
|
||||
- DOZZLE_AUTH_PROVIDER=simple
|
||||
- DOZZLE_NO_ANALYTICS=1
|
||||
ports:
|
||||
- 9090:8080
|
||||
build:
|
||||
context: .
|
||||
dozzle:
|
||||
@@ -61,7 +58,7 @@ services:
|
||||
|
||||
playwright:
|
||||
container_name: playwright
|
||||
image: mcr.microsoft.com/playwright:v1.39.0-jammy
|
||||
image: mcr.microsoft.com/playwright:v1.40.1-jammy
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- .:/app
|
||||
@@ -72,5 +69,4 @@ services:
|
||||
depends_on:
|
||||
- dozzle
|
||||
- custom_base
|
||||
- auth
|
||||
- remote
|
||||
|
||||
@@ -68,6 +68,7 @@ export default defineConfig({
|
||||
{ text: "Healthcheck", link: "/guide/healthcheck" },
|
||||
{ text: "Hostname", link: "/guide/hostname" },
|
||||
{ text: "Remote Hosts", link: "/guide/remote-hosts" },
|
||||
{ text: "Container Actions", link: "/guide/actions" },
|
||||
{ text: "Supported Env Vars", link: "/guide/supported-env-vars" },
|
||||
{ text: "Logging Files on Disk", link: "/guide/log-files-on-disk" },
|
||||
{ text: "Analytics", link: "/guide/analytics" },
|
||||
|
||||
|
After Width: | Height: | Size: 34 KiB |
@@ -1,30 +0,0 @@
|
||||
import { Handler, HandlerEvent, HandlerContext } from "@netlify/functions";
|
||||
|
||||
let cache;
|
||||
let cacheTime = 0;
|
||||
|
||||
const handler: Handler = async (event: HandlerEvent, context: HandlerContext) => {
|
||||
if (cache && Date.now() - cacheTime < 1000 * 60 * 10) {
|
||||
const headers = {
|
||||
"x-cache": "HIT",
|
||||
};
|
||||
return {
|
||||
headers,
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(cache),
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch("https://hub.docker.com/v2/repositories/amir20/dozzle");
|
||||
const data = await response.json();
|
||||
const { full_description, ...rest } = data;
|
||||
cache = rest;
|
||||
cacheTime = Date.now();
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify(rest),
|
||||
};
|
||||
};
|
||||
|
||||
export { handler };
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
title: Container Actions
|
||||
---
|
||||
|
||||
# Using Container Actions
|
||||
|
||||
Dozzle now supports **Container Actions**, which allows you to `start`, `stop` and `restart` container from within the UI in the dropdown menu.
|
||||
|
||||
<img title="Container Actions" alt="Container Acions Menu UI" width="250" src="/.vitepress/theme/media/dozzle-ui-actions.png">
|
||||
|
||||
This feature is **disabled** by default, which can be enabled by setting environment variable`DOZZLE_ENABLE_ACTIONS` to `true`
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh
|
||||
docker run --volume=/var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --enable-actions
|
||||
```
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
version: "3"
|
||||
services:
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
DOZZLE_ENABLE_ACTIONS: true
|
||||
```
|
||||
|
||||
:::
|
||||
@@ -8,6 +8,62 @@ Dozzle support two configurations for authentication. In the first configuration
|
||||
|
||||
If you do not have an authentication solution then Dozzle has a simple file based user management solution. Authentication providers are setup using `--auth-provider` flag. In both of these configurations, Dozzle will try to save user settings to disk. This data is written to `/data`.
|
||||
|
||||
## File Based User Management
|
||||
|
||||
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will try to read `/data/users.yml`.
|
||||
|
||||
The content of the file looks like:
|
||||
|
||||
```yaml
|
||||
users:
|
||||
# "admin" here is username
|
||||
admin:
|
||||
name: "Admin"
|
||||
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
|
||||
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
email: me@email.net
|
||||
```
|
||||
|
||||
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional.
|
||||
|
||||
The password is hashed using `sha256` which can be generated with `echo -n 'secret-password' | shasum -a 256` or `echo -n 'secret-password' | sha256sum` on linux.
|
||||
|
||||
You will need to mount this file for Dozzle to find it. Here is an example:
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [cli]
|
||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple
|
||||
```
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
version: "3"
|
||||
services:
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /path/to/dozzle/data:/data
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
DOZZLE_AUTH_PROVIDER: simple
|
||||
```
|
||||
|
||||
```yaml [users.yml]
|
||||
users:
|
||||
# "admin" here is username
|
||||
admin:
|
||||
name: "Admin"
|
||||
# Just sha-256 which can be computed with "echo -n password | shasum -a 256"
|
||||
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
email: me@email.net
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
|
||||
|
||||
## Forward Proxy
|
||||
|
||||
Dozzle can be configured to read proxy headers by setting `--auth-provider` to `forward-proxy`.
|
||||
@@ -178,103 +234,33 @@ notifier:
|
||||
|
||||
Valid SSL keys are required because Authelia only supports SSL.
|
||||
|
||||
## File Based User Management
|
||||
### Setting up Dozzle with Cloudflare Zero Trust
|
||||
|
||||
Dozzle supports multi-user authentication by setting `--auth-provider` to `simple`. In this mode, Dozzle will try to read `/data/users.yml`. The content of the file looks like
|
||||
|
||||
```yml
|
||||
users:
|
||||
admin:
|
||||
name: "Admin"
|
||||
# Just sha-256 which can be computed with echo -n password | shasum -a 256
|
||||
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
email: me@email.net
|
||||
```
|
||||
|
||||
Dozzle uses `email` to generate avatars using [Gravatar](https://gravatar.com/). It is optional.
|
||||
|
||||
The password is hashed using `sha256` which can be generated with `echo -n "secret-password" | shasum -a 256` or `echo -n "secret-password" | sha256sum` on linux.
|
||||
|
||||
You will need to mount this file for Dozzle to find it. Here is an example:
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [cli]
|
||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -v /path/to/dozzle/data:/data -p 8080:8080 amir20/dozzle --auth-provider simple
|
||||
```
|
||||
Cloudflare Zero Trust is a service for authenticated access to selfhosted
|
||||
software. This section defines how Dozzle can be setup to use Cloudflare Zero
|
||||
Trust for authentication.
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
version: "3"
|
||||
version: "3.3"
|
||||
|
||||
services:
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
networks:
|
||||
- net
|
||||
environment:
|
||||
DOZZLE_AUTH_PROVIDER: forward-proxy
|
||||
DOZZLE_AUTH_HEADER_USER: Cf-Access-Authenticated-User-Email
|
||||
DOZZLE_AUTH_HEADER_EMAIL: Cf-Access-Authenticated-User-Email
|
||||
DOZZLE_AUTH_HEADER_NAME: Cf-Access-Authenticated-User-Email
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /path/to/dozzle/data:/data
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
DOZZLE_AUTH_PROVIDER: simple
|
||||
expose:
|
||||
- 8080
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
Dozzle uses [JWT](https://en.wikipedia.org/wiki/JSON_Web_Token) to generate tokens for authentication. This token is saved in a cookie.
|
||||
|
||||
## Single Username/Password
|
||||
|
||||
::: danger
|
||||
`--username` and `--password` flags will be removed in v6.x in favor of `--auth-provider`.
|
||||
:::
|
||||
|
||||
Dozzle supports a very simple authentication out of the box with just username and password. You should deploy using SSL to keep the credentials safe. See configuration to use `--username` and `--password`. You can also use docker secrets `--usernamefile` and `--passwordfile`.
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh [cli]
|
||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock -p 8080:8080 amir20/dozzle --username amirraminfar --password supersecretpassword
|
||||
```
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
version: "3"
|
||||
services:
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
ports:
|
||||
- 8080:8080
|
||||
environment:
|
||||
DOZZLE_USERNAME: amirraminfar
|
||||
DOZZLE_PASSWORD: supersecretpassword
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## Setting up authentication with Docker secrets
|
||||
|
||||
Dozzle also support path to file for username and password which can be used to with Docker Secrets.
|
||||
|
||||
```yaml
|
||||
version: "3"
|
||||
services:
|
||||
dozzle:
|
||||
image: amir20/dozzle:latest
|
||||
environment:
|
||||
DOZZLE_LEVEL: debug
|
||||
DOZZLE_USERNAME_FILE: /run/secrets/dozzle_user
|
||||
DOZZLE_PASSWORD_FILE: /run/secrets/dozzle_password
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
secrets:
|
||||
- dozzle_user
|
||||
- dozzle_password
|
||||
ports:
|
||||
- 8080:8080
|
||||
|
||||
secrets:
|
||||
dozzle_user:
|
||||
file: dozzle_user.txt
|
||||
dozzle_password:
|
||||
file: dozzle_password.txt
|
||||
```
|
||||
After running the Dozzle container, configure the Application in Cloudflare Zero
|
||||
Trust dashboard by following the
|
||||
[guide](https://developers.cloudflare.com/cloudflare-one/applications/configure-apps/self-hosted-apps/)
|
||||
here.
|
||||
|
||||
@@ -12,10 +12,32 @@ However, there are workarounds to be able to still access files using mounts.
|
||||
|
||||
## Mounting Local Log Files with Alpine
|
||||
|
||||
Dozzle reads any output stream. This can be used in combination with alpine to `tail` a mounted file. An example of this is:
|
||||
Dozzle reads any output stream. This can be used in combination with alpine to `tail` a mounted file. An example of this is as follows:
|
||||
|
||||
::: code-group
|
||||
|
||||
```sh
|
||||
docker run -v /var/log/system.log:/var/log/test.log alpine tail -f /var/log/test.log
|
||||
```
|
||||
|
||||
```yaml [docker-compose.yml]
|
||||
version: "3"
|
||||
services:
|
||||
dozzle-from-file:
|
||||
container_name: dozzle-from-file
|
||||
image: alpine
|
||||
volumes:
|
||||
- /var/log/system.log:/var/log/stream.log
|
||||
command:
|
||||
- tail
|
||||
- -f
|
||||
- /var/log/stream.log
|
||||
network_mode: none
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
In the above example `/var/log/system.log` is mounted from the host and used with `tail -f` to follow the file. `tail` is smart to follow log rotations. During my testing, using alpine only uses about `300KB` of memory.
|
||||
|
||||
The second tab shows a `docker-compose` file which is useful if you want the log stream to survive server reboot.
|
||||
|
||||
@@ -6,16 +6,18 @@ title: Environment variables and configuration
|
||||
|
||||
Configurations can be done with flags or environment variables. The table below outlines all supported options and their respective env vars.
|
||||
|
||||
| Flag | Env Variable | Default |
|
||||
| ---------------- | ---------------------- | ------- |
|
||||
| `--addr` | `DOZZLE_ADDR` | `:8080` |
|
||||
| `--base` | `DOZZLE_BASE` | `/` |
|
||||
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
|
||||
| `--level` | `DOZZLE_LEVEL` | `info` |
|
||||
| `--filter` | `DOZZLE_FILTER` | `""` |
|
||||
| `--username` | `DOZZLE_USERNAME` | `""` |
|
||||
| `--password` | `DOZZLE_PASSWORD` | `""` |
|
||||
| `--usernamefile` | `DOZZLE_USERNAME_FILE` | `""` |
|
||||
| `--passwordfile` | `DOZZLE_PASSWORD_FILE` | `""` |
|
||||
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
|
||||
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
|
||||
| Flag | Env Variable | Default |
|
||||
| --------------------------- | -------------------------------- | -------------- |
|
||||
| `--addr` | `DOZZLE_ADDR` | `:8080` |
|
||||
| `--base` | `DOZZLE_BASE` | `/` |
|
||||
| `--hostname` | `DOZZLE_HOSTNAME` | `""` |
|
||||
| `--level` | `DOZZLE_LEVEL` | `info` |
|
||||
| `--auth-provider` | `DOZZLE_AUTH_PROVIDER` | `none` |
|
||||
| `--auth-header-user` | `DOZZLE_AUTH_HEADER_USER` | `Remote-User` |
|
||||
| `--auth-header-email` | `DOZZLE_AUTH_HEADER_EMAIL` | `Remote-Email` |
|
||||
| `--auth-header-name` | `DOZZLE_AUTH_HEADER_NAME` | `Remote-Name` |
|
||||
| `--enable-actions` | `DOZZLE_ENABLE_ACTIONS` | false |
|
||||
| `--wait-for-docker-seconds` | `DOZZLE_WAIT_FOR_DOCKER_SECONDS` | 0 |
|
||||
| `--filter` | `DOZZLE_FILTER` | `""` |
|
||||
| `--no-analytics` | `DOZZLE_NO_ANALYTICS` | false |
|
||||
| `--remote-host` | `DOZZLE_REMOTE_HOST` | |
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
"type": "module",
|
||||
"name": "docs",
|
||||
"devDependencies": {
|
||||
"@netlify/functions": "^2.3.0",
|
||||
"@unocss/preset-typography": "^0.57.2",
|
||||
"@unocss/reset": "^0.57.2",
|
||||
"@unocss/transformer-directives": "^0.57.2",
|
||||
"@unocss/preset-typography": "^0.58.3",
|
||||
"@unocss/reset": "^0.58.3",
|
||||
"@unocss/transformer-directives": "^0.58.3",
|
||||
"dozzle": "workspace:*",
|
||||
"sitemap": "^7.1.1",
|
||||
"unocss": "^0.57.2"
|
||||
"unocss": "^0.58.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("authentication", async ({ page }) => {
|
||||
await page.goto("http://auth:8080/");
|
||||
await page.locator('input[name="username"]').fill("foo");
|
||||
await page.locator('input[name="password"]').fill("bar");
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await expect(page.getByTestId("containers")).toHaveText("Containers");
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
users:
|
||||
admin:
|
||||
name: Admin
|
||||
password: "5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"
|
||||
@@ -0,0 +1,9 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("simple authentication", async ({ page }) => {
|
||||
await page.goto("http://simple-auth:8080/");
|
||||
await page.locator('input[name="username"]').fill("admin");
|
||||
await page.locator('input[name="password"]').fill("password");
|
||||
await page.locator('button[type="submit"]').click();
|
||||
await expect(page.getByTestId("containers")).toHaveText("Containers");
|
||||
});
|
||||
|
Before Width: | Height: | Size: 9.9 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
@@ -4,13 +4,12 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/alexflint/go-arg v1.4.3
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760
|
||||
github.com/docker/distribution v2.8.2+incompatible // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
github.com/docker/docker v24.0.7+incompatible
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
@@ -18,42 +17,46 @@ require (
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/spf13/afero v1.10.0
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/net v0.14.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/jwtauth/v5 v5.1.1
|
||||
github.com/PuerkitoBio/goquery v1.8.1
|
||||
github.com/go-chi/chi/v5 v5.0.11
|
||||
github.com/go-chi/jwtauth/v5 v5.3.0
|
||||
github.com/yuin/goldmark v1.6.0
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alexflint/go-scalar v1.2.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||
github.com/distribution/reference v0.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/google/go-cmp v0.5.5 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.4 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11 // indirect
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.18 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/segmentio/asm v1.2.0 // indirect
|
||||
golang.org/x/crypto v0.12.0 // indirect
|
||||
golang.org/x/mod v0.12.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
golang.org/x/tools v0.12.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.0.3 // indirect
|
||||
)
|
||||
|
||||
go 1.21
|
||||
go 1.21.5
|
||||
|
||||
@@ -1,62 +1,19 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||
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.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||
github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo=
|
||||
github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA=
|
||||
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw=
|
||||
github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760 h1:FvTM5NSN5HYvfKpgL+8x73U5v063vHsd7AX05eV1DnM=
|
||||
github.com/beme/abide v0.0.0-20190723115211-635a09831760/go.mod h1:6+8gCKsZnxzhGTmKRh4BSkLos9CbWRJNcrp55We4SqQ=
|
||||
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=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
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/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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=
|
||||
@@ -64,8 +21,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||
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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+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/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
@@ -74,108 +33,36 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
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-chi/jwtauth/v5 v5.1.1 h1:Pjixqu5YkjE9sCLpzE01L0Q4sQzJIPdo7uz9r8ftp/c=
|
||||
github.com/go-chi/jwtauth/v5 v5.1.1/go.mod h1:CYP1WSbzD4MPuKCr537EM3kfFhSQgpUEtMJFuYJjqWU=
|
||||
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-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.0 h1:X7RKGks1lrVeIe2omGyz47pNaNjG2YmwlRN5UKhN8qg=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.0/go.mod h1:2PoGm/KbnzRN9ILY6HFZAI6fTnb1gEZAKogAyqkd6fY=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
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=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
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/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/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
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/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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
||||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||
github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
|
||||
github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11 h1:ViHMnaMeaO0qV16RZWBHM7GTrAnX2aFLVKofc7FuKLQ=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.11/go.mod h1:ZtPtMFlrfDrH2Y0iwfa3dRFn8VzwBrB+cyrm3IBWdDg=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.18 h1:HHZkYS5wWDDyAiNBwztEtDoX07WDhGEdixm8G06R50o=
|
||||
github.com/lestrrat-go/jwx/v2 v2.0.18/go.mod h1:fAJ+k5eTgKdDqanzCuK6DAt3W7n3cs2/FX7JhQdk83U=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
@@ -192,19 +79,16 @@ github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
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.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY=
|
||||
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@@ -212,363 +96,108 @@ github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
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/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=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
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=
|
||||
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
|
||||
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
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=
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
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/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=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
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/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.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.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=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
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=
|
||||
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/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/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
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.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
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.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
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=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
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/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=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||
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/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
|
||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package analytics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func SendBeacon(e BeaconEvent) error {
|
||||
jsonValue, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", "https://b.dozzle.dev/event", bytes.NewBuffer(jsonValue))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode/100 != 2 {
|
||||
dump, err := httputil.DumpResponse(response, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("%v", string(dump))
|
||||
return fmt.Errorf("google analytics returned non-2xx status code: %v", response.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,3 +16,18 @@ type RequestEvent struct {
|
||||
TotalContainers int `json:"totalContainers"`
|
||||
RunningContainers int `json:"runningContainers"`
|
||||
}
|
||||
|
||||
type BeaconEvent struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Browser string `json:"browser"`
|
||||
AuthProvider string `json:"authProvider"`
|
||||
FilterLength int `json:"filterLength"`
|
||||
Clients int `json:"clients"`
|
||||
HasDocumentation bool `json:"hasDocumentation"`
|
||||
HasCustomAddress bool `json:"hasCustomAddress"`
|
||||
HasCustomBase bool `json:"hasCustomBase"`
|
||||
HasHostname bool `json:"hasHostname"`
|
||||
RunningContainers int `json:"runningContainers"`
|
||||
HasActions bool `json:"hasActions"`
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ type contextKey string
|
||||
|
||||
const remoteUser contextKey = "remoteUser"
|
||||
|
||||
type proxyAuthAuth struct {
|
||||
type proxyAuthContext struct {
|
||||
headerUser string
|
||||
headerEmail string
|
||||
headerName string
|
||||
}
|
||||
|
||||
func hashEmail(email string) string {
|
||||
@@ -25,14 +28,18 @@ func hashEmail(email string) string {
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func NewForwardProxyAuth() *proxyAuthAuth {
|
||||
return &proxyAuthAuth{}
|
||||
func NewForwardProxyAuth(user, email, name string) *proxyAuthContext {
|
||||
return &proxyAuthContext{
|
||||
headerUser: user,
|
||||
headerEmail: email,
|
||||
headerName: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *proxyAuthAuth) AuthMiddleware(next http.Handler) http.Handler {
|
||||
func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Remote-Email") != "" {
|
||||
user := newUser(r.Header.Get("Remote-User"), r.Header.Get("Remote-Email"), r.Header.Get("Remote-Name"))
|
||||
if r.Header.Get(p.headerUser) != "" {
|
||||
user := newUser(r.Header.Get(p.headerUser), r.Header.Get(p.headerEmail), r.Header.Get(p.headerName))
|
||||
ctx := context.WithValue(r.Context(), remoteUser, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
} else {
|
||||
@@ -41,7 +48,7 @@ func (p *proxyAuthAuth) AuthMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func (p *proxyAuthAuth) CreateToken(username, password string) (string, error) {
|
||||
func (p *proxyAuthContext) CreateToken(username, password string) (string, error) {
|
||||
log.Fatalf("CreateToken not implemented for proxy auth")
|
||||
return "", nil
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
)
|
||||
|
||||
type simpleAuthContext struct {
|
||||
UserDatabase *UserDatabase
|
||||
UserDatabase UserDatabase
|
||||
tokenAuth *jwtauth.JWTAuth
|
||||
}
|
||||
|
||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
|
||||
func NewSimpleAuth(userDatabase *UserDatabase) *simpleAuthContext {
|
||||
func NewSimpleAuth(userDatabase UserDatabase) *simpleAuthContext {
|
||||
h := sha256.New()
|
||||
for _, user := range userDatabase.Users {
|
||||
h.Write([]byte(user.Password))
|
||||
|
||||
@@ -4,10 +4,14 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -15,47 +19,95 @@ type User struct {
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email" yaml:"email"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
Password string `json:"-" yaml:"password"`
|
||||
}
|
||||
|
||||
func newUser(username, email, name string) *User {
|
||||
avatar := ""
|
||||
if email != "" {
|
||||
avatar = "https://gravatar.com/avatar/" + hashEmail(email)
|
||||
func (u User) AvatarURL() string {
|
||||
name := u.Name
|
||||
if name == "" {
|
||||
name = u.Username
|
||||
}
|
||||
return &User{
|
||||
return fmt.Sprintf("https://gravatar.com/avatar/%s?d=https%%3A%%2F%%2Fui-avatars.com%%2Fapi%%2F/%s/128", hashEmail(u.Email), url.QueryEscape(name))
|
||||
}
|
||||
|
||||
func newUser(username, email, name string) User {
|
||||
return User{
|
||||
Username: username,
|
||||
Email: email,
|
||||
Name: name,
|
||||
Avatar: avatar,
|
||||
}
|
||||
}
|
||||
|
||||
type UserDatabase struct {
|
||||
Users map[string]*User `yaml:"users"`
|
||||
Users map[string]*User `yaml:"users"`
|
||||
LastRead time.Time `yaml:"-"`
|
||||
Path string `yaml:"-"`
|
||||
}
|
||||
|
||||
func ReadUsersFromFile(path string) (*UserDatabase, error) {
|
||||
func ReadUsersFromFile(path string) (UserDatabase, error) {
|
||||
users, err := decodeUsersFromFile(path)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
|
||||
users.LastRead = time.Now()
|
||||
users.Path = path
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func decodeUsersFromFile(path string) (UserDatabase, error) {
|
||||
users := UserDatabase{}
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return &users, err
|
||||
return users, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := yaml.NewDecoder(file).Decode(&users); err != nil {
|
||||
return &users, err
|
||||
return users, err
|
||||
}
|
||||
|
||||
for username, user := range users.Users {
|
||||
user.Username = username
|
||||
if user.Password == "" {
|
||||
log.Fatalf("User %s has no password", username)
|
||||
}
|
||||
|
||||
if user.Name == "" {
|
||||
user.Name = username
|
||||
}
|
||||
}
|
||||
|
||||
return &users, nil
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *UserDatabase) readFileIfChanged() error {
|
||||
if u.Path == "" {
|
||||
return nil
|
||||
}
|
||||
info, err := os.Stat(u.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.ModTime().After(u.LastRead) {
|
||||
log.Infof("Found changes to %s. Updating users...", u.Path)
|
||||
users, err := decodeUsersFromFile(u.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Users = users.Users
|
||||
u.LastRead = time.Now()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *UserDatabase) Find(username string) *User {
|
||||
if err := u.readFileIfChanged(); err != nil {
|
||||
log.Errorf("Error reading users file: %s", err)
|
||||
}
|
||||
user, ok := u.Users[username]
|
||||
if !ok {
|
||||
return nil
|
||||
@@ -82,8 +134,8 @@ func sha256sum(s string) string {
|
||||
}
|
||||
|
||||
func UserFromContext(ctx context.Context) *User {
|
||||
if user, ok := ctx.Value(remoteUser).(*User); ok {
|
||||
return user
|
||||
if user, ok := ctx.Value(remoteUser).(User); ok {
|
||||
return &user
|
||||
} else {
|
||||
if _, claims, err := jwtauth.FromContext(ctx); err == nil {
|
||||
username, ok := claims["username"].(string)
|
||||
@@ -95,7 +147,8 @@ func UserFromContext(ctx context.Context) *User {
|
||||
}
|
||||
email := claims["email"].(string)
|
||||
name := claims["name"].(string)
|
||||
return newUser(username, email, name)
|
||||
user := newUser(username, email, name)
|
||||
return &user
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
@@ -49,6 +50,9 @@ type DockerCLI interface {
|
||||
ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
||||
ContainerStats(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
||||
Ping(ctx context.Context) (types.Ping, error)
|
||||
ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error
|
||||
ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error
|
||||
ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
@@ -145,6 +149,19 @@ func (d *Client) FindContainer(id string) (Container, error) {
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func (d *Client) ContainerActions(action string, containerID string) error {
|
||||
switch action {
|
||||
case "start":
|
||||
return d.cli.ContainerStart(context.Background(), containerID, types.ContainerStartOptions{})
|
||||
case "stop":
|
||||
return d.cli.ContainerStop(context.Background(), containerID, container.StopOptions{})
|
||||
case "restart":
|
||||
return d.cli.ContainerRestart(context.Background(), containerID, container.StopOptions{})
|
||||
default:
|
||||
return fmt.Errorf("unknown action: %s", action)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Client) ListContainers() ([]Container, error) {
|
||||
containerListOptions := types.ContainerListOptions{
|
||||
Filters: d.filters,
|
||||
@@ -173,6 +190,7 @@ func (d *Client) ListContainers() ([]Container, error) {
|
||||
Status: c.Status,
|
||||
Host: d.host.ID,
|
||||
Health: findBetweenParentheses(c.Status),
|
||||
Labels: c.Labels,
|
||||
}
|
||||
containers = append(containers, container)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,42 @@ func (m *mockedProxy) ContainerStats(ctx context.Context, containerID string, st
|
||||
return types.ContainerStats{}, nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStart(ctx context.Context, containerID string, options types.ContainerStartOptions) error {
|
||||
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
|
||||
if err != nil {
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerStop(ctx context.Context, containerID string, options container.StopOptions) error {
|
||||
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
|
||||
if err != nil {
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockedProxy) ContainerRestart(ctx context.Context, containerID string, options container.StopOptions) error {
|
||||
|
||||
args := m.Called(ctx, containerID, options)
|
||||
err := args.Get(0)
|
||||
|
||||
if err != nil {
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Test_dockerClient_ListContainers_null(t *testing.T) {
|
||||
proxy := new(mockedProxy)
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
@@ -202,3 +238,78 @@ func Test_dockerClient_FindContainer_error(t *testing.T) {
|
||||
|
||||
proxy.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_dockerClient_ContainerActions_happy(t *testing.T) {
|
||||
containers := []types.Container{
|
||||
{
|
||||
ID: "abcdefghijklmnopqrst",
|
||||
Names: []string{"/z_test_container"},
|
||||
},
|
||||
{
|
||||
ID: "1234567890_abcxyzdef",
|
||||
Names: []string{"/a_test_container"},
|
||||
},
|
||||
}
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
json := types.ContainerJSON{Config: &container.Config{Tty: false}}
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
proxy.On("ContainerInspect", mock.Anything, "abcdefghijkl").Return(json, nil)
|
||||
proxy.On("ContainerStart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
proxy.On("ContainerStop", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
proxy.On("ContainerRestart", mock.Anything, "abcdefghijkl", mock.Anything).Return(nil)
|
||||
|
||||
container, err := client.FindContainer("abcdefghijkl")
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
|
||||
assert.Equal(t, container, Container{
|
||||
ID: "abcdefghijkl",
|
||||
Name: "z_test_container",
|
||||
Names: []string{"/z_test_container"},
|
||||
Host: "localhost",
|
||||
Tty: false,
|
||||
})
|
||||
|
||||
actions := []string{"start", "stop", "restart"}
|
||||
for _, action := range actions {
|
||||
err := client.ContainerActions(action, container.ID)
|
||||
require.NoError(t, err, "error should not be thrown")
|
||||
assert.Equal(t, err, nil)
|
||||
}
|
||||
|
||||
proxy.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func Test_dockerClient_ContainerActions_error(t *testing.T) {
|
||||
containers := []types.Container{
|
||||
{
|
||||
ID: "abcdefghijklmnopqrst",
|
||||
Names: []string{"/z_test_container"},
|
||||
},
|
||||
{
|
||||
ID: "1234567890_abcxyzdef",
|
||||
Names: []string{"/a_test_container"},
|
||||
},
|
||||
}
|
||||
|
||||
proxy := new(mockedProxy)
|
||||
client := &Client{proxy, filters.NewArgs(), &Host{ID: "localhost"}}
|
||||
|
||||
proxy.On("ContainerList", mock.Anything, mock.Anything).Return(containers, nil)
|
||||
proxy.On("ContainerStart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
proxy.On("ContainerStop", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
proxy.On("ContainerRestart", mock.Anything, mock.Anything, mock.Anything).Return(errors.New("test"))
|
||||
|
||||
container, err := client.FindContainer("random-id")
|
||||
require.Error(t, err, "error should be thrown")
|
||||
|
||||
actions := []string{"start", "stop", "restart"}
|
||||
for _, action := range actions {
|
||||
err := client.ContainerActions(action, container.ID)
|
||||
require.Error(t, err, "error should be thrown")
|
||||
assert.Error(t, err, "error should have been returned")
|
||||
}
|
||||
|
||||
proxy.AssertExpectations(t)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"io"
|
||||
@@ -78,6 +79,9 @@ func (g *EventGenerator) consumeReader() {
|
||||
message, streamType, readerError := readEvent(g.reader, g.tty)
|
||||
if message != "" {
|
||||
logEvent := createEvent(message, streamType)
|
||||
if _, ok := logEvent.Message.(string); ok {
|
||||
logEvent.Message = stripANSI(logEvent.Message.(string))
|
||||
}
|
||||
logEvent.Level = guessLogLevel(logEvent)
|
||||
g.buffer <- logEvent
|
||||
}
|
||||
@@ -164,7 +168,12 @@ func createEvent(message string, streamType StdType) *LogEvent {
|
||||
if json.Valid([]byte(message)) {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(message), &data); err != nil {
|
||||
log.Warnf("unable to parse json logs - error was \"%v\" while trying unmarshal \"%v\"", err.Error(), message)
|
||||
var jsonErr *json.UnmarshalTypeError
|
||||
if errors.As(err, &jsonErr) {
|
||||
if jsonErr.Value == "string" {
|
||||
log.Warnf("unable to parse json logs - error was \"%v\" while trying unmarshal \"%v\"", err.Error(), message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logEvent.Message = data
|
||||
}
|
||||
|
||||
@@ -117,6 +117,15 @@ func Test_createEvent(t *testing.T) {
|
||||
Message: "{\"key\"}",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid json message",
|
||||
args: args{
|
||||
message: "2020-05-13T18:55:37.772853839Z 123",
|
||||
},
|
||||
want: &LogEvent{
|
||||
Message: "123",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
)
|
||||
|
||||
var KEY_VALUE_REGEX = regexp.MustCompile(`level=(\w+)`)
|
||||
var ANSI_COLOR_REGEX = regexp.MustCompile(`\x1b\[[0-9;]*m`)
|
||||
var LOG_LEVELS = []string{"error", "warn", "warning", "info", "debug", "trace", "fatal"}
|
||||
var LOG_LEVELS_PLAIN = map[string]*regexp.Regexp{}
|
||||
var LOG_LEVEL_BRACKET = map[string]*regexp.Regexp{}
|
||||
@@ -24,13 +23,12 @@ func init() {
|
||||
func guessLogLevel(logEvent *LogEvent) string {
|
||||
switch value := logEvent.Message.(type) {
|
||||
case string:
|
||||
stripped := ANSI_COLOR_REGEX.ReplaceAllString(value, "") // remove ansi color codes
|
||||
for _, level := range LOG_LEVELS {
|
||||
if LOG_LEVELS_PLAIN[level].MatchString(stripped) {
|
||||
if LOG_LEVELS_PLAIN[level].MatchString(value) {
|
||||
return level
|
||||
}
|
||||
|
||||
if LOG_LEVEL_BRACKET[level].MatchString(stripped) {
|
||||
if LOG_LEVEL_BRACKET[level].MatchString(value) {
|
||||
return level
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"
|
||||
|
||||
var re = regexp.MustCompile(ansi)
|
||||
|
||||
func stripANSI(str string) string {
|
||||
return re.ReplaceAllString(str, "")
|
||||
}
|
||||
@@ -6,18 +6,19 @@ import (
|
||||
|
||||
// Container represents an internal representation of docker containers
|
||||
type Container struct {
|
||||
ID string `json:"id"`
|
||||
Names []string `json:"names"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
ImageID string `json:"imageId"`
|
||||
Command string `json:"command"`
|
||||
Created int64 `json:"created"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
Health string `json:"health,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Tty bool `json:"-"`
|
||||
ID string `json:"id"`
|
||||
Names []string `json:"names"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
ImageID string `json:"imageId"`
|
||||
Command string `json:"command"`
|
||||
Created int64 `json:"created"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
Health string `json:"health,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
Tty bool `json:"-"`
|
||||
Labels map[string]string `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerStat represent stats instant for a container
|
||||
|
||||
@@ -2,10 +2,10 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func HttpRequest(addr string, base string) error {
|
||||
@@ -13,7 +13,16 @@ func HttpRequest(addr string, base string) error {
|
||||
addr = "localhost" + addr
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("http://%s%s/healthcheck", addr, base)
|
||||
if base == "/" {
|
||||
base = ""
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s%s/healthcheck", addr, base)
|
||||
|
||||
if !strings.HasPrefix(url, "http") {
|
||||
url = "http://" + url
|
||||
}
|
||||
|
||||
log.Info("Checking health of " + url)
|
||||
resp, err := http.Get(url)
|
||||
|
||||
@@ -23,10 +32,8 @@ func HttpRequest(addr string, base string) error {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
|
||||
return nil
|
||||
return fmt.Errorf("Healthcheck failed with status code %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package healthcheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpRequest(t *testing.T) {
|
||||
// Test server that always responds with a status code of 200
|
||||
server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Test server that always responds with a status code of 500
|
||||
errorServer := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
defer errorServer.Close()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
addr string
|
||||
base string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Healthcheck OK",
|
||||
addr: server.URL,
|
||||
base: "/",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Healthcheck Fail",
|
||||
addr: errorServer.URL,
|
||||
base: "/",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := HttpRequest(tt.addr, tt.base)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("HttpRequest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package profile
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -11,6 +13,12 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
profileFilename = "profile.json"
|
||||
)
|
||||
|
||||
var missingProfileErr = errors.New("Profile file does not exist")
|
||||
|
||||
type Settings struct {
|
||||
Search bool `json:"search"`
|
||||
MenuWidth float32 `json:"menuWidth"`
|
||||
@@ -26,7 +34,15 @@ type Settings struct {
|
||||
HourStyle string `json:"hourStyle,omitempty"`
|
||||
}
|
||||
|
||||
var data_path string
|
||||
type Profile struct {
|
||||
Settings *Settings `json:"settings,omitempty"`
|
||||
Pinned []string `json:"pinned"`
|
||||
VisibleKeys map[string][][]string `json:"visibleKeys,omitempty"`
|
||||
ReleaseSeen string `json:"releaseSeen,omitempty"`
|
||||
}
|
||||
|
||||
var dataPath string
|
||||
var mux = &sync.Mutex{}
|
||||
|
||||
func init() {
|
||||
path, err := filepath.Abs("./data")
|
||||
@@ -40,28 +56,40 @@ func init() {
|
||||
return
|
||||
}
|
||||
}
|
||||
data_path = path
|
||||
dataPath = path
|
||||
}
|
||||
|
||||
func SaveUserSettings(user *auth.User, settings *Settings) error {
|
||||
path := filepath.Join(data_path, user.Username)
|
||||
func UpdateFromReader(user auth.User, reader io.Reader) error {
|
||||
mux.Lock()
|
||||
defer mux.Unlock()
|
||||
existingProfile, err := Load(user)
|
||||
if err != nil && err != missingProfileErr {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create user directory if it doesn't exist
|
||||
if err := json.NewDecoder(reader).Decode(&existingProfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return Save(user, existingProfile)
|
||||
}
|
||||
|
||||
func Save(user auth.User, profile Profile) error {
|
||||
path := filepath.Join(dataPath, user.Username)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if err := os.Mkdir(path, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
settings_path := filepath.Join(path, "settings.json")
|
||||
|
||||
data, err := json.MarshalIndent(settings, "", " ")
|
||||
filePath := filepath.Join(path, profileFilename)
|
||||
data, err := json.MarshalIndent(profile, "", " ")
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Create(settings_path)
|
||||
f, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,24 +104,28 @@ func SaveUserSettings(user *auth.User, settings *Settings) error {
|
||||
return f.Sync()
|
||||
}
|
||||
|
||||
func LoadUserSettings(user *auth.User) (*Settings, error) {
|
||||
path := filepath.Join(data_path, user.Username)
|
||||
settings_path := filepath.Join(path, "settings.json")
|
||||
func Load(user auth.User) (Profile, error) {
|
||||
path := filepath.Join(dataPath, user.Username)
|
||||
profilePath := filepath.Join(path, profileFilename)
|
||||
|
||||
if _, err := os.Stat(settings_path); os.IsNotExist(err) {
|
||||
return &Settings{}, errors.New("Settings file does not exist")
|
||||
if _, err := os.Stat(profilePath); os.IsNotExist(err) {
|
||||
return Profile{}, missingProfileErr
|
||||
}
|
||||
|
||||
f, err := os.Open(settings_path)
|
||||
f, err := os.Open(profilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return Profile{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
var settings Settings
|
||||
if err := json.NewDecoder(f).Decode(&settings); err != nil {
|
||||
return nil, err
|
||||
var profile Profile
|
||||
if err := json.NewDecoder(f).Decode(&profile); err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
|
||||
return &settings, nil
|
||||
if profile.Pinned == nil {
|
||||
profile.Pinned = []string{}
|
||||
}
|
||||
|
||||
return profile, nil
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package releases
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/yuin/goldmark"
|
||||
)
|
||||
|
||||
type githubRelease struct {
|
||||
Name string `json:"name"`
|
||||
MentionsCount int `json:"mentions_count"`
|
||||
TagName string `json:"tag_name"`
|
||||
Body string `json:"body"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
HtmlUrl string `json:"html_url"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Name string `json:"name"`
|
||||
MentionsCount int `json:"mentionsCount"`
|
||||
Tag string `json:"tag"`
|
||||
Body string `json:"body"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
HtmlUrl string `json:"htmlUrl"`
|
||||
Latest bool `json:"latest"`
|
||||
Features int `json:"features"`
|
||||
BugFixes int `json:"bugFixes"`
|
||||
Breaking int `json:"breaking"`
|
||||
}
|
||||
|
||||
func Fetch(currentVersion string) ([]Release, error) {
|
||||
response, err := http.Get("https://api.github.com/repos/amir20/dozzle/releases?per_page=9")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
var githubReleases []githubRelease
|
||||
if err := json.NewDecoder(response.Body).Decode(&githubReleases); err != nil {
|
||||
return []Release{}, err
|
||||
}
|
||||
|
||||
var releases []Release
|
||||
for _, githubRelease := range githubReleases {
|
||||
var buffer bytes.Buffer
|
||||
goldmark.Convert([]byte(githubRelease.Body), &buffer)
|
||||
html := buffer.String()
|
||||
|
||||
if githubRelease.TagName == currentVersion {
|
||||
break
|
||||
}
|
||||
|
||||
release := Release{
|
||||
Name: githubRelease.Name,
|
||||
MentionsCount: githubRelease.MentionsCount,
|
||||
Tag: githubRelease.TagName,
|
||||
Body: html,
|
||||
CreatedAt: githubRelease.CreatedAt,
|
||||
HtmlUrl: githubRelease.HtmlUrl,
|
||||
}
|
||||
|
||||
doc, _ := goquery.NewDocumentFromReader(&buffer)
|
||||
doc.Find("h3").Each(func(i int, s *goquery.Selection) {
|
||||
if strings.Contains(s.Text(), "Features") {
|
||||
release.Features = s.Next().Find("li").Length()
|
||||
}
|
||||
|
||||
if strings.Contains(s.Text(), "Bug Fixes") {
|
||||
release.BugFixes = s.Next().Find("li").Length()
|
||||
}
|
||||
|
||||
if strings.Contains(s.Text(), "Breaking Changes") {
|
||||
release.Breaking = s.Next().Find("li").Length()
|
||||
}
|
||||
})
|
||||
|
||||
releases = append(releases, release)
|
||||
}
|
||||
|
||||
if len(releases) > 0 {
|
||||
releases[0].Latest = true
|
||||
}
|
||||
|
||||
return releases, nil
|
||||
}
|
||||