Compare commits

...

139 commits

Author SHA1 Message Date
Renovate Bot
f085b54df9
fix(deps): update dependency vue-i18n to v11.1.10 [security] 2025-07-18 00:09:46 +00:00
Renovate Bot
0f29036c23
chore(deps): update typescript-eslint monorepo to v8.37.0 2025-07-17 18:10:00 +00:00
Renovate Bot
347cd428de
chore(deps): update golang:1.24.5-alpine docker digest to daae04e 2025-07-17 18:09:26 +00:00
Renovate Bot
94db8d872d
chore(deps): update redis:alpine docker digest to 25c0ae3 2025-07-17 16:39:26 +00:00
Renovate Bot
17cf0421c6
chore(deps): update golang:1.24.5-alpine docker digest to 48ee313 2025-07-16 03:09:38 +00:00
Renovate Bot
a4c82bcea2
chore(deps): update redis:alpine docker digest to d12963a 2025-07-16 01:39:39 +00:00
Renovate Bot
d2c76a15ad
chore(deps): update golang:1.24.5-alpine docker digest to 9c4b616 2025-07-15 23:39:39 +00:00
Renovate Bot
4edf5527cd
chore(deps): update redis:alpine docker digest to 1d7b7ad 2025-07-15 22:09:39 +00:00
Renovate Bot
ce1f00a61b
chore(deps): update alpine:3.22 docker digest to 4bcff63 2025-07-15 21:39:28 +00:00
Renovate Bot
eaf53dac8a
chore(deps): update dependency eslint to v9.31.0 2025-07-14 20:39:29 +00:00
Renovate Bot
70e1e6f695
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 30a579e 2025-07-14 00:09:23 +00:00
Renovate Bot
044aa2f625
chore(deps): update typescript-eslint monorepo to v8.36.0 2025-07-11 11:17:38 +00:00
Renovate Bot
e0e53c0d98
chore(deps): update dependency esbuild to v0.25.6 2025-07-10 17:09:29 +00:00
Renovate Bot
154cafe931
chore(deps): update golang docker tag to v1.24.5 2025-07-09 18:39:53 +00:00
Renovate Bot
b9ea713d50
chore(deps): update dependency go to v1.24.5 2025-07-08 17:39:49 +00:00
Renovate Bot
e912ba7a0e
chore(deps): update redis:alpine docker digest to 73734b0 2025-07-07 19:39:33 +00:00
Renovate Bot
ab91d3a069
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to bef714d 2025-07-07 03:09:27 +00:00
Renovate Bot
de93013229
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 166b7a4 2025-07-07 00:09:27 +00:00
Renovate Bot
b0851562d2
fix(deps): update dependency vue-i18n to v11.1.9 2025-07-06 08:09:34 +00:00
Renovate Bot
255986b438
fix(deps): update dependency vue-i18n to v11.1.8 2025-07-05 15:09:27 +00:00
Renovate Bot
542455a897
chore(deps): update dependency eslint-plugin-vue to v10.3.0 2025-07-05 10:39:27 +00:00
Renovate Bot
0a30fce81d
chore(deps): update dependency @babel/eslint-parser to v7.28.0 2025-07-05 08:39:27 +00:00
Renovate Bot
524f37112b
chore(deps): update dependency eslint to v9.30.1 2025-07-04 20:39:27 +00:00
Renovate Bot
2f42751b3a
chore(deps): update dependency vue-eslint-parser to v10.2.0 2025-07-04 12:09:27 +00:00
Renovate Bot
6b5da64a87
chore(deps): update typescript-eslint monorepo to v8.35.1 2025-07-03 17:39:29 +00:00
Renovate Bot
ac6c6e3bf1
chore(deps): update dependency eslint to v9.30.0 2025-06-30 21:09:29 +00:00
Renovate Bot
b192339737
ci(deps): update github/codeql-action action to v3.29.2 2025-06-30 13:39:23 +00:00
Renovate Bot
aee0e7b6ca
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to f5b5e5d 2025-06-30 03:39:21 +00:00
Renovate Bot
619bb76a36
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to a88f984 2025-06-30 00:09:21 +00:00
Renovate Bot
c4b800a7fc
ci(deps): update ncipollo/release-action action to v1.18.0 2025-06-29 21:39:22 +00:00
Renovate Bot
1f13ca3a09
ci(deps): update ncipollo/release-action action to v1.17.0 2025-06-29 01:39:36 +00:00
Renovate Bot
6a87028fa8
ci(deps): update github/codeql-action action to v3.29.1 2025-06-27 11:39:49 +00:00
Renovate Bot
2e28802c13
chore(deps): update dependency vue-eslint-parser to v10.1.4 2025-06-27 07:53:12 +00:00
Renovate Bot
dc886a6abc
chore(deps): update typescript-eslint monorepo to v8.35.0 2025-06-26 17:40:02 +00:00
Renovate Bot
2b66aef09f
fix(deps): update dependency vue-i18n to v11.1.7 2025-06-25 13:09:59 +00:00
Renovate Bot
92278c2cdb
fix(deps): update module github.com/redis/go-redis/v9 to v9.11.0 2025-06-24 12:09:58 +00:00
Renovate Bot
902e6b2153
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 5087089 2025-06-23 03:09:45 +00:00
Renovate Bot
134abf146d
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 5f4ad2a 2025-06-23 00:09:45 +00:00
Renovate Bot
da8b6bde2e
fix(deps): update dependency vue to v3.5.17 2025-06-21 13:39:57 +00:00
Renovate Bot
5e3fe81e1a
fix(deps): update dependency bootstrap to v5.3.7 2025-06-20 16:39:53 +00:00
Renovate Bot
eb07510802
fix(deps): update dependency vue-i18n to v11.1.6 2025-06-20 01:39:54 +00:00
Renovate Bot
2fe93c447d
chore(deps): update typescript-eslint monorepo to v8.34.1 2025-06-19 17:39:38 +00:00
Renovate Bot
347d1dde89
ci(deps): update docker/setup-buildx-action action to v3.11.1 2025-06-18 08:44:01 +00:00
Knut Ahlers
5c5e13664b
docs: improve README wording
closes #214

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-06-17 13:11:24 +02:00
Renovate Bot
2e7be97835 ci(deps): update stefanzweifel/git-auto-commit-action action to v6 (#43)
Co-authored-by: Renovate Bot <renovate@luzifer.io>
Co-committed-by: Renovate Bot <renovate@luzifer.io>
2025-06-16 22:44:02 +00:00
Renovate Bot
7d69359587
chore(deps): update dependency eslint to v9.29.0 2025-06-16 18:14:10 +00:00
Renovate Bot
1c67b3a38e
ci(deps): update docker/setup-buildx-action action to v3.11.0 2025-06-16 13:34:15 +00:00
Renovate Bot
1856b78bc2
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to c9af228 2025-06-16 03:34:11 +00:00
Renovate Bot
571f50f8e0
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 32791e1 2025-06-16 00:03:49 +00:00
Knut Ahlers
93599e9bec
Release: OTS v1.17.2 2025-06-15 13:21:31 +02:00
Renovate Bot
b7416e984f
chore(deps): update typescript-eslint monorepo to v8.34.0 2025-06-12 17:34:35 +00:00
Renovate Bot
845d36a12f
ci(deps): update github/codeql-action action to v3.29.0 2025-06-11 19:34:13 +00:00
Renovate Bot
6bef171e06
chore(deps): update dependency eslint-plugin-vue to v10.2.0 2025-06-09 03:34:24 +00:00
Renovate Bot
a4d2aaf95b
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 8b7b77b 2025-06-09 03:33:48 +00:00
Renovate Bot
5db3906edb
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 42814eb 2025-06-09 00:03:25 +00:00
Renovate Bot
efdde54bc2
fix(deps): update module github.com/redis/go-redis/v9 to v9.10.0 2025-06-06 16:04:16 +00:00
Renovate Bot
b80ddc3528
chore(deps): update dependency @babel/eslint-parser to v7.27.5 2025-06-06 11:34:10 +00:00
Renovate Bot
ba84cbe8c9
chore(deps): update dependency go to v1.24.4 2025-06-05 22:38:56 +00:00
Renovate Bot
2848241317
chore(deps): update golang docker tag to v1.24.4 2025-06-05 22:37:39 +00:00
Renovate Bot
c9f9a8f456
chore(deps): update typescript-eslint monorepo to v8.33.1 2025-06-05 17:34:17 +00:00
Renovate Bot
6e65e93a45
ci(deps): update github/codeql-action action to v3.28.19 2025-06-03 13:33:46 +00:00
Renovate Bot
b308826cfb
chore(deps): update dependency eslint to v9.28.0 2025-06-02 21:04:12 +00:00
Renovate Bot
c5ef8e5c75
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 8721171 2025-06-02 03:33:41 +00:00
Renovate Bot
1d73de5603
chore(deps): update ghcr.io/luzifer-docker/gh-arch-env docker digest to 5575c8e 2025-06-02 00:04:03 +00:00
Renovate Bot
8afa78ca75
fix(deps): update dependency vue to v3.5.16 2025-06-01 00:32:59 +00:00
Renovate Bot
2f239c1a6d
chore(deps): update golang:1.24.3-alpine docker digest to b4f875e 2025-05-31 08:33:01 +00:00
Renovate Bot
1cbfa43a70
chore(deps): update golang:1.24.3-alpine docker digest to 2853d62 2025-05-31 06:03:07 +00:00
Renovate Bot
176c677104
chore(deps): update golang:1.24.3-alpine docker digest to b158e66 2025-05-31 03:02:52 +00:00
Renovate Bot
fac9d4ee52
chore(deps): update alpine docker tag to v3.22 2025-05-30 20:28:56 +00:00
Renovate Bot
50c8aa82cf
chore(deps): update typescript-eslint monorepo to v8.33.0 2025-05-30 17:33:38 +00:00
Renovate Bot
17b27ec647
fix(deps): update dependency vue-i18n to v11.1.5 2025-05-30 16:03:34 +00:00
Renovate Bot
67de7ddaf2
chore(deps): update dependency esbuild to v0.25.5 2025-05-30 04:34:25 +00:00
Renovate Bot
9c4d04cd06
chore(deps): update redis:alpine docker digest to 48501c5 2025-05-30 04:33:46 +00:00
Knut Ahlers
839b57b392
Release: OTS v1.17.1 2025-05-29 15:16:32 +02:00
Renovate Bot
ba6c1a752a
fix(deps): update dependency vue to v3.5.15 2025-05-29 13:04:00 +00:00
Renovate Bot
fcfc673870
Update docker/build-push-action action to v6.18.0 2025-05-28 08:34:07 +00:00
Renovate Bot
16c84924ff
Update module github.com/redis/go-redis/v9 to v9.9.0 2025-05-27 16:33:39 +00:00
Renovate Bot
87a4733342
Update dependency vue-i18n to v11.1.4 2025-05-27 10:04:03 +00:00
Renovate Bot
421e489bf5
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to 401e667 2025-05-26 03:33:14 +00:00
Renovate Bot
faf1c2f478
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to 85f168b 2025-05-26 00:03:39 +00:00
Renovate Bot
473bd6763f
Update dependency eslint to v9.27.0 2025-05-16 19:10:47 +00:00
Renovate Bot
c6c0681e1d
Update github/codeql-action action to v3.28.18 2025-05-16 10:38:18 +00:00
Renovate Bot
a10edd22dc
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to d72ad55 2025-05-16 03:07:46 +00:00
Renovate Bot
0bac9a504a
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to 7ed9201 2025-05-16 00:08:50 +00:00
Renovate Bot
a9ecba7983
Update docker/build-push-action action to v6.17.0 2025-05-15 13:06:42 +00:00
Renovate Bot
d6c5b49134
Update dependency vue to v3.5.14 2025-05-15 03:36:34 +00:00
Renovate Bot
deec94d16b
Update redis:alpine Docker digest to 62b5498 2025-05-13 22:37:02 +00:00
Renovate Bot
ada23c2e5f
Update typescript-eslint monorepo to v8.32.1 2025-05-12 17:38:31 +00:00
Knut Ahlers
fabebc8283
Release: OTS v1.17.0 2025-05-12 09:57:44 +02:00
Piotr Icikowski
ef257c2f2a
Update Polish translation (#213)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-05-12 09:51:28 +02:00
Renovate Bot
063604e2b2
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to 1c82360 2025-05-09 03:06:09 +00:00
Renovate Bot
0e88ea72f1
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to bc6b14d 2025-05-09 00:06:12 +00:00
Renovate Bot
1332c020d1
Update actions/setup-go action to v5.5.0 2025-05-08 03:07:25 +00:00
Renovate Bot
d8930c57be
Update dependency go to v1.24.3 2025-05-06 22:41:51 +00:00
Renovate Bot
0b700c5f1a
Update golang Docker tag to v1.24.3 2025-05-06 21:05:47 +00:00
Renovate Bot
18064dec54
Update dependency esbuild to v0.25.4 2025-05-06 00:34:45 +00:00
Renovate Bot
ef21473ef2
Update typescript-eslint monorepo to v8.32.0 2025-05-05 23:14:27 +00:00
Renovate Bot
2df3a5e580
Update dependency bootstrap to v5.3.6 2025-05-05 23:05:47 +00:00
Renovate Bot
db582a80af
Update redis:alpine Docker digest to 0779069 2025-05-05 23:05:09 +00:00
Renovate Bot
4389af557d
Update redis:alpine Docker digest to f33f899 2025-05-05 20:04:58 +00:00
Knut Ahlers
25f474210b
Fix: JS does not support autobuild
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-03 01:39:15 +02:00
Knut Ahlers
23b29934d3
CI: Remove extra autobuild step
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-03 01:31:06 +02:00
Renovate Bot
9f0f399e09
Update dependency eslint to v9.26.0 2025-05-02 22:05:37 +00:00
Renovate Bot
f2210fa9c5
Update github/codeql-action action to v3.28.17 2025-05-02 11:19:58 +00:00
Knut Ahlers
6fef469597
CI: Use same image in all workflows
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-02 13:15:25 +02:00
Renovate Bot
a8eca8691c
Update luzifer/gh-arch-env Docker digest to 90b9c6e 2025-05-02 05:05:13 +00:00
Renovate Bot
e04c55c261
Update ghcr.io/luzifer-docker/gh-arch-env Docker digest to fbf1e32 2025-05-02 04:05:03 +00:00
Renovate Bot
be209cde64
Update module gopkg.in/yaml.v2 to v3 (#8)
Co-authored-by: Renovate Bot <renovate@luzifer.io>
Co-committed-by: Renovate Bot <renovate@luzifer.io>
2025-05-01 16:36:24 +00:00
Knut Ahlers
283ffa548f
CI: Trigger test-and-build for branches with slash
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 17:18:34 +02:00
Knut Ahlers
fcacea8562
Disable updates for internal packages
to prevent infinite commit loop

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 16:41:08 +02:00
Renovate Bot
0f50d56a90
Update github.com/Luzifer/ots/pkg/client digest to f537ef3 2025-05-01 14:30:39 +00:00
Renovate Bot
f537ef3dce
Update github.com/Luzifer/ots/pkg/client digest to f07fa02 2025-05-01 14:30:14 +00:00
Renovate Bot
f07fa028eb
Update github.com/Luzifer/ots/pkg/client digest to 7554efb 2025-05-01 14:29:05 +00:00
Renovate Bot
7554efb0e2
Update github.com/Luzifer/ots/pkg/client digest to e7d044c 2025-05-01 14:28:41 +00:00
Knut Ahlers
e7d044c8e3
Fix: Update versions to match
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 16:27:52 +02:00
Renovate Bot
caff9de904
Update github/codeql-action action to v3 2025-05-01 14:10:43 +00:00
Renovate Bot
1ad5a9e869
Update module github.com/stretchr/testify to v1.10.0 2025-05-01 14:09:31 +00:00
Renovate Bot
e272cfa47b
Pin dependencies 2025-05-01 14:08:24 +00:00
Renovate Bot
7660206fa8
Update github.com/Luzifer/ots/pkg/customization digest to 0d2df36 2025-05-01 14:07:44 +00:00
Renovate Bot
5acf6a2fa4
Update github.com/Luzifer/ots/pkg/client digest to 32a8b09 2025-05-01 14:07:09 +00:00
Renovate Bot
d4f512e0f7
Update github/codeql-action action to v2.28.1 2025-05-01 13:53:59 +00:00
Knut Ahlers
32a8b09142
Pin action versions
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 15:53:23 +02:00
Renovate Bot
0d2df36f82
Update github.com/Luzifer/ots/pkg/customization digest to a7b1657 2025-05-01 13:36:41 +00:00
Renovate Bot
f51a7ff79c
Update github.com/Luzifer/ots/pkg/client digest to ceb2963 2025-05-01 13:36:06 +00:00
Knut Ahlers
ceb296332f
Enable renovate
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 15:32:31 +02:00
Knut Ahlers
21d5e645a3
Remove reference to download_libs make target
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 15:27:08 +02:00
Knut Ahlers
a7b165783a
Update Dockerfile for new frontend build
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 15:06:14 +02:00
Knut Ahlers
e572d2f545
Port to Vue3 and TypeScript
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 13:25:11 +02:00
Knut Ahlers
b447417d0f
gev: Run code instead of producing a binary
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 10:47:40 +02:00
Knut Ahlers
401d79c7b5
Release: OTS v1.16.0 2025-05-01 00:33:39 +02:00
Knut Ahlers
722dab91ea
Update Go dependencies
adresses CVE-2025-22869, CVE-2025-29923

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 00:20:25 +02:00
Knut Ahlers
b1aa78dd71
Lint: Update linter config for golangci-lint v2
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 00:12:10 +02:00
Wesley
e7d1efc505
feat: support auto theme mode (#212)
Co-authored-by: Mave <11227996+Ma-ve@users.noreply.github.com>
Co-authored-by: Knut Ahlers <knut@ahlers.me>
2025-05-01 00:08:32 +02:00
Knut Ahlers
45184a496e
Cleanup test workflow
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-02-12 20:27:27 +01:00
Knut Ahlers
1f52d8056e
Update Node dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-02-12 20:23:26 +01:00
Knut Ahlers
86af345d40
Update Go dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2025-02-12 20:15:50 +01:00
Knut Ahlers
8fadf7205f
Release: OTS v1.15.1 2024-12-12 10:33:02 +01:00
Knut Ahlers
0be304ffbe
Update Node dependencies
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-12-12 10:26:48 +01:00
Knut Ahlers
8a804d93eb
Update Go dependencies
fixes CVE-2024-45337

Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-12-12 10:25:56 +01:00
59 changed files with 2788 additions and 5444 deletions

View file

@ -1,151 +0,0 @@
/*
* Hack to automatically load globally installed eslint modules
* on Archlinux systems placed in /usr/lib/node_modules
*
* Source: https://github.com/eslint/eslint/issues/11914#issuecomment-569108633
*/
const Module = require('module')
const hacks = [
'babel-eslint',
'eslint-plugin-vue',
]
const ModuleFindPath = Module._findPath
Module._findPath = (request, paths, isMain) => {
const r = ModuleFindPath(request, paths, isMain)
if (!r && hacks.includes(request)) {
return require.resolve(`/usr/lib/node_modules/${request}`)
}
return r
}
/*
* ESLint configuration derived as differences from eslint:recommended
* with changes I found useful to ensure code quality and equal formatting
* https://eslint.org/docs/user-guide/configuring
*/
module.exports = {
env: {
browser: true,
node: true,
},
extends: [
'plugin:vue/recommended',
'eslint:recommended', // https://eslint.org/docs/rules/
],
globals: {
process: true,
},
parserOptions: {
ecmaVersion: 2020,
parser: '@babel/eslint-parser',
requireConfigFile: false,
},
plugins: [
// required to lint *.vue files
'vue',
],
reportUnusedDisableDirectives: true,
root: true,
rules: {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { after: true, before: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'comma-dangle': ['error', 'always-multiline'],
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'default-case-last': ['error'],
'default-param-last': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { null: 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'], // allow async-await
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { afterColon: true, beforeColon: false, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['warn'],
'newline-per-chained-call': ['error'],
'no-alert': ['error'],
'no-console': ['off'],
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development
'no-duplicate-imports': ['error'],
'no-else-return': ['error'],
'no-empty-function': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxBOF: 0, maxEOF: 0 }],
'no-promise-executor-return': ['error'],
'no-return-assign': ['error'],
'no-script-url': ['error'],
'no-template-curly-in-string': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-unreachable-loop': ['error'],
'no-unsafe-optional-chaining': ['error'],
'no-useless-return': ['error'],
'no-var': ['error'],
'no-warning-comments': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-rest-params': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: false }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'require-atomic-updates': ['error'],
'require-await': ['error'],
'semi': ['error', 'never'],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: false, ignoreMemberSort: false }],
'sort-keys': ['error', 'asc', { caseSensitive: true, natural: false }],
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': ['error', 'never'],
'space-in-parens': ['error', 'never'],
'space-infix-ops': ['error'],
'space-unary-ops': ['error', { nonwords: false, words: true }],
'spaced-comment': ['warn', 'always'],
'switch-colon-spacing': ['error'],
'template-curly-spacing': ['error', 'never'],
'unicode-bom': ['error', 'never'],
'vue/new-line-between-multi-line-property': ['error'],
'vue/no-empty-component-block': ['error'],
'vue/no-reserved-component-names': ['error'],
'vue/no-template-target-blank': ['error'],
'vue/no-unused-properties': ['error'],
'vue/no-unused-refs': ['error'],
'vue/no-useless-mustaches': ['error'],
'vue/order-in-components': ['off'], // Collides with sort-keys
'vue/require-name-property': ['error'],
'vue/v-for-delimiter-style': ['error'],
'vue/v-on-function-call': ['error'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
}

View file

@ -1,13 +1,12 @@
--- ---
name: 'CodeQL'
name: "CodeQL"
on: on:
push: push:
branches: [ "master" ] branches: ['master']
pull_request: pull_request:
# The branches below must be a subset of the branches above # The branches below must be a subset of the branches above
branches: [ "master" ] branches: ['master']
schedule: schedule:
- cron: '38 21 * * 3' - cron: '38 21 * * 3'
@ -24,29 +23,25 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
language: [ 'go', 'javascript' ] language: ['go', 'javascript']
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Go - name: Install Go
uses: actions/setup-go@v4 uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with: with:
go-version-file: go.mod go-version-file: go.mod
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v2 uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
build-mode: ${{ matrix.language == 'go' && 'autobuild' || '' }}
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2 uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2
with: with:
category: "/language:${{matrix.language}}" category: '/language:${{matrix.language}}'
...

View file

@ -1,5 +1,4 @@
--- ---
name: docker-publish name: docker-publish
on: on:
push: push:
@ -19,18 +18,18 @@ jobs:
steps: steps:
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- uses: actions/checkout@v4 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
lfs: true lfs: true
show-progress: false show-progress: false
- name: Log into registry - name: Log into registry
uses: docker/login-action@v3 uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
@ -41,11 +40,9 @@ jobs:
run: bash ci/docker-gen-tagnames.sh run: bash ci/docker-gen-tagnames.sh
- name: Build and push - name: Build and push
uses: docker/build-push-action@v6 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.taggen.outputs.docker_build_tags }} tags: ${{ steps.taggen.outputs.docker_build_tags }}
...

View file

@ -1,9 +1,8 @@
--- ---
name: pull-request-ci name: pull-request-ci
on: on:
pull_request_target: pull_request_target:
paths: ["i18n.yaml"] paths: ['i18n.yaml']
jobs: jobs:
generate-translations: generate-translations:
@ -12,7 +11,7 @@ jobs:
shell: bash shell: bash
container: container:
image: luzifer/gh-arch-env image: ghcr.io/luzifer-docker/gh-arch-env@sha256:30a579e63513635d85ad9cc17344f73377a3a4add99c8efdc8b51493f926085d
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
GOPATH: /go GOPATH: /go
@ -23,7 +22,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with: with:
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }} ref: ${{ github.head_ref }}
@ -35,10 +34,8 @@ jobs:
working-directory: ./ci/translate working-directory: ./ci/translate
run: go run . run: go run .
- uses: stefanzweifel/git-auto-commit-action@v5 - uses: stefanzweifel/git-auto-commit-action@778341af668090896ca464160c2def5d1d1a3eb0 # v6.0.1
with: with:
commit_author: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>' commit_author: 'github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>'
commit_message: 'CI: Update embedded translations' commit_message: 'CI: Update embedded translations'
file_pattern: 'src/langs/langs.js' file_pattern: 'src/langs/langs.js'
...

View file

@ -1,9 +1,8 @@
--- ---
name: test-and-build name: test-and-build
on: on:
push: push:
branches: ['*'] branches: ['**']
tags: ['v*'] tags: ['v*']
permissions: permissions:
@ -17,7 +16,7 @@ jobs:
shell: bash shell: bash
container: container:
image: luzifer/archlinux image: ghcr.io/luzifer-docker/gh-arch-env@sha256:30a579e63513635d85ad9cc17344f73377a3a4add99c8efdc8b51493f926085d
env: env:
CGO_ENABLED: 0 CGO_ENABLED: 0
GOPATH: /go GOPATH: /go
@ -25,28 +24,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Enable custom AUR package repo - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
run: echo -e "[luzifer]\nSigLevel = Never\nServer = https://archrepo.hub.luzifer.io/\$arch" >>/etc/pacman.conf
- name: Install required packages
run: |
pacman -Syy --noconfirm \
awk \
curl \
diffutils \
git \
go \
golangci-lint-bin \
make \
nodejs-lts-hydrogen \
npm \
tar \
trivy \
unzip \
which \
zip
- uses: actions/checkout@v3
- name: Marking workdir safe - name: Marking workdir safe
run: git config --global --add safe.directory /__w/ots/ots run: git config --global --add safe.directory /__w/ots/ots
@ -76,7 +54,7 @@ jobs:
grep -q "### Language" translate-issue.md || rm -f translate-issue.md grep -q "### Language" translate-issue.md || rm -f translate-issue.md
- name: Update Translations Issue - name: Update Translations Issue
uses: JasonEtco/create-an-issue@v2 uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 # v2.9.2
if: github.ref == 'refs/heads/master' && hashFiles('translate-issue.md') != '' if: github.ref == 'refs/heads/master' && hashFiles('translate-issue.md') != ''
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@ -99,12 +77,10 @@ jobs:
run: 'awk "/^#/ && ++c==2{exit}; /^#/f" "History.md" | tail -n +2 >release_changelog.md' run: 'awk "/^#/ && ++c==2{exit}; /^#/f" "History.md" | tail -n +2 >release_changelog.md'
- name: Release - name: Release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@bcfe5470707e8832e12347755757cec0eb3c22af # v1.18.0
if: startsWith(github.ref, 'refs/tags/') if: startsWith(github.ref, 'refs/tags/')
with: with:
artifacts: '.build/*' artifacts: '.build/*'
bodyFile: release_changelog.md bodyFile: release_changelog.md
draft: false draft: false
generateReleaseNotes: false generateReleaseNotes: false
...

1
.gitignore vendored
View file

@ -8,6 +8,7 @@ frontend/css
frontend/js frontend/js
frontend/locale/*.untranslated.json frontend/locale/*.untranslated.json
frontend/webfonts frontend/webfonts
frontend/*.ttf
frontend/*.woff2 frontend/*.woff2
node_modules node_modules
ots ots

View file

@ -1,174 +1,147 @@
# Derived from https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml version: '2'
---
run: run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
timeout: 5m
# Force readonly modules usage for checking
modules-download-mode: readonly modules-download-mode: readonly
relative-path-mode: wd
output: output:
formats: formats:
- format: tab tab:
path: stdout path: stdout
colors: false
issues:
# This disables the included exclude-list in golangci-lint as that
# list for example fully hides G304 gosec rule, errcheck, exported
# rule of revive and other errors one really wants to see.
# Smme detail: https://github.com/golangci/golangci-lint/issues/456
exclude-use-default: false
# Don't limit the number of shown issues: Report ALL of them
max-issues-per-linter: 0
max-same-issues: 0
linters: linters:
disable-all: true default: none
enable: enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false] - asciicheck
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] - bidichk
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false] - bodyclose
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false] - containedctx
- contextcheck # check the function whether use a non-inherited context [fast: false, auto-fix: false] - contextcheck
- copyloopvar # copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false] - copyloopvar
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] - dogsled
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false] - durationcheck
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] - errcheck
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false] - errchkjson
- forbidigo # Forbids identifiers [fast: true, auto-fix: false] - forbidigo
- funlen # Tool for detection of long functions [fast: true, auto-fix: false] - funlen
- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false] - gocognit
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] - goconst
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false] - gocritic
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false] - gocyclo
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false] - godox
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true] - gosec
- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true] - govet
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true] - ineffassign
- gosec # Inspects source code for security problems [fast: true, auto-fix: false] - misspell
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false] - mnd
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false] - nakedret
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] - nilerr
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true] - nilnil
- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false] - noctx
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false] - nolintlint
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false] - revive
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] - staticcheck
- noctx # noctx finds sending http request without context.Context [fast: true, auto-fix: false] - unconvert
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false] - unused
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false] - wastedassign
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false] - wrapcheck
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
linters-settings: settings:
funlen: funlen:
lines: 100 lines: 100
statements: 60 statements: 60
gocyclo: gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 15 min-complexity: 15
gomnd: mnd:
ignored-functions: 'strconv.(?:Format|Parse)\B+' ignored-functions:
- strconv.(?:Format|Parse)\B+
revive: revive:
rules: rules:
#- name: add-constant # Suggests using constant for magic numbers and string literals - name: atomic
# Opinion: Makes sense for strings, not for numbers but checks numbers - name: banned-characters
#- name: argument-limit # Specifies the maximum number of arguments a function can receive | Opinion: Don't need this
- name: atomic # Check for common mistaken usages of the `sync/atomic` package
- name: banned-characters # Checks banned characters in identifiers
arguments: arguments:
- ';' # Greek question mark - ;
- name: bare-return # Warns on bare returns - name: bare-return
- name: blank-imports # Disallows blank imports - name: blank-imports
- name: bool-literal-in-expr # Suggests removing Boolean literals from logic expressions - name: bool-literal-in-expr
- name: call-to-gc # Warns on explicit call to the garbage collector - name: call-to-gc
#- name: cognitive-complexity # Sets restriction for maximum Cognitive complexity. - name: confusing-naming
# There is a dedicated linter for this - name: confusing-results
- name: confusing-naming # Warns on methods with names that differ only by capitalization - name: constant-logical-expr
- name: confusing-results # Suggests to name potentially confusing function results - name: context-as-argument
- name: constant-logical-expr # Warns on constant logical expressions - name: context-keys-type
- name: context-as-argument # `context.Context` should be the first argument of a function. - name: deep-exit
- name: context-keys-type # Disallows the usage of basic types in `context.WithValue`. - name: defer
#- name: cyclomatic # Sets restriction for maximum Cyclomatic complexity. - name: dot-imports
# There is a dedicated linter for this - name: duplicated-imports
#- name: datarace # Spots potential dataraces - name: early-return
# Is not (yet) available? - name: empty-block
- name: deep-exit # Looks for program exits in funcs other than `main()` or `init()` - name: empty-lines
- name: defer # Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1) - name: errorf
- name: dot-imports # Forbids `.` imports. - name: error-naming
- name: duplicated-imports # Looks for packages that are imported two or more times - name: error-return
- name: early-return # Spots if-then-else statements that can be refactored to simplify code reading - name: error-strings
- name: empty-block # Warns on empty code blocks - name: exported
- name: empty-lines # Warns when there are heading or trailing newlines in a block
- name: errorf # Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`
- name: error-naming # Naming of error variables.
- name: error-return # The error return parameter should be last.
- name: error-strings # Conventions around error strings.
- name: exported # Naming and commenting conventions on exported symbols.
arguments: ['sayRepetitiveInsteadOfStutters']
#- name: file-header # Header which each file should have.
# Useless without config, have no config for it
- name: flag-parameter # Warns on boolean parameters that create a control coupling
#- name: function-length # Warns on functions exceeding the statements or lines max
# There is a dedicated linter for this
#- name: function-result-limit # Specifies the maximum number of results a function can return
# Opinion: Don't need this
- name: get-return # Warns on getters that do not yield any result
- name: identical-branches # Spots if-then-else statements with identical `then` and `else` branches
- name: if-return # Redundant if when returning an error.
#- name: imports-blacklist # Disallows importing the specified packages
# Useless without config, have no config for it
- name: import-shadowing # Spots identifiers that shadow an import
- name: increment-decrement # Use `i++` and `i--` instead of `i += 1` and `i -= 1`.
- name: indent-error-flow # Prevents redundant else statements.
#- name: line-length-limit # Specifies the maximum number of characters in a lined
# There is a dedicated linter for this
#- name: max-public-structs # The maximum number of public structs in a file.
# Opinion: Don't need this
- name: modifies-parameter # Warns on assignments to function parameters
- name: modifies-value-receiver # Warns on assignments to value-passed method receivers
#- name: nested-structs # Warns on structs within structs
# Opinion: Don't need this
- name: optimize-operands-order # Checks inefficient conditional expressions
#- name: package-comments # Package commenting conventions.
# Opinion: Don't need this
- name: range # Prevents redundant variables when iterating over a collection.
- name: range-val-address # Warns if address of range value is used dangerously
- name: range-val-in-closure # Warns if range value is used in a closure dispatched as goroutine
- name: receiver-naming # Conventions around the naming of receivers.
- name: redefines-builtin-id # Warns on redefinitions of builtin identifiers
#- name: string-format # Warns on specific string literals that fail one or more user-configured regular expressions
# Useless without config, have no config for it
- name: string-of-int # Warns on suspicious casts from int to string
- name: struct-tag # Checks common struct tags like `json`,`xml`,`yaml`
- name: superfluous-else # Prevents redundant else statements (extends indent-error-flow)
- name: time-equal # Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.
- name: time-naming # Conventions around the naming of time variables.
- name: unconditional-recursion # Warns on function calls that will lead to (direct) infinite recursion
- name: unexported-naming # Warns on wrongly named un-exported symbols
- name: unexported-return # Warns when a public return is from unexported type.
- name: unhandled-error # Warns on unhandled errors returned by funcion calls
arguments: arguments:
- "fmt.(Fp|P)rint(f|ln|)" - sayRepetitiveInsteadOfStutters
- name: unnecessary-stmt # Suggests removing or simplifying unnecessary statements - name: flag-parameter
- name: unreachable-code # Warns on unreachable code - name: get-return
- name: unused-parameter # Suggests to rename or remove unused function parameters - name: identical-branches
- name: unused-receiver # Suggests to rename or remove unused method receivers - name: if-return
#- name: use-any # Proposes to replace `interface{}` with its alias `any` - name: import-shadowing
# Is not (yet) available? - name: increment-decrement
- name: useless-break # Warns on useless `break` statements in case clauses - name: indent-error-flow
- name: var-declaration # Reduces redundancies around variable declaration. - name: modifies-parameter
- name: var-naming # Naming rules. - name: modifies-value-receiver
- name: waitgroup-by-value # Warns on functions taking sync.WaitGroup as a by-value parameter - name: optimize-operands-order
- name: range
- name: range-val-address
- name: range-val-in-closure
- name: receiver-naming
- name: redefines-builtin-id
- name: string-of-int
- name: struct-tag
- name: superfluous-else
- name: time-equal
- name: time-naming
- name: unconditional-recursion
- name: unexported-naming
- name: unexported-return
- name: unhandled-error
arguments:
- fmt.(Fp|P)rint(f|ln|)
- name: unnecessary-stmt
- name: unreachable-code
- name: unused-parameter
- name: unused-receiver
- name: useless-break
- name: var-declaration
- name: var-naming
- name: waitgroup-by-value
... exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
issues:
max-issues-per-linter: 0
max-same-issues: 0
formatters:
enable:
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$

View file

@ -1,4 +1,4 @@
FROM golang:1-alpine AS builder FROM golang:1.24.5-alpine@sha256:daae04ebad0c21149979cd8e9db38f565ecefd8547cf4a591240dc1972cf1399 AS builder
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GOPATH=/go \ GOPATH=/go \
@ -12,20 +12,20 @@ RUN set -ex \
curl \ curl \
git \ git \
make \ make \
nodejs-lts \ nodejs-current \
npm \ npm \
tar \ tar \
unzip \ unzip \
&& make download_libs generate-inner generate-apidocs \ && make frontend_prod generate-apidocs \
&& go install \ && go install \
-ldflags "-X main.version=$(git describe --tags --always || echo dev)" \ -ldflags "-X main.version=$(git describe --tags --always || echo dev)" \
-mod=readonly -mod=readonly
FROM alpine:latest FROM alpine:3.22@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
LABEL org.opencontainers.image.authors='Knut Ahlers <knut@ahlers.me>' \ LABEL org.opencontainers.image.authors='Knut Ahlers <knut@ahlers.me>' \
org.opencontainers.image.version='1.15.0' \ org.opencontainers.image.version='1.17.2' \
org.opencontainers.image.url='https://github.com/Luzifer/ots/pkgs/container/ots' \ org.opencontainers.image.url='https://github.com/Luzifer/ots/pkgs/container/ots' \
org.opencontainers.image.documentation='https://github.com/Luzifer/ots/wiki' \ org.opencontainers.image.documentation='https://github.com/Luzifer/ots/wiki' \
org.opencontainers.image.source='https://github.com/Luzifer/ots' \ org.opencontainers.image.source='https://github.com/Luzifer/ots' \

View file

@ -1,4 +1,4 @@
FROM golang:1-alpine AS builder FROM golang:1.24.5-alpine@sha256:daae04ebad0c21149979cd8e9db38f565ecefd8547cf4a591240dc1972cf1399 AS builder
ENV CGO_ENABLED=0 \ ENV CGO_ENABLED=0 \
GOPATH=/go \ GOPATH=/go \
@ -12,11 +12,11 @@ RUN set -ex \
curl \ curl \
git \ git \
make \ make \
nodejs-lts \ nodejs-current \
npm \ npm \
tar \ tar \
unzip \ unzip \
&& make download_libs generate-inner generate-apidocs \ && make frontend_prod generate-apidocs \
&& go install \ && go install \
-ldflags "-X main.version=$(git describe --tags --always || echo dev)" \ -ldflags "-X main.version=$(git describe --tags --always || echo dev)" \
-mod=readonly -mod=readonly
@ -25,7 +25,7 @@ RUN set -ex \
FROM scratch FROM scratch
LABEL org.opencontainers.image.authors='Knut Ahlers <knut@ahlers.me>' \ LABEL org.opencontainers.image.authors='Knut Ahlers <knut@ahlers.me>' \
org.opencontainers.image.version='1.15.0' \ org.opencontainers.image.version='1.17.2' \
org.opencontainers.image.url='https://github.com/Luzifer/ots/pkgs/container/ots' \ org.opencontainers.image.url='https://github.com/Luzifer/ots/pkgs/container/ots' \
org.opencontainers.image.documentation='https://github.com/Luzifer/ots/wiki' \ org.opencontainers.image.documentation='https://github.com/Luzifer/ots/wiki' \
org.opencontainers.image.source='https://github.com/Luzifer/ots' \ org.opencontainers.image.source='https://github.com/Luzifer/ots' \

View file

@ -1,3 +1,48 @@
# 1.17.2 / 2025-06-15
* Bugfixes
* chore(deps): update alpine docker tag to v3.22
* chore(deps): update dependency go to v1.24.4
* fix(deps): update dependency vue-i18n to v11.1.5
* fix(deps): update dependency vue to v3.5.16
* fix(deps): update module github.com/redis/go-redis/v9 to v9.10.0
# 1.17.1 / 2025-05-29
* Bugfixes
* fix(deps): update dependency vue-i18n to v11.1.4
* fix(deps): update dependency vue to v3.5.15
* fix(deps): update module github.com/redis/go-redis/v9 to v9.9.0
# 1.17.0 / 2025-05-12
* Improvements
* Port to Vue3 and TypeScript
* Bugfixes
* Update Go dependencies
* Update Node dependencies
* Translations
* Update Polish translation (#213) (Thanks @Icikowski)
# 1.16.0 / 2025-05-01
* New Features
* feat: support auto theme mode (#212) (Thanks @Ma-ve)
* Bugfixes
* Cleanup test workflow
* Lint: Update linter config for golangci-lint v2
* Update Go dependencies
* Update Node dependencies
# 1.15.1 / 2024-12-12
* Bugfixes
* Update Node dependencies
* Update Go dependencies
# 1.15.0 / 2024-12-05 # 1.15.0 / 2024-12-05
* Improvements * Improvements

View file

@ -1,49 +1,35 @@
VER_FONTAWESOME:=6.4.0 default: build-local
build-local: frontend generate-apidocs
default: generate download_libs
build-local: download_libs generate-inner generate-apidocs
go build \ go build \
-buildmode=pie \ -buildmode=pie \
-ldflags "-s -w -X main.version=$(shell git describe --tags --always || echo dev)" \ -ldflags "-s -w -X main.version=$(shell git describe --tags --always || echo dev)" \
-mod=readonly \ -mod=readonly \
-trimpath -trimpath
generate:
docker run --rm -i -v $(CURDIR):$(CURDIR) -w $(CURDIR) node:18-alpine \
sh -exc "apk add make && make generate-inner generate-apidocs && chown -R $(shell id -u) frontend node_modules"
generate-apidocs: generate-apidocs:
npx --yes @redocly/cli build-docs docs/openapi.yaml --disableGoogleFont true -o /tmp/api.html npx --yes @redocly/cli build-docs docs/openapi.yaml --disableGoogleFont true -o /tmp/api.html
mv /tmp/api.html frontend/ mv /tmp/api.html frontend/
generate-inner: frontend_prod: export NODE_ENV=production
npm ci --include=dev frontend_prod: frontend
node ./ci/build.mjs
frontend: node_modules
corepack yarn@1 node ci/build.mjs
frontend_lint: node_modules
corepack yarn@1 eslint --fix src
node_modules:
corepack yarn@1 install --production=false --frozen-lockfile
publish: export NODE_ENV=production publish: export NODE_ENV=production
publish: download_libs generate-inner generate-apidocs publish: frontend_prod generate-apidocs
bash ./ci/build.sh bash ./ci/build.sh
translate: translate:
cd ci/translate && go run . --write-issue-file cd ci/translate && go run . --write-issue-file
# -- Download / refresh external libraries --
clean_libs:
rm -rf \
frontend/css \
frontend/js \
frontend/webfonts
download_libs: clean_libs
download_libs: fontawesome
fontawesome:
curl -sSfL https://github.com/FortAwesome/Font-Awesome/archive/$(VER_FONTAWESOME).tar.gz | \
tar -vC frontend -xz --strip-components=1 --wildcards --exclude='*/js-packages' '*/css' '*/webfonts'
# -- Vulnerability scanning -- # -- Vulnerability scanning --
trivy: trivy:
@ -56,3 +42,5 @@ trivy:
--scanners config,license,secret,vuln \ --scanners config,license,secret,vuln \
--severity HIGH,CRITICAL \ --severity HIGH,CRITICAL \
--skip-dirs docs --skip-dirs docs
.PHONY: node_modules

View file

@ -8,8 +8,8 @@
## Features ## Features
- AES 256bit encryption - Secrets are encrypted with AES 256bit encryption in browser
- Server does never get the password - Server never receives the plain text secret
- Secret is deleted on first read - Secret is deleted on first read
## Setup ## Setup
@ -99,7 +99,9 @@ Requirements:
- Node v22+ - Node v22+
- Tilt v0.33+ - Tilt v0.33+
Just run `tilt up` and visit `http://localhost:15641/` for the development server. Run `tilt up`, and see `http://localhost:10350/` for the Tilt dashboard.
Front-end application is available at `http://localhost:15641/`.
## Localize to your own language ## Localize to your own language

View file

@ -1,16 +1,16 @@
# Install Node deps on change of package.json # Install Node deps on change of package.json
local_resource( local_resource(
'npm', 'yarn',
cmd='npm i', cmd='corepack yarn@1 install', # Not using the make target to edit the lockfile
deps=['package.json'], deps=['package.json'],
) )
# Rebuild frontend if source files change # Rebuild frontend if source files change
local_resource( local_resource(
'frontend', 'frontend',
cmd='node ./ci/build.mjs', cmd='make frontend',
deps=['src'], deps=['src'],
resource_deps=['npm'], resource_deps=['yarn'],
) )
# Generate translation files on source change # Generate translation files on source change
@ -23,7 +23,6 @@ local_resource(
# Rebuild and run Go webserver on code changes # Rebuild and run Go webserver on code changes
local_resource( local_resource(
'server', 'server',
cmd='go build .',
deps=[ deps=[
'api.go', 'api.go',
'frontend', 'frontend',
@ -32,9 +31,12 @@ local_resource(
'pkg', 'pkg',
'storage.go', 'storage.go',
'tplFuncs.go', 'tplFuncs.go',
'go.mod', 'go.sum',
], ],
ignore=['ots', 'src'], ignore=[
serve_cmd='./ots --listen=:15641', 'src'
],
serve_cmd='go run . --listen=:15641',
serve_env={ serve_env={
'CUSTOMIZE': 'customize.yaml', 'CUSTOMIZE': 'customize.yaml',
}, },
@ -42,5 +44,8 @@ local_resource(
http_get=http_get_action(15641, path='/api/healthz'), http_get=http_get_action(15641, path='/api/healthz'),
initial_delay_secs=1, initial_delay_secs=1,
), ),
resource_deps=['frontend', 'translations'], resource_deps=[
'frontend',
'translations',
],
) )

View file

@ -1,6 +1,6 @@
import { sassPlugin } from 'esbuild-sass-plugin'
import vuePlugin from 'esbuild-vue'
import esbuild from 'esbuild' import esbuild from 'esbuild'
import { sassPlugin } from 'esbuild-sass-plugin'
import vuePlugin from 'esbuild-plugin-vue3'
esbuild.build({ esbuild.build({
assetNames: '[name]', assetNames: '[name]',
@ -8,9 +8,10 @@ esbuild.build({
define: { define: {
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'dev'), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'dev'),
}, },
entryPoints: ['src/main.js'], entryPoints: ['src/main.ts'],
legalComments: 'none', legalComments: 'none',
loader: { loader: {
'.ttf': 'empty', // Drop files, all targets below support woff2
'.woff2': 'file', '.woff2': 'file',
}, },
minify: true, minify: true,
@ -20,10 +21,10 @@ esbuild.build({
vuePlugin(), vuePlugin(),
], ],
target: [ target: [
'chrome87', 'chrome109',
'edge87', 'edge132',
'es2020', 'es2020',
'firefox84', 'firefox115',
'safari14', 'safari16',
], ],
}) })

View file

@ -1,12 +1,12 @@
module translate module translate
go 1.23 go 1.23.0
toolchain go1.23.0 toolchain go1.24.5
require ( require (
github.com/Luzifer/go_helpers/v2 v2.25.0 github.com/Luzifer/go_helpers/v2 v2.25.0
github.com/Luzifer/rconfig/v2 v2.5.2 github.com/Luzifer/rconfig/v2 v2.6.0
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/mitchellh/hashstructure/v2 v2.0.2
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
@ -17,15 +17,15 @@ require (
require ( require (
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.32.0 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect gopkg.in/validator.v2 v2.0.1 // indirect
) )

View file

@ -2,12 +2,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Luzifer/go_helpers/v2 v2.25.0 h1:k1J4gd1+BfuokTDoWgcgib9P5mdadjzKEgbtKSVe46k= github.com/Luzifer/go_helpers/v2 v2.25.0 h1:k1J4gd1+BfuokTDoWgcgib9P5mdadjzKEgbtKSVe46k=
github.com/Luzifer/go_helpers/v2 v2.25.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc= github.com/Luzifer/go_helpers/v2 v2.25.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
github.com/Luzifer/rconfig/v2 v2.5.2 h1:4Bfp8mTrCCK/xghUmUbh/qtKiLZA6RC0tHTgqkNw1m4= github.com/Luzifer/rconfig/v2 v2.6.0 h1:ZKgsO2Wt/XZXawuAZCDkW7xszxZ8hQDTV1Wm63Jvnqk=
github.com/Luzifer/rconfig/v2 v2.5.2/go.mod h1:HnqUWg+NQh60/neUqfMDDDo5d1v8UPuhwKR1HqM4VWQ= github.com/Luzifer/rconfig/v2 v2.6.0/go.mod h1:CxISRwCV2WjO5gnUnaRGDq17u1M3TvmFgzJLr87ejtc=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -41,19 +41,19 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 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/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -1,6 +1,8 @@
module github.com/Luzifer/ots/cmd/ots-cli module github.com/Luzifer/ots/cmd/ots-cli
go 1.23 go 1.23.0
toolchain go1.24.5
replace ( replace (
github.com/Luzifer/ots/pkg/client => ../../pkg/client github.com/Luzifer/ots/pkg/client => ../../pkg/client
@ -8,19 +10,19 @@ replace (
) )
require ( require (
github.com/Luzifer/ots/pkg/client v0.0.0-20240922105511-48bf8c9ca4fe github.com/Luzifer/ots/pkg/client v0.0.0-20250501151834-283ffa548fa8
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.9.1
) )
require ( require (
github.com/Luzifer/go-openssl/v4 v4.2.2 // indirect github.com/Luzifer/go-openssl/v4 v4.2.4 // indirect
github.com/Luzifer/ots/pkg/customization v0.0.0-20240922105511-48bf8c9ca4fe // indirect github.com/Luzifer/ots/pkg/customization v0.0.0-20250501151834-283ffa548fa8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/crypto v0.27.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.25.0 // indirect golang.org/x/sys v0.32.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View file

@ -1,6 +1,6 @@
github.com/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc= github.com/Luzifer/go-openssl/v4 v4.2.4 h1:3Eu3gSeZpr8Ha+IofVnSWttCL1xejRr/lda4l4TZRWk=
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc= github.com/Luzifer/go-openssl/v4 v4.2.4/go.mod h1:ykquxaR0R1Vor83/FAtGBJZZO5zswuSQTVx1FQc1bJY=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -15,23 +15,21 @@ github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkB
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -2,7 +2,7 @@ version: "3.8"
services: services:
app: app:
build: build:
context: https://github.com/Luzifer/ots.git#v1.15.0 context: https://github.com/Luzifer/ots.git#v1.17.2
restart: always restart: always
environment: environment:
# Optional, see "Customization" in README # Optional, see "Customization" in README
@ -18,7 +18,7 @@ services:
ports: ports:
- 3000:3000 - 3000:3000
redis: redis:
image: redis:alpine image: redis:alpine@sha256:25c0ae32c6c2301798579f5944af53729766a18eff5660bbef196fc2e6214a9c
restart: always restart: always
volumes: volumes:
- ./data:/data - ./data:/data

View file

@ -107,7 +107,7 @@ spec:
name: ots-cutomize name: ots-cutomize
containers: containers:
- name: ots - name: ots
image: ghcr.io/luzifer/ots:v1.15.0 image: ghcr.io/luzifer/ots:v1.17.2
args: args:
- --storage-type - --storage-type
- redis - redis

126
eslint.config.mjs Normal file
View file

@ -0,0 +1,126 @@
import globals from 'globals'
import js from '@eslint/js'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import vue from 'eslint-plugin-vue'
export default [
js.configs.recommended,
...vue.configs['flat/recommended'],
{
languageOptions: {
ecmaVersion: 'latest',
globals: {
...globals.browser,
...globals.node,
process: true,
},
parserOptions: {
parser: '@typescript-eslint/parser',
requireConfigFile: true,
},
},
linterOptions: {
reportUnusedDisableDirectives: true,
},
plugins: {
'@typescript-eslint': typescriptEslint,
vue,
},
rules: {
'array-bracket-newline': ['error', { multiline: true }],
'array-bracket-spacing': ['error'],
'arrow-body-style': ['error', 'as-needed'],
'arrow-parens': ['error', 'as-needed'],
'arrow-spacing': ['error', { after: true, before: true }],
'block-spacing': ['error'],
'brace-style': ['error', '1tbs'],
'camelcase': ['warn'],
'comma-dangle': ['error', 'always-multiline'],
'comma-spacing': ['error'],
'comma-style': ['error', 'last'],
'curly': ['error'],
'default-case-last': ['error'],
'default-param-last': ['error'],
'dot-location': ['error', 'property'],
'dot-notation': ['error'],
'eol-last': ['error', 'always'],
'eqeqeq': ['error', 'always', { null: 'ignore' }],
'func-call-spacing': ['error', 'never'],
'function-paren-newline': ['error', 'multiline'],
'generator-star-spacing': ['off'],
'implicit-arrow-linebreak': ['error'],
'indent': ['error', 2],
'key-spacing': ['error', { afterColon: true, beforeColon: false, mode: 'strict' }],
'keyword-spacing': ['error'],
'linebreak-style': ['error', 'unix'],
'lines-between-class-members': ['error'],
'multiline-comment-style': ['off'],
'newline-per-chained-call': ['error'],
'no-alert': ['error'],
'no-console': ['off'],
'no-debugger': 'off',
'no-duplicate-imports': ['error'],
'no-else-return': ['error'],
'no-empty-function': ['error'],
'no-extra-parens': ['error'],
'no-implicit-coercion': ['error'],
'no-lonely-if': ['error'],
'no-multi-spaces': ['error'],
'no-multiple-empty-lines': ['warn', { max: 2, maxBOF: 0, maxEOF: 0 }],
'no-promise-executor-return': ['error'],
'no-return-assign': ['error'],
'no-script-url': ['error'],
'no-template-curly-in-string': ['error'],
'no-trailing-spaces': ['error'],
'no-unneeded-ternary': ['error'],
'no-unreachable-loop': ['error'],
'no-unsafe-optional-chaining': ['error'],
'no-useless-return': ['error'],
'no-var': ['error'],
'no-warning-comments': ['error'],
'no-whitespace-before-property': ['error'],
'object-curly-newline': ['error', { consistent: true }],
'object-curly-spacing': ['error', 'always'],
'object-shorthand': ['error'],
'padded-blocks': ['error', 'never'],
'prefer-arrow-callback': ['error'],
'prefer-const': ['error'],
'prefer-object-spread': ['error'],
'prefer-rest-params': ['error'],
'prefer-template': ['error'],
'quote-props': ['error', 'consistent-as-needed', { keywords: false }],
'quotes': ['error', 'single', { allowTemplateLiterals: true }],
'require-atomic-updates': ['error'],
'require-await': ['error'],
'semi': ['error', 'never'],
'sort-imports': ['error', { ignoreCase: true, ignoreDeclarationSort: false, ignoreMemberSort: false }],
'sort-keys': ['error', 'asc', { caseSensitive: true, natural: false }],
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': ['error', 'never'],
'space-in-parens': ['error', 'never'],
'space-infix-ops': ['error'],
'space-unary-ops': ['error', { nonwords: false, words: true }],
'spaced-comment': ['warn', 'always'],
'switch-colon-spacing': ['error'],
'template-curly-spacing': ['error', 'never'],
'unicode-bom': ['error', 'never'],
'vue/comment-directive': 'off',
'vue/new-line-between-multi-line-property': ['error'],
'vue/no-empty-component-block': ['error'],
'vue/no-reserved-component-names': ['error'],
'vue/no-template-target-blank': ['error'],
'vue/no-unused-properties': ['error'],
'vue/no-unused-refs': ['error'],
'vue/no-useless-mustaches': ['error'],
'vue/order-in-components': ['off'],
'vue/require-name-property': ['error'],
'vue/v-for-delimiter-style': ['error'],
'wrap-iife': ['error'],
'yoda': ['error'],
},
},
]

View file

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{{ range (list "webfonts/fa-solid-900.woff2" "webfonts/fa-brands-400.woff2" "lato-v20-latin-ext_latin-regular.woff2" "lato-v20-latin-ext_latin-700.woff2") }} {{ range (list "fa-solid-900.woff2" "fa-brands-400.woff2") }}
<link <link
as="font" as="font"
crossorigin="anonymous" crossorigin="anonymous"
@ -21,17 +21,20 @@
rel="stylesheet" rel="stylesheet"
> >
<link
crossorigin="anonymous"
href="css/all.min.css"
integrity="{{ assetSRI `css/all.min.css` }}"
rel="stylesheet"
>
<title>{{ .Customize.AppTitle }}</title> <title>{{ .Customize.AppTitle }}</title>
<script nonce="{{ .InlineContentNonce }}"> <script nonce="{{ .InlineContentNonce }}">
window.getTheme = () => localStorage.getItem('set-color-scheme') || (window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark') window.getThemeFromStorage = () => localStorage.getItem('set-color-scheme') || 'auto'
window.getTheme = () => {
const colorScheme = window.getThemeFromStorage()
if (!colorScheme || colorScheme === 'auto') {
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark'
}
return colorScheme
}
window.refreshTheme = () => { window.refreshTheme = () => {
document.querySelector('html').setAttribute('data-bs-theme', window.getTheme()) document.querySelector('html').setAttribute('data-bs-theme', window.getTheme())
@ -46,8 +49,8 @@
document.addEventListener('DOMContentLoaded', () => window.refreshTheme()) document.addEventListener('DOMContentLoaded', () => window.refreshTheme())
// Template variable from Golang process // Template variable from Golang process
const maxSecretExpire = {{ .MaxSecretExpiry }} window.maxSecretExpire = {{ .MaxSecretExpiry }}
const version = "{{ .Version }}" window.version = "{{ .Version }}"
window.OTSCustomize = JSON.parse('{{ .Customize.ToJSON }}') window.OTSCustomize = JSON.parse('{{ .Customize.ToJSON }}')
window.useFormalLanguage = {{ .Customize.UseFormalLanguage | mustToJson }} window.useFormalLanguage = {{ .Customize.UseFormalLanguage | mustToJson }}
</script> </script>

32
go.mod
View file

@ -1,47 +1,45 @@
module github.com/Luzifer/ots module github.com/Luzifer/ots
go 1.23 go 1.23.0
toolchain go1.23.0 toolchain go1.24.5
replace github.com/Luzifer/ots/pkg/customization => ./pkg/customization replace github.com/Luzifer/ots/pkg/customization => ./pkg/customization
require ( require (
github.com/Luzifer/go_helpers/v2 v2.25.0 github.com/Luzifer/go_helpers/v2 v2.25.0
github.com/Luzifer/ots/pkg/customization v0.0.0-20241026124716-b41db78745de github.com/Luzifer/ots/pkg/customization v0.0.0-20250501151834-283ffa548fa8
github.com/Luzifer/rconfig/v2 v2.5.2 github.com/Luzifer/rconfig/v2 v2.6.0
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/gofrs/uuid v4.4.0+incompatible github.com/gofrs/uuid v4.4.0+incompatible
github.com/gorilla/mux v1.8.1 github.com/gorilla/mux v1.8.1
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_golang v1.22.0
github.com/redis/go-redis/v9 v9.7.0 github.com/redis/go-redis/v9 v9.11.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
) )
require ( require (
dario.cat/mergo v1.0.1 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.16.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/crypto v0.28.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.32.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

62
go.sum
View file

@ -2,12 +2,12 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Luzifer/go_helpers/v2 v2.25.0 h1:k1J4gd1+BfuokTDoWgcgib9P5mdadjzKEgbtKSVe46k= github.com/Luzifer/go_helpers/v2 v2.25.0 h1:k1J4gd1+BfuokTDoWgcgib9P5mdadjzKEgbtKSVe46k=
github.com/Luzifer/go_helpers/v2 v2.25.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc= github.com/Luzifer/go_helpers/v2 v2.25.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
github.com/Luzifer/rconfig/v2 v2.5.2 h1:4Bfp8mTrCCK/xghUmUbh/qtKiLZA6RC0tHTgqkNw1m4= github.com/Luzifer/rconfig/v2 v2.6.0 h1:ZKgsO2Wt/XZXawuAZCDkW7xszxZ8hQDTV1Wm63Jvnqk=
github.com/Luzifer/rconfig/v2 v2.5.2/go.mod h1:HnqUWg+NQh60/neUqfMDDDo5d1v8UPuhwKR1HqM4VWQ= github.com/Luzifer/rconfig/v2 v2.6.0/go.mod h1:CxISRwCV2WjO5gnUnaRGDq17u1M3TvmFgzJLr87ejtc=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=
github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -27,16 +27,16 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -53,44 +53,42 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= github.com/redis/go-redis/v9 v9.11.0 h1:E3S08Gl/nJNn5vkxd2i78wZxWAPNZgUNTp8WIJUAiIs=
github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/redis/go-redis/v9 v9.11.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY= gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8= gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -13,6 +13,7 @@ reference:
btn-reveal-secret: Show me the secret! btn-reveal-secret: Show me the secret!
btn-reveal-secret-processing: Secret is being decrypted… btn-reveal-secret-processing: Secret is being decrypted…
btn-show-explanation: How does this work? btn-show-explanation: How does this work?
btn-theme-switcher-auto: Auto
expire-default: Default Expiry expire-default: Default Expiry
expire-n-days: '{n} day | {n} days' expire-n-days: '{n} day | {n} days'
expire-n-hours: '{n} hour | {n} hours' expire-n-hours: '{n} hour | {n} hours'
@ -107,6 +108,7 @@ translations:
btn-reveal-secret: Zeig mir das Secret! btn-reveal-secret: Zeig mir das Secret!
btn-reveal-secret-processing: Secret wird entschlüsselt… btn-reveal-secret-processing: Secret wird entschlüsselt…
btn-show-explanation: Wie funktioniert das? btn-show-explanation: Wie funktioniert das?
btn-theme-switcher-auto: Auto
expire-default: Server-Standard expire-default: Server-Standard
expire-n-days: '{n} Tag | {n} Tage' expire-n-days: '{n} Tag | {n} Tage'
expire-n-hours: '{n} Stunde | {n} Stunden' expire-n-hours: '{n} Stunde | {n} Stunden'
@ -343,6 +345,7 @@ translations:
btn-reveal-secret: Toon mij het geheim! btn-reveal-secret: Toon mij het geheim!
btn-reveal-secret-processing: Geheim wordt ontsleuteld... btn-reveal-secret-processing: Geheim wordt ontsleuteld...
btn-show-explanation: Hoe werkt dit? btn-show-explanation: Hoe werkt dit?
btn-theme-switcher-auto: Auto
expire-default: Standaard vervaltermijn expire-default: Standaard vervaltermijn
expire-n-days: '{n} dag | {n} dagen' expire-n-days: '{n} dag | {n} dagen'
expire-n-hours: '{n} uur | {n} uur' expire-n-hours: '{n} uur | {n} uur'
@ -431,6 +434,7 @@ translations:
btn-reveal-secret: Pokaż mi sekret! btn-reveal-secret: Pokaż mi sekret!
btn-reveal-secret-processing: Sekret jest odszyfrowywany... btn-reveal-secret-processing: Sekret jest odszyfrowywany...
btn-show-explanation: Jak to działa? btn-show-explanation: Jak to działa?
btn-theme-switcher-auto: Auto
expire-default: Domyślne wygasanie expire-default: Domyślne wygasanie
expire-n-days: '{n} dzień | {n} dni' expire-n-days: '{n} dzień | {n} dni'
expire-n-hours: '{n} godzina | {n} godzin(y)' expire-n-hours: '{n} godzina | {n} godzin(y)'

View file

@ -32,7 +32,7 @@ var (
LogRequests bool `flag:"log-requests" default:"true" description:"Enable request logging"` LogRequests bool `flag:"log-requests" default:"true" description:"Enable request logging"`
LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"` LogLevel string `flag:"log-level" default:"info" description:"Set log level (debug, info, warning, error)"`
SecretExpiry int64 `flag:"secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"` SecretExpiry int64 `flag:"secret-expiry" default:"0" description:"Maximum expiry of the stored secrets in seconds"`
StorageType string `flag:"storage-type" default:"mem" description:"Storage to use for putting secrets to" validate:"nonzero"` StorageType string `flag:"storage-type" default:"mem" description:"Storage to use for putting secrets to" validate:"nonzero"` //revive:disable-line:struct-tag // Matches wrong validation library
VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"` VersionAndExit bool `flag:"version" default:"false" description:"Print version information and exit"`
EnableTLS bool `flag:"enable-tls" default:"false" description:"Enable HTTPS/TLS"` EnableTLS bool `flag:"enable-tls" default:"false" description:"Enable HTTPS/TLS"`
CertFile string `flag:"cert-file" default:"" description:"Path to the TLS certificate file"` CertFile string `flag:"cert-file" default:"" description:"Path to the TLS certificate file"`

4501
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,28 @@
{ {
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.25.1", "@babel/eslint-parser": "7.28.0",
"esbuild": "^0.19.4", "@types/bootstrap": "5.2.10",
"esbuild-sass-plugin": "^2.16.1", "@typescript-eslint/eslint-plugin": "8.37.0",
"esbuild-vue": "^1.2.2", "@typescript-eslint/parser": "8.37.0",
"eslint": "^8.57.0", "@vue/tsconfig": "0.7.0",
"eslint-plugin-vue": "^9.27.0", "esbuild": "0.25.6",
"vue-template-compiler": "^2.7.16" "esbuild-plugin-vue3": "0.4.2",
"esbuild-sass-plugin": "3.3.1",
"eslint": "9.31.0",
"eslint-plugin-vue": "10.3.0",
"typescript": "5.8.3",
"vue-eslint-parser": "10.2.0"
}, },
"name": "ots", "name": "ots",
"private": true, "private": true,
"dependencies": { "dependencies": {
"base64-js": "^1.5.1", "@fortawesome/fontawesome-free": "6.7.2",
"bootstrap": "^5.3.3", "base64-js": "1.5.1",
"qrcode": "^1.5.4", "bootstrap": "5.3.7",
"vue": "^2.7.16", "qrcode": "1.5.4",
"vue-i18n": "^8.28.2", "vue": "3.5.17",
"vue-router": "^3.6.5" "vue-i18n": "11.1.10",
} "vue-router": "4.5.1"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
} }

View file

@ -1,23 +1,24 @@
module github.com/Luzifer/ots/pkg/client module github.com/Luzifer/ots/pkg/client
go 1.23 go 1.23.0
toolchain go1.24.5
replace github.com/Luzifer/ots/pkg/customization => ../customization replace github.com/Luzifer/ots/pkg/customization => ../customization
require ( require (
github.com/Luzifer/go-openssl/v4 v4.2.2 github.com/Luzifer/go-openssl/v4 v4.2.4
github.com/Luzifer/ots/pkg/customization v0.0.0-20240922105511-48bf8c9ca4fe github.com/Luzifer/ots/pkg/customization v0.0.0-20250501151834-283ffa548fa8
github.com/ryanuber/go-glob v1.0.0 github.com/ryanuber/go-glob v1.0.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.10.0
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.27.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/sys v0.25.0 // indirect golang.org/x/sys v0.32.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

View file

@ -1,5 +1,5 @@
github.com/Luzifer/go-openssl/v4 v4.2.2 h1:wKF/GhSKGJtHFQYTkN61wXig7mPvDj/oPpW6MmnBpjc= github.com/Luzifer/go-openssl/v4 v4.2.4 h1:3Eu3gSeZpr8Ha+IofVnSWttCL1xejRr/lda4l4TZRWk=
github.com/Luzifer/go-openssl/v4 v4.2.2/go.mod h1:+kAwI4NpyYXoWil85gKSCEJNoCQlMeFikEMn2f+5ffc= github.com/Luzifer/go-openssl/v4 v4.2.4/go.mod h1:ykquxaR0R1Vor83/FAtGBJZZO5zswuSQTVx1FQc1bJY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -13,17 +13,15 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -9,7 +9,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v3"
) )
// Frontend has a max attachment size of 64MiB as the base64 encoding // Frontend has a max attachment size of 64MiB as the base64 encoding

View file

@ -1,11 +1,13 @@
module github.com/Luzifer/ots/pkg/customization module github.com/Luzifer/ots/pkg/customization
go 1.23 go 1.23.0
toolchain go1.24.5
require ( require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1
) )
require golang.org/x/sys v0.25.0 // indirect require golang.org/x/sys v0.32.0 // indirect

View file

@ -11,11 +11,10 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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=

14
renovate.json Normal file
View file

@ -0,0 +1,14 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"local>luzifer/renovate-config"
],
"packageRules": [
{
"enabled": false,
"matchPackageNames": [
"github.com/Luzifer/ots/pkg/**"
]
}
]
}

View file

@ -1,7 +1,9 @@
<!-- eslint-disable vue/no-v-html -->
<template> <template>
<div id="app"> <div id="app">
<app-navbar /> <app-navbar
v-model:theme="theme"
@navigate="navigate"
/>
<div class="container mt-4"> <div class="container mt-4">
<div <div
@ -19,7 +21,10 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<router-view @error="displayError" /> <router-view
@error="displayError"
@navigate="navigate"
/>
</div> </div>
</div> </div>
@ -28,15 +33,15 @@
> >
<div class="col form-text text-center"> <div class="col form-text text-center">
<span <span
v-if="!$root.customize.disablePoweredBy" v-if="!customize.disablePoweredBy"
class="mx-2" class="mx-2"
> >
{{ $t('text-powered-by') }} {{ $t('text-powered-by') }}
<a href="https://github.com/Luzifer/ots"><i class="fab fa-github" /> OTS</a> <a href="https://github.com/Luzifer/ots"><i class="fab fa-github" /> OTS</a>
{{ $root.version }} {{ version }}
</span> </span>
<span <span
v-for="link in $root.customize.footerLinks" v-for="link in customize.footerLinks"
:key="link.url" :key="link.url"
class="mx-2" class="mx-2"
> >
@ -48,26 +53,39 @@
</div> </div>
</template> </template>
<script> <script lang="ts">
import AppNavbar from './components/navbar.vue' import { isNavigationFailure, NavigationFailureType } from 'vue-router'
export default { import AppNavbar from './components/navbar.vue'
components: { import { defineComponent } from 'vue'
AppNavbar,
export default defineComponent({
components: { AppNavbar },
computed: {
isSecureEnvironment(): boolean {
return Boolean(window.crypto.subtle)
},
version(): string {
return window.version
},
}, },
created() { created() {
this.$root.navigate('/') this.navigate('/')
}, },
data() { data() {
return { return {
error: '', customize: {} as any,
error: '' as string | null,
theme: 'auto',
} }
}, },
methods: { methods: {
displayError(error) { displayError(error: string | null) {
this.error = error this.error = error
}, },
@ -80,13 +98,13 @@ export default {
const parts = hash.substring(1).split('|') const parts = hash.substring(1).split('|')
const secretId = parts[0] const secretId = parts[0]
let securePassword = null let securePassword = null as string | null
if (parts.length === 2) { if (parts.length === 2) {
securePassword = parts[1] securePassword = parts[1]
} }
this.$root.navigate({ this.navigate({
path: '/secret', path: '/secret',
query: { query: {
secretId, secretId,
@ -94,18 +112,44 @@ export default {
}, },
}) })
}, },
navigate(to: string | any): void {
this.error = ''
this.$router.replace(to)
.catch(err => {
if (isNavigationFailure(err, NavigationFailureType.duplicated)) {
// Hide duplicate nav errors
return
}
throw err
})
},
}, },
// Trigger initialization functions // Trigger initialization functions
mounted() { mounted() {
this.customize = window.OTSCustomize
window.onhashchange = this.hashLoad window.onhashchange = this.hashLoad
this.hashLoad() this.hashLoad()
if (!this.$root.isSecureEnvironment) { if (!this.isSecureEnvironment) {
this.error = this.$t('alert-insecure-environment') this.error = this.$t('alert-insecure-environment')
} }
this.theme = window.getThemeFromStorage()
window.matchMedia('(prefers-color-scheme: light)')
.addEventListener('change', () => {
window.refreshTheme()
})
}, },
name: 'App', name: 'App',
}
watch: {
theme(to): void {
window.setTheme(to)
},
},
})
</script> </script>

View file

@ -8,10 +8,13 @@
<i :class="{'fas fa-fw fa-clipboard': !copyToClipboardSuccess, 'fas fa-fw fa-circle-check': copyToClipboardSuccess}" /> <i :class="{'fas fa-fw fa-clipboard': !copyToClipboardSuccess, 'fas fa-fw fa-circle-check': copyToClipboardSuccess}" />
</button> </button>
</template> </template>
<script>
export default { <script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
computed: { computed: {
hasClipboard() { hasClipboard(): boolean {
return Boolean(navigator.clipboard && navigator.clipboard.writeText) return Boolean(navigator.clipboard && navigator.clipboard.writeText)
}, },
}, },
@ -23,7 +26,7 @@ export default {
}, },
methods: { methods: {
copy() { copy(): void {
navigator.clipboard.writeText(this.content) navigator.clipboard.writeText(this.content)
.then(() => { .then(() => {
this.copyToClipboardSuccess = true this.copyToClipboardSuccess = true
@ -43,5 +46,5 @@ export default {
type: String, type: String,
}, },
}, },
} })
</script> </script>

View file

@ -1,4 +1,3 @@
<!-- eslint-disable vue/no-v-html -->
<template> <template>
<!-- Creation disabled --> <!-- Creation disabled -->
<div <div
@ -36,11 +35,11 @@
v-model="secret" v-model="secret"
class="form-control" class="form-control"
:rows="2" :rows="2"
@pasteFile="handlePasteFile" @paste-file="handlePasteFile"
/> />
</div> </div>
<div <div
v-if="!$root.customize.disableFileAttachment" v-if="!customize.disableFileAttachment"
class="col-12 mb-3" class="col-12 mb-3"
> >
<label for="createSecretFiles">{{ $t('label-secret-files') }}</label> <label for="createSecretFiles">{{ $t('label-secret-files') }}</label>
@ -50,7 +49,7 @@
class="form-control" class="form-control"
type="file" type="file"
multiple multiple
:accept="$root.customize.acceptedFileTypes" :accept="customize.acceptedFileTypes"
@change="handleSelectFiles" @change="handleSelectFiles"
> >
<div class="form-text"> <div class="form-text">
@ -74,7 +73,7 @@
:can-delete="true" :can-delete="true"
:track-download="false" :track-download="false"
:files="attachedFiles" :files="attachedFiles"
@fileClicked="deleteFile" @file-clicked="deleteFile"
/> />
</div> </div>
<div class="col-md-6 col-12 order-2 order-md-1"> <div class="col-md-6 col-12 order-2 order-md-1">
@ -93,7 +92,7 @@
</button> </button>
</div> </div>
<div <div
v-if="!$root.customize.disableExpiryOverride" v-if="!customize.disableExpiryOverride"
class="col-md-6 col-12 order-1 order-md-2" class="col-md-6 col-12 order-1 order-md-2"
> >
<div class="row mb-3 justify-content-end"> <div class="row mb-3 justify-content-end">
@ -108,7 +107,7 @@
> >
<option <option
v-for="opt in expiryChoices" v-for="opt in expiryChoices"
:key="opt.value" :key="opt.value || 'null'"
:value="opt.value" :value="opt.value"
> >
{{ opt.text }} {{ opt.text }}
@ -121,11 +120,11 @@
</div> </div>
</div> </div>
</template> </template>
<script>
/* global maxSecretExpire */
import appCrypto from '../crypto.js' <script lang="ts">
import appCrypto from '../crypto.ts'
import { bytesToHuman } from '../helpers' import { bytesToHuman } from '../helpers'
import { defineComponent } from 'vue'
import FilesDisplay from './fileDisplay.vue' import FilesDisplay from './fileDisplay.vue'
import GrowArea from './growarea.vue' import GrowArea from './growarea.vue'
import OTSMeta from '../ots-meta' import OTSMeta from '../ots-meta'
@ -157,30 +156,35 @@ const internalMaxFileSize = 64 * 1024 * 1024 // 64 MiB
const passwordCharset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' const passwordCharset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
const passwordLength = 20 const passwordLength = 20
export default { export default defineComponent({
components: { FilesDisplay, GrowArea }, components: { FilesDisplay, GrowArea },
computed: { computed: {
canCreate() { canCreate(): boolean {
return (this.secret.trim().length > 0 || this.selectedFileMeta.length > 0) && !this.maxFileSizeExceeded && !this.invalidFilesSelected return (this.secret.trim().length > 0 || this.selectedFileMeta.length > 0) && !this.maxFileSizeExceeded && !this.invalidFilesSelected
}, },
expiryChoices() { customize(): any {
const choices = [{ text: this.$t('expire-default'), value: null }] return window.OTSCustomize || {}
for (const choice of this.$root.customize.expiryChoices || defaultExpiryChoices) { },
if (maxSecretExpire > 0 && choice > maxSecretExpire) {
expiryChoices(): Record<string, string | null>[] {
const choices = [{ text: this.$t('expire-default'), value: null as string | null }]
for (const choice of this.customize.expiryChoices || defaultExpiryChoices) {
if (window.maxSecretExpire > 0 && choice > window.maxSecretExpire) {
continue continue
} }
const option = { value: choice } const option = { text: '', value: choice }
if (choice >= 86400) { if (choice >= 86400) {
option.text = this.$tc('expire-n-days', Math.round(choice / 86400)) option.text = this.$t('expire-n-days', Math.round(choice / 86400))
} else if (choice >= 3600) { } else if (choice >= 3600) {
option.text = this.$tc('expire-n-hours', Math.round(choice / 3600)) option.text = this.$t('expire-n-hours', Math.round(choice / 3600))
} else if (choice >= 60) { } else if (choice >= 60) {
option.text = this.$tc('expire-n-minutes', Math.round(choice / 60)) option.text = this.$t('expire-n-minutes', Math.round(choice / 60))
} else { } else {
option.text = this.$tc('expire-n-seconds', choice) option.text = this.$t('expire-n-seconds', choice)
} }
choices.push(option) choices.push(option)
@ -189,13 +193,13 @@ export default {
return choices return choices
}, },
invalidFilesSelected() { invalidFilesSelected(): boolean {
if (this.$root.customize.acceptedFileTypes === '') { if (this.customize.acceptedFileTypes === '') {
// No limitation configured, no need to check // No limitation configured, no need to check
return false return false
} }
const accepted = this.$root.customize.acceptedFileTypes.split(',') const accepted = this.customize.acceptedFileTypes.split(',')
for (const fm of this.selectedFileMeta) { for (const fm of this.selectedFileMeta) {
let isAccepted = false let isAccepted = false
@ -213,20 +217,24 @@ export default {
return false return false
}, },
maxFileSize() { isSecureEnvironment(): boolean {
return this.$root.customize.maxAttachmentSizeTotal === 0 ? internalMaxFileSize : Math.min(internalMaxFileSize, this.$root.customize.maxAttachmentSizeTotal) return Boolean(window.crypto.subtle)
}, },
maxFileSizeExceeded() { maxFileSize(): number {
return this.customize.maxAttachmentSizeTotal === 0 ? internalMaxFileSize : Math.min(internalMaxFileSize, this.customize.maxAttachmentSizeTotal)
},
maxFileSizeExceeded(): boolean {
return this.fileSize > this.maxFileSize return this.fileSize > this.maxFileSize
}, },
showCreateForm() { showCreateForm(): boolean {
return this.canWrite && this.$root.isSecureEnvironment return this.canWrite && this.isSecureEnvironment
}, },
}, },
created() { created(): void {
this.checkWriteAccess() this.checkWriteAccess()
}, },
@ -243,11 +251,13 @@ export default {
} }
}, },
emits: ['error', 'navigate'],
methods: { methods: {
bytesToHuman, bytesToHuman,
checkWriteAccess() { checkWriteAccess(): Promise<void> {
fetch('api/isWritable', { return fetch('api/isWritable', {
credentials: 'same-origin', credentials: 'same-origin',
method: 'GET', method: 'GET',
redirect: 'error', redirect: 'error',
@ -264,9 +274,9 @@ export default {
}, },
// createSecret executes the secret creation after encrypting the secret // createSecret executes the secret creation after encrypting the secret
createSecret() { createSecret(): void {
if (!this.canCreate) { if (!this.canCreate) {
return false return
} }
// Encoding large files takes a while, prevent duplicate click on "create" // Encoding large files takes a while, prevent duplicate click on "create"
@ -309,7 +319,7 @@ export default {
resp.json() resp.json()
.then(data => { .then(data => {
this.$root.navigate({ this.$emit('navigate', {
path: '/display-secret-url', path: '/display-secret-url',
query: { query: {
expiresAt: data.expires_at, expiresAt: data.expires_at,
@ -324,16 +334,14 @@ export default {
this.$emit('error', this.$t('alert-something-went-wrong')) this.$emit('error', this.$t('alert-something-went-wrong'))
}) })
}) })
return false
}, },
deleteFile(fileId) { deleteFile(fileId: string): void {
this.attachedFiles = [...this.attachedFiles].filter(file => file.id !== fileId) this.attachedFiles = [...this.attachedFiles].filter(file => file.id !== fileId)
this.updateFileMeta() this.updateFileMeta()
}, },
handlePasteFile(file) { handlePasteFile(file: File): void {
this.attachedFiles.push({ this.attachedFiles.push({
fileObj: file, fileObj: file,
id: window.crypto.randomUUID(), id: window.crypto.randomUUID(),
@ -344,7 +352,7 @@ export default {
this.updateFileMeta() this.updateFileMeta()
}, },
handleSelectFiles() { handleSelectFiles(): void {
for (const file of this.$refs.createSecretFiles.files) { for (const file of this.$refs.createSecretFiles.files) {
this.attachedFiles.push({ this.attachedFiles.push({
fileObj: file, fileObj: file,
@ -359,7 +367,7 @@ export default {
this.$refs.createSecretFiles.value = '' this.$refs.createSecretFiles.value = ''
}, },
isAcceptedBy(fileMeta, accept) { isAcceptedBy(fileMeta: any, accept: string): boolean {
if (/^(?:[a-z]+|\*)\/(?:[a-zA-Z0-9.+_-]+|\*)$/.test(accept)) { if (/^(?:[a-z]+|\*)\/(?:[a-zA-Z0-9.+_-]+|\*)$/.test(accept)) {
// That's likely supposed to be a mime-type // That's likely supposed to be a mime-type
return RegExp(`^${accept.replaceAll('*', '.*')}$`).test(fileMeta.type) return RegExp(`^${accept.replaceAll('*', '.*')}$`).test(fileMeta.type)
@ -372,7 +380,7 @@ export default {
return false return false
}, },
updateFileMeta() { updateFileMeta(): void {
let cumSize = 0 let cumSize = 0
for (const f of this.attachedFiles) { for (const f of this.attachedFiles) {
cumSize += f.size cumSize += f.size
@ -387,5 +395,5 @@ export default {
}, },
name: 'AppCreate', name: 'AppCreate',
} })
</script> </script>

View file

@ -17,7 +17,7 @@
type="text" type="text"
readonly readonly
:value="secretUrl" :value="secretUrl"
@focus="$refs.secretUrl.select()" @focus="selectURL"
> >
<app-clipboard-button <app-clipboard-button
:content="secretUrl" :content="secretUrl"
@ -46,14 +46,17 @@
</div> </div>
</div> </div>
</template> </template>
<script>
<script lang="ts">
import appClipboardButton from './clipboard-button.vue' import appClipboardButton from './clipboard-button.vue'
import appQrButton from './qr-button.vue' import appQrButton from './qr-button.vue'
import { defineComponent } from 'vue'
export default { export default defineComponent({
components: { appClipboardButton, appQrButton }, components: { appClipboardButton, appQrButton },
computed: { computed: {
secretUrl() { secretUrl(): string {
return [ return [
window.location.href.split('#')[0], window.location.href.split('#')[0],
encodeURIComponent([ encodeURIComponent([
@ -72,20 +75,25 @@ export default {
}, },
methods: { methods: {
burnSecret() { burnSecret(): Promise<void> {
return fetch(`api/get/${this.secretId}`) return fetch(`api/get/${this.secretId}`)
.then(() => { .then(() => {
this.burned = true this.burned = true
}) })
}, },
selectURL(): void {
this.$refs.secretUrl.select()
},
}, },
mounted() { mounted(): void {
// Give the interface a moment to transistion and focus // Give the interface a moment to transistion and focus
window.setTimeout(() => this.$refs.secretUrl.focus(), 100) window.setTimeout(() => this.$refs.secretUrl.focus(), 100)
}, },
name: 'AppDisplayURL', name: 'AppDisplayURL',
props: { props: {
expiresAt: { expiresAt: {
default: null, default: null,
@ -103,5 +111,5 @@ export default {
type: String, type: String,
}, },
}, },
} })
</script> </script>

View file

@ -1,4 +1,3 @@
<!-- eslint-disable vue/no-v-html -->
<template> <template>
<div class="card border-primary-subtle mb-3"> <div class="card border-primary-subtle mb-3">
<div <div
@ -8,7 +7,7 @@
<div class="card-body"> <div class="card-body">
<ul> <ul>
<li <li
v-for="(explanation, idx) in $t('items-explanation')" v-for="(explanation, idx) in $tm('items-explanation')"
:key="`idx${idx}`" :key="`idx${idx}`"
> >
{{ explanation }} {{ explanation }}
@ -17,6 +16,11 @@
</div> </div>
</div> </div>
</template> </template>
<script>
export default { name: 'AppExplanation' } <script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'AppExplanation',
})
</script> </script>

View file

@ -30,20 +30,23 @@
</div> </div>
</template> </template>
<script> <script lang="ts">
import { bytesToHuman } from '../helpers' import { bytesToHuman } from '../helpers'
import { defineComponent } from 'vue'
export default { export default defineComponent({
data() { data() {
return { return {
hasDownloaded: {}, hasDownloaded: {},
} }
}, },
emits: ['fileClicked'],
methods: { methods: {
bytesToHuman, bytesToHuman,
fasFileType(type) { fasFileType(type: string): string {
return [ return [
'fas', 'fas',
'fa-fw', 'fa-fw',
@ -60,8 +63,8 @@ export default {
].join(' ') ].join(' ')
}, },
handleClick(file) { handleClick(file: any): void {
this.$set(this.hasDownloaded, file.id, true) this.hasDownloaded[file.id] = true
this.$emit('fileClicked', file.id) this.$emit('fileClicked', file.id)
}, },
}, },
@ -77,7 +80,7 @@ export default {
files: { files: {
required: true, required: true,
type: Array, type: Array<any>,
}, },
trackDownload: { trackDownload: {
@ -86,5 +89,5 @@ export default {
type: Boolean, type: Boolean,
}, },
}, },
} })
</script> </script>

View file

@ -7,8 +7,10 @@
/> />
</template> </template>
<script> <script lang="ts">
export default { import { defineComponent } from 'vue'
export default defineComponent({
created() { created() {
this.data = this.value this.data = this.value
}, },
@ -19,8 +21,14 @@ export default {
} }
}, },
emits: ['input', 'pasteFile'],
methods: { methods: {
changeSize() { changeSize(): void {
if (!this.$refs.area) {
return
}
const verticalBorderSize = this.getStyle('borderTopWidth') + this.getStyle('borderBottomWidth') || 0 const verticalBorderSize = this.getStyle('borderTopWidth') + this.getStyle('borderBottomWidth') || 0
const verticalPaddingSize = this.getStyle('paddingTop') + this.getStyle('paddingBottom') || 0 const verticalPaddingSize = this.getStyle('paddingTop') + this.getStyle('paddingBottom') || 0
@ -31,12 +39,12 @@ export default {
this.$refs.area.style.height = `${newHeight}px` this.$refs.area.style.height = `${newHeight}px`
}, },
getStyle(name) { getStyle(name: string): number {
return parseInt(getComputedStyle(this.$refs.area, null)[name]) return parseInt(getComputedStyle(this.$refs.area, null)[name])
}, },
handlePaste(evt) { handlePaste(evt: ClipboardEvent): void {
if ([...evt.clipboardData.items] if ([...evt.clipboardData?.items || []]
.filter(item => item.kind !== 'string') .filter(item => item.kind !== 'string')
.length === 0) { .length === 0) {
return return
@ -49,7 +57,7 @@ export default {
evt.stopPropagation() evt.stopPropagation()
evt.preventDefault() evt.preventDefault()
for (const item of evt.clipboardData.items) { for (const item of evt.clipboardData?.items || []) {
if (item.kind === 'string') { if (item.kind === 'string') {
continue continue
} }
@ -59,8 +67,7 @@ export default {
}, },
}, },
mounted(): void {
mounted() {
this.changeSize() this.changeSize()
}, },
@ -92,5 +99,5 @@ export default {
} }
}, },
}, },
} })
</script> </script>

View file

@ -4,18 +4,18 @@
<a <a
class="navbar-brand" class="navbar-brand"
href="#" href="#"
@click.prevent="$root.navigate('/')" @click.prevent="$emit('navigate', '/')"
> >
<i <i
v-if="!appIcon" v-if="!appIcon"
class="fas fa-user-secret mr-1" class="fas fa-user-secret me-1"
/> />
<img <img
v-else v-else
class="mr-1" class="me-1"
:src="appIcon" :src="appIcon"
> >
<span v-if="!$root.customize.disableAppTitle">{{ $root.customize.appTitle }}</span> <span v-if="!customize.disableAppTitle">{{ customize.appTitle }}</span>
</a> </a>
<button <button
@ -39,7 +39,7 @@
<a <a
class="nav-link" class="nav-link"
href="#" href="#"
@click.prevent="$root.navigate('/explanation')" @click.prevent="$emit('navigate', '/explanation')"
> >
<i class="fas fa-circle-info" /> {{ $t('btn-show-explanation') }} <i class="fas fa-circle-info" /> {{ $t('btn-show-explanation') }}
</a> </a>
@ -48,46 +48,122 @@
<a <a
class="nav-link" class="nav-link"
href="#" href="#"
@click.prevent="$root.navigate('/')" @click.prevent="$emit('navigate', '/')"
> >
<i class="fas fa-plus" /> {{ $t('btn-new-secret') }} <i class="fas fa-plus" /> {{ $t('btn-new-secret') }}
</a> </a>
</li> </li>
</ul> </ul>
<form <form
v-if="!$root.customize.disableThemeSwitcher" v-if="!customize.disableThemeSwitcher"
class="d-flex align-items-center" class="d-flex align-items-center btn-group"
> >
<i class="fas fa-sun me-2" />
<div class="form-check form-switch">
<input <input
id="themeswitch" id="theme-light"
v-model="$root.darkTheme" v-model="intTheme"
class="form-check-input" type="radio"
type="checkbox" name="theme"
role="switch" class="btn-check"
value="light"
>
<label
class="btn btn-outline-secondary btn-sm"
for="theme-light"
>
<i class="fas fa-sun" />
</label>
<input
id="theme-auto"
v-model="intTheme"
type="radio"
name="theme"
class="btn-check"
value="auto"
>
<label
class="btn btn-outline-secondary btn-sm"
for="theme-auto"
>
{{ $t('btn-theme-switcher-auto') }}
</label>
<input
id="theme-dark"
v-model="intTheme"
type="radio"
name="theme"
class="btn-check"
value="dark"
>
<label
class="btn btn-outline-secondary btn-sm"
for="theme-dark"
> >
</div>
<i class="fas fa-moon" /> <i class="fas fa-moon" />
</label>
</form> </form>
</div> </div>
</div> </div>
</nav> </nav>
</template> </template>
<script> <script lang="ts">
export default { import { defineComponent } from 'vue'
computed: {
appIcon() {
// Use specified icon or fall back to null
const appIcon = this.$root.customize.appIcon || null
// Use specified icon or fall back to light-mode appIcon (which might be null)
const darkIcon = this.$root.customize.appIconDark || appIcon
return this.$root.darkTheme ? darkIcon : appIcon export default defineComponent({
computed: {
appIcon(): string {
// Use specified icon or fall back to null
const appIcon = this.$parent.customize.appIcon || null
// Use specified icon or fall back to light-mode appIcon (which might be null)
const darkIcon = this.$parent.customize.appIconDark || appIcon
return this.$root.theme === 'dark' ? darkIcon : appIcon
}, },
customize(): any {
return this.$parent.customize || {}
},
},
data() {
return {
intTheme: '',
}
},
emits: ['navigate', 'update:theme'],
mounted(): void {
this.intTheme = this.theme
}, },
name: 'AppNavbar', name: 'AppNavbar',
}
props: {
theme: {
required: true,
type: String,
},
},
watch: {
intTheme(to: string, from: string): void {
if (to === from) {
return
}
this.$emit('update:theme', to)
},
theme(to: string, from: string): void {
if (to === from) {
return
}
this.intTheme = to
},
},
})
</script> </script>

View file

@ -1,6 +1,6 @@
<template> <template>
<button <button
v-if="!$root.customize.disableQRSupport" v-if="!customize.disableQRSupport"
id="secret-url-qrcode" id="secret-url-qrcode"
ref="qrButton" ref="qrButton"
class="btn btn-secondary" class="btn btn-secondary"
@ -9,11 +9,19 @@
<i class="fas fa-qrcode" /> <i class="fas fa-qrcode" />
</button> </button>
</template> </template>
<script>
<script lang="ts">
import { defineComponent } from 'vue'
import { Popover } from 'bootstrap' import { Popover } from 'bootstrap'
import qrcode from 'qrcode' import qrcode from 'qrcode'
export default { export default defineComponent({
computed: {
customize(): any {
return window.OTSCustomize || {}
},
},
data() { data() {
return { return {
qrDataURL: null, qrDataURL: null,
@ -21,8 +29,8 @@ export default {
}, },
methods: { methods: {
generateQR() { generateQR(): void {
if (this.$root.customize.disableQRSupport) { if (window.OTSCustomize.disableQRSupport) {
return return
} }
@ -33,7 +41,7 @@ export default {
}, },
}, },
mounted() { mounted(): void {
this.generateQR() this.generateQR()
}, },
@ -51,7 +59,7 @@ export default {
this.generateQR() this.generateQR()
}, },
qrDataURL(to) { qrDataURL(to: string): void {
if (this.popover) { if (this.popover) {
this.popover.dispose() this.popover.dispose()
} }
@ -69,5 +77,5 @@ export default {
}) })
}, },
}, },
} })
</script> </script>

View file

@ -1,4 +1,3 @@
<!-- eslint-disable vue/no-v-html -->
<template> <template>
<div class="card border-primary-subtle mb-3"> <div class="card border-primary-subtle mb-3">
<div <div
@ -44,7 +43,7 @@
/> />
<a <a
class="btn btn-secondary" class="btn btn-secondary"
:href="secretContentBlobURL" :href="secretContentBlobURL || ''"
download download
:title="$t('tooltip-download-as-file')" :title="$t('tooltip-download-as-file')"
> >
@ -63,15 +62,17 @@
</div> </div>
</div> </div>
</template> </template>
<script>
<script lang="ts">
import appClipboardButton from './clipboard-button.vue' import appClipboardButton from './clipboard-button.vue'
import appCrypto from '../crypto.js' import appCrypto from '../crypto.ts'
import appQrButton from './qr-button.vue' import appQrButton from './qr-button.vue'
import { defineComponent } from 'vue'
import FilesDisplay from './fileDisplay.vue' import FilesDisplay from './fileDisplay.vue'
import GrowArea from './growarea.vue' import GrowArea from './growarea.vue'
import OTSMeta from '../ots-meta' import OTSMeta from '../ots-meta'
export default { export default defineComponent({
components: { FilesDisplay, GrowArea, appClipboardButton, appQrButton }, components: { FilesDisplay, GrowArea, appClipboardButton, appQrButton },
data() { data() {
@ -84,9 +85,11 @@ export default {
} }
}, },
emits: ['error'],
methods: { methods: {
// requestSecret requests the encrypted secret from the backend // requestSecret requests the encrypted secret from the backend
requestSecret() { requestSecret(): void {
this.secretLoading = true this.secretLoading = true
window.history.replaceState({}, '', window.location.href.split('#')[0]) window.history.replaceState({}, '', window.location.href.split('#')[0])
fetch(`api/get/${this.secretId}`) fetch(`api/get/${this.secretId}`)
@ -163,5 +166,5 @@ export default {
this.secretContentBlobURL = window.URL.createObjectURL(new Blob([to], { type: 'text/plain' })) this.secretContentBlobURL = window.URL.createObjectURL(new Blob([to], { type: 'text/plain' }))
}, },
}, },
} })
</script> </script>

View file

@ -1,14 +1,13 @@
import base64 from 'base64-js' import base64 from 'base64-js'
const opensslBanner = new Uint8Array(new TextEncoder('utf8').encode('Salted__')) const opensslBanner = new Uint8Array(new TextEncoder().encode('Salted__'))
const pbkdf2Params = { hash: 'SHA-512', iterations: 300000, name: 'PBKDF2' } const pbkdf2Params = { hash: 'SHA-512', iterations: 300000, name: 'PBKDF2' }
/** /**
* @param {String} cipherText Encrypted data in base64 encoded form * @param {string} cipherText Encrypted data in base64 encoded form
* @param {String} passphrase Encryption passphrase used for key-derivation * @param {string} passphrase Encryption passphrase used for key-derivation
* @returns String
*/ */
function dec(cipherText, passphrase) { function dec(cipherText: string, passphrase: string): Promise<string> {
return decrypt(passphrase, cipherText) return decrypt(passphrase, cipherText)
} }
/** /**
@ -17,7 +16,7 @@ function dec(cipherText, passphrase) {
* @param {String} passphrase Encryption passphrase used for key-derivation * @param {String} passphrase Encryption passphrase used for key-derivation
* @returns String * @returns String
*/ */
function enc(plainText, passphrase) { function enc(plainText: string, passphrase: string): Promise<string> {
return encrypt(passphrase, generateSalt(), plainText) return encrypt(passphrase, generateSalt(), plainText)
} }
@ -26,7 +25,7 @@ function enc(plainText, passphrase) {
* @param {String} encData Encrypted data in base64 encoded form * @param {String} encData Encrypted data in base64 encoded form
* @returns String * @returns String
*/ */
function decrypt(passphrase, encData) { function decrypt(passphrase: string, encData: string): Promise<string> {
const data = base64.toByteArray(encData) const data = base64.toByteArray(encData)
return deriveKey(passphrase, data.slice(8, 16)) return deriveKey(passphrase, data.slice(8, 16))
@ -40,8 +39,8 @@ function decrypt(passphrase, encData) {
* @param {Uint8Array} salt * @param {Uint8Array} salt
* @returns Object * @returns Object
*/ */
function deriveKey(passphrase, salt) { function deriveKey(passphrase: string, salt: Uint8Array): any {
return window.crypto.subtle.importKey('raw', new TextEncoder('utf8').encode(passphrase), 'PBKDF2', false, ['deriveBits']) return window.crypto.subtle.importKey('raw', new TextEncoder().encode(passphrase), 'PBKDF2', false, ['deriveBits'])
.then(passwordKey => window.crypto.subtle.deriveBits({ ...pbkdf2Params, salt }, passwordKey, 384)) .then(passwordKey => window.crypto.subtle.deriveBits({ ...pbkdf2Params, salt }, passwordKey, 384))
.then(key => window.crypto.subtle.importKey('raw', key.slice(0, 32), { name: 'AES-CBC' }, false, ['encrypt', 'decrypt']) .then(key => window.crypto.subtle.importKey('raw', key.slice(0, 32), { name: 'AES-CBC' }, false, ['encrypt', 'decrypt'])
.then(aesKey => ({ iv: key.slice(32, 48), key: aesKey }))) .then(aesKey => ({ iv: key.slice(32, 48), key: aesKey })))
@ -53,9 +52,9 @@ function deriveKey(passphrase, salt) {
* @param {String} plainData Data to encrypt * @param {String} plainData Data to encrypt
* @returns String * @returns String
*/ */
function encrypt(passphrase, salt, plainData) { function encrypt(passphrase: string, salt: Uint8Array, plainData: string): Promise<string> {
return deriveKey(passphrase, salt) return deriveKey(passphrase, salt)
.then(({ iv, key }) => window.crypto.subtle.encrypt({ iv, name: 'AES-CBC' }, key, new TextEncoder('utf8').encode(plainData))) .then(({ iv, key }) => window.crypto.subtle.encrypt({ iv, name: 'AES-CBC' }, key, new TextEncoder().encode(plainData)))
.then(encData => new Uint8Array([...opensslBanner, ...salt, ...new Uint8Array(encData)])) .then(encData => new Uint8Array([...opensslBanner, ...salt, ...new Uint8Array(encData)]))
.then(data => base64.fromByteArray(data)) .then(data => base64.fromByteArray(data))
} }
@ -65,7 +64,7 @@ function encrypt(passphrase, salt, plainData) {
* *
* @returns Uint8Array * @returns Uint8Array
*/ */
function generateSalt() { function generateSalt(): Uint8Array {
const salt = new Uint8Array(8) // Salt MUST consist of 8 byte const salt = new Uint8Array(8) // Salt MUST consist of 8 byte
return window.crypto.getRandomValues(salt) return window.crypto.getRandomValues(salt)
} }

14
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,14 @@
export { }
declare global {
interface Window {
getTheme: () => string;
getThemeFromStorage: () => string;
maxSecretExpire: number;
OTSCustomize: any;
refreshTheme: () => void;
setTheme: (string) => void;
useFormalLanguage: boolean;
version: string;
}
}

View file

@ -1,9 +1,9 @@
/** /**
* Converts number of bytes into human format (524288 -> "512.0 KiB") * Converts number of bytes into human format (524288 -> "512.0 KiB")
* @param {Number} bytes Byte amount to convert into human readable format * @param {number} bytes Byte amount to convert into human readable format
* @returns String * @returns string
*/ */
function bytesToHuman(bytes) { function bytesToHuman(bytes: number): string {
for (const t of [ for (const t of [
{ thresh: 1024 * 1024, unit: 'MiB' }, { thresh: 1024 * 1024, unit: 'MiB' },
{ thresh: 1024, unit: 'KiB' }, { thresh: 1024, unit: 'KiB' },

16
src/i18n.ts Normal file
View file

@ -0,0 +1,16 @@
import { createI18n } from "vue-i18n";
import messages from './langs/langs.js'
const cookieSet = Object.fromEntries(document.cookie.split('; ')
.map(el => el.split('=')
.map(el => decodeURIComponent(el))))
const i18n = createI18n({
legacy: false,
fallbackLocale: 'en',
locale: cookieSet.lang || navigator?.language || 'en',
messages,
})
export default i18n

View file

@ -6,11 +6,11 @@ export default {
'ca': JSON.parse('{"alert-secret-not-found":"Aquest no és el secret que busques\u0026hellip; - Si esperaves que el secret estiguera ací, és possible que s\'haja vist compromés, ja que una altra persona podria haver obert l\'enllaç en comptes de tu.","alert-something-went-wrong":"Alguna cosa ha eixit malament. Ens sap molt greu\u0026hellip;","btn-create-secret":"Crea el secret!","btn-create-secret-processing":"El secret s\'està creant...","btn-new-secret":"Nou secret","btn-reveal-secret":"Mostra\'m el secret!","btn-reveal-secret-processing":"El secret s\'està desxifrant...","btn-show-explanation":"Com funciona?","expire-default":"Caducitat predeterminada","expire-n-days":"{n} dia | {n} dies","expire-n-hours":"{n} hora | {n} hores","expire-n-minutes":"{n} minut | {n} minuts","expire-n-seconds":"{n} segon | {n} segons","items-explanation":["Introduïx un secret en el formulari que hi ha en aquesta pàgina","El teu navegador xifra el secret utilitzant una contrasenya generada","Únicament s\'envia al servidor el secret xifrat (mai s\'envien ni el secret sense xifrar ni la contrasenya!)","El servidor emmagatzema el secret xifrat durant un temps limitat","Envia al destinatari l\'enllaç mostrat, que conté l\'identificador del secret i la contrasenya de desxifrat","El destinatari pot veure el secret una sola vegada: si no pot, el secret podria haver sigut vist per una altra persona!","Quan s\'ha obtingut per primera i única vegada el secret xifrat, s\'elimina del servidor"],"label-expiry":"Caduca en:","label-secret-data":"Informació secreta:","label-secret-files":"Adjuntar arxius:","text-attached-files":"El remitent ha adjuntat arxius al secret. Assegura\'t que confies en aquesta persona, ja que els arxius no han sigut revisats.","text-burn-hint":"Per favor, recorda no accedir a aquest enllaç tu mateix, ja que això destruiria el secret. Només has de passar-li\'l a una altra persona!","text-burn-time":"Si no es mostra abans, aquest secret s\'eliminarà automàticament:","text-hint-burned":"\u003cstrong\u003eAtenció:\u003c/strong\u003e Només veuràs això una vegada. Quan recarregues la pàgina, el secret desapareixerà, així que copia\'l ja\u0026hellip;","text-max-filesize":"Mida màxima: {maxSize}","text-max-filesize-exceeded":"Els arxius seleccionats són massa grans per adjuntar-los: {curSize} / {maxSize}","text-powered-by":"Funciona amb","text-pre-reveal-hint":"Per a mostrar el secret prem aquest botó, però tingues en compte que en fer-ho es destruirà. Només pots veure\'l una vegada!","text-pre-url":"El teu secret ha sigut creat i emmagatzemat en el següent enllaç:","text-secret-create-disabled":"La creació de nous secrets està desactivada en aquesta instància.","title-explanation":"Així és com funciona\u0026hellip;","title-new-secret":"Crea un nou secret","title-reading-secret":"Obtenint el teu secret\u0026hellip;","title-secret-create-disabled":"S\'ha desactivat la creació de secrets...","title-secret-created":"Secret creat!"}'), 'ca': JSON.parse('{"alert-secret-not-found":"Aquest no és el secret que busques\u0026hellip; - Si esperaves que el secret estiguera ací, és possible que s\'haja vist compromés, ja que una altra persona podria haver obert l\'enllaç en comptes de tu.","alert-something-went-wrong":"Alguna cosa ha eixit malament. Ens sap molt greu\u0026hellip;","btn-create-secret":"Crea el secret!","btn-create-secret-processing":"El secret s\'està creant...","btn-new-secret":"Nou secret","btn-reveal-secret":"Mostra\'m el secret!","btn-reveal-secret-processing":"El secret s\'està desxifrant...","btn-show-explanation":"Com funciona?","expire-default":"Caducitat predeterminada","expire-n-days":"{n} dia | {n} dies","expire-n-hours":"{n} hora | {n} hores","expire-n-minutes":"{n} minut | {n} minuts","expire-n-seconds":"{n} segon | {n} segons","items-explanation":["Introduïx un secret en el formulari que hi ha en aquesta pàgina","El teu navegador xifra el secret utilitzant una contrasenya generada","Únicament s\'envia al servidor el secret xifrat (mai s\'envien ni el secret sense xifrar ni la contrasenya!)","El servidor emmagatzema el secret xifrat durant un temps limitat","Envia al destinatari l\'enllaç mostrat, que conté l\'identificador del secret i la contrasenya de desxifrat","El destinatari pot veure el secret una sola vegada: si no pot, el secret podria haver sigut vist per una altra persona!","Quan s\'ha obtingut per primera i única vegada el secret xifrat, s\'elimina del servidor"],"label-expiry":"Caduca en:","label-secret-data":"Informació secreta:","label-secret-files":"Adjuntar arxius:","text-attached-files":"El remitent ha adjuntat arxius al secret. Assegura\'t que confies en aquesta persona, ja que els arxius no han sigut revisats.","text-burn-hint":"Per favor, recorda no accedir a aquest enllaç tu mateix, ja que això destruiria el secret. Només has de passar-li\'l a una altra persona!","text-burn-time":"Si no es mostra abans, aquest secret s\'eliminarà automàticament:","text-hint-burned":"\u003cstrong\u003eAtenció:\u003c/strong\u003e Només veuràs això una vegada. Quan recarregues la pàgina, el secret desapareixerà, així que copia\'l ja\u0026hellip;","text-max-filesize":"Mida màxima: {maxSize}","text-max-filesize-exceeded":"Els arxius seleccionats són massa grans per adjuntar-los: {curSize} / {maxSize}","text-powered-by":"Funciona amb","text-pre-reveal-hint":"Per a mostrar el secret prem aquest botó, però tingues en compte que en fer-ho es destruirà. Només pots veure\'l una vegada!","text-pre-url":"El teu secret ha sigut creat i emmagatzemat en el següent enllaç:","text-secret-create-disabled":"La creació de nous secrets està desactivada en aquesta instància.","title-explanation":"Així és com funciona\u0026hellip;","title-new-secret":"Crea un nou secret","title-reading-secret":"Obtenint el teu secret\u0026hellip;","title-secret-create-disabled":"S\'ha desactivat la creació de secrets...","title-secret-created":"Secret creat!"}'),
'de': switchFormal( 'de': switchFormal(
JSON.parse('{"alert-insecure-environment":"Sie besuchen diese Instanz über eine unsichere Verbindung. Sie können deswegen keine Secrets erstellen oder lesen.","alert-secret-not-found":"Dieses Secret existiert nicht. - Falls Sie diesen Link noch nicht selbst geöffnet haben, könnte der Inhalt kompromittiert sein, da jemand anderes den Link geöffnet haben könnte.","alert-something-went-wrong":"Irgendwas ging schief. Entschuldigung\u0026hellip;","btn-create-secret":"Secret erstellen!","btn-create-secret-processing":"Secret wird erstellt…","btn-new-secret":"Neues Secret","btn-reveal-secret":"Secret anzeigen","btn-reveal-secret-processing":"Secret wird entschlüsselt…","btn-show-explanation":"Wie funktioniert das?","expire-default":"Server-Standard","expire-n-days":"{n} Tag | {n} Tage","expire-n-hours":"{n} Stunde | {n} Stunden","expire-n-minutes":"{n} Minute | {n} Minuten","expire-n-seconds":"{n} Sekunde | {n} Sekunden","items-explanation":["Sie geben ein Secret auf dieser Seite ein","Ihr Browser verschlüsselt das Secret mit einem generierten Passwort","Nur das verschlüsselte Secret wird an den Server geschickt (das Passwort oder das Secret im Klartext werden niemals übertragen!)","Der Server speichert das verschlüsselte Secret für eine Weile","Sie geben die angezeigte URL, welche die ID und das Passwort des Secrets enthält, an den Empfänger","Der Empfänger kann das Secret einmalig abrufen: Funktioniert das nicht, könnte jemand anderes es abgerufen haben!","Wenn das verschlüsselte Secret das erste Mal abgerufen wurde, wird es automatisch vom Server gelöscht"],"label-expiry":"Ablauf in:","label-secret-data":"Inhalt des Secrets:","label-secret-files":"Dateien Anhängen:","text-attached-files":"Der Absender hat Dateien an das Secret angehängt. Stellen Sie sicher, dass Sie dem Absender vertrauen, da die Dateien nicht geprüft wurden!","text-burn-hint":"Bitte rufen Sie die URL nicht selbst auf, da das Secret dadurch zerstört würde.","text-burn-time":"Wenn es vorher nicht eingesehen wurde, wird dieses Secret automatisch gelöscht:","text-hint-burned":"\u003cstrong\u003eAchtung:\u003c/strong\u003e Sie können das Secret nur einmal ansehen! Sobald Sie die Seite neu laden, kann das Secret nicht erneut abgerufen werden, also besser direkt kopieren und sicher abspeichern\u0026hellip;","text-invalid-files-selected":"Mindestens eine der ausgewählten Dateien ist nicht als Anhang erlaubt.","text-max-filesize":"Maximale Größe: {maxSize}","text-max-filesize-exceeded":"Die ausgewählten Dateien übersteigen die maximale Größe: {curSize} / {maxSize}","text-powered-by":"Läuft mit","text-pre-reveal-hint":"Klicken Sie auf diesen Button um das Secret anzuzeigen, bedenken Sie aber, dass das Secret nur einmal angezeigt und dabei gelöscht wird.","text-pre-url":"Das Secret wurde angelegt und unter folgender URL gespeichert:","text-secret-burned":"Das Secret wurde zerstört.","text-secret-create-disabled":"Auf dieser Instanz wurde das Erstellen neuer Secrets deaktiviert.","title-explanation":"So funktioniert es\u0026hellip;","title-new-secret":"Ein neues Secret erstellen","title-reading-secret":"Secret auslesen\u0026hellip;","title-secret-create-disabled":"Erstellen von Secrets deaktiviert…","title-secret-created":"Secret erstellt!","tooltip-burn-secret":"Secret jetzt zerstören!","tooltip-copy-to-clipboard":"In die Zwischenablage kopieren","tooltip-download-as-file":"Als Datei herunterladen"}'), JSON.parse('{"alert-insecure-environment":"Sie besuchen diese Instanz über eine unsichere Verbindung. Sie können deswegen keine Secrets erstellen oder lesen.","alert-secret-not-found":"Dieses Secret existiert nicht. - Falls Sie diesen Link noch nicht selbst geöffnet haben, könnte der Inhalt kompromittiert sein, da jemand anderes den Link geöffnet haben könnte.","alert-something-went-wrong":"Irgendwas ging schief. Entschuldigung\u0026hellip;","btn-create-secret":"Secret erstellen!","btn-create-secret-processing":"Secret wird erstellt…","btn-new-secret":"Neues Secret","btn-reveal-secret":"Secret anzeigen","btn-reveal-secret-processing":"Secret wird entschlüsselt…","btn-show-explanation":"Wie funktioniert das?","btn-theme-switcher-auto":"Auto","expire-default":"Server-Standard","expire-n-days":"{n} Tag | {n} Tage","expire-n-hours":"{n} Stunde | {n} Stunden","expire-n-minutes":"{n} Minute | {n} Minuten","expire-n-seconds":"{n} Sekunde | {n} Sekunden","items-explanation":["Sie geben ein Secret auf dieser Seite ein","Ihr Browser verschlüsselt das Secret mit einem generierten Passwort","Nur das verschlüsselte Secret wird an den Server geschickt (das Passwort oder das Secret im Klartext werden niemals übertragen!)","Der Server speichert das verschlüsselte Secret für eine Weile","Sie geben die angezeigte URL, welche die ID und das Passwort des Secrets enthält, an den Empfänger","Der Empfänger kann das Secret einmalig abrufen: Funktioniert das nicht, könnte jemand anderes es abgerufen haben!","Wenn das verschlüsselte Secret das erste Mal abgerufen wurde, wird es automatisch vom Server gelöscht"],"label-expiry":"Ablauf in:","label-secret-data":"Inhalt des Secrets:","label-secret-files":"Dateien Anhängen:","text-attached-files":"Der Absender hat Dateien an das Secret angehängt. Stellen Sie sicher, dass Sie dem Absender vertrauen, da die Dateien nicht geprüft wurden!","text-burn-hint":"Bitte rufen Sie die URL nicht selbst auf, da das Secret dadurch zerstört würde.","text-burn-time":"Wenn es vorher nicht eingesehen wurde, wird dieses Secret automatisch gelöscht:","text-hint-burned":"\u003cstrong\u003eAchtung:\u003c/strong\u003e Sie können das Secret nur einmal ansehen! Sobald Sie die Seite neu laden, kann das Secret nicht erneut abgerufen werden, also besser direkt kopieren und sicher abspeichern\u0026hellip;","text-invalid-files-selected":"Mindestens eine der ausgewählten Dateien ist nicht als Anhang erlaubt.","text-max-filesize":"Maximale Größe: {maxSize}","text-max-filesize-exceeded":"Die ausgewählten Dateien übersteigen die maximale Größe: {curSize} / {maxSize}","text-powered-by":"Läuft mit","text-pre-reveal-hint":"Klicken Sie auf diesen Button um das Secret anzuzeigen, bedenken Sie aber, dass das Secret nur einmal angezeigt und dabei gelöscht wird.","text-pre-url":"Das Secret wurde angelegt und unter folgender URL gespeichert:","text-secret-burned":"Das Secret wurde zerstört.","text-secret-create-disabled":"Auf dieser Instanz wurde das Erstellen neuer Secrets deaktiviert.","title-explanation":"So funktioniert es\u0026hellip;","title-new-secret":"Ein neues Secret erstellen","title-reading-secret":"Secret auslesen\u0026hellip;","title-secret-create-disabled":"Erstellen von Secrets deaktiviert…","title-secret-created":"Secret erstellt!","tooltip-burn-secret":"Secret jetzt zerstören!","tooltip-copy-to-clipboard":"In die Zwischenablage kopieren","tooltip-download-as-file":"Als Datei herunterladen"}'),
JSON.parse('{"alert-insecure-environment":"Du besuchst diese Instanz über eine unsichere Verbindung. Du kannst deswegen keine Secrets erstellen oder lesen.","alert-secret-not-found":"Das ist nicht das Secret, was du suchst\u0026hellip; - Falls du diesen Link noch nicht selbst geöffnet hast, könnte das Secret kompromittiert sein, da jemand anderes den Link geöffnet haben könnte.","alert-something-went-wrong":"Irgendwas ging schief. Entschuldigung\u0026hellip;","btn-create-secret":"Secret erstellen!","btn-create-secret-processing":"Secret wird erstellt…","btn-new-secret":"Neues Secret","btn-reveal-secret":"Zeig mir das Secret!","btn-reveal-secret-processing":"Secret wird entschlüsselt…","btn-show-explanation":"Wie funktioniert das?","expire-default":"Server-Standard","expire-n-days":"{n} Tag | {n} Tage","expire-n-hours":"{n} Stunde | {n} Stunden","expire-n-minutes":"{n} Minute | {n} Minuten","expire-n-seconds":"{n} Sekunde | {n} Sekunden","items-explanation":["Du gibst ein Secret auf dieser Seite ein","Dein Browser verschlüsselt das Secret mit einem generierten Passwort","Nur das verschlüsselte Secret wird an den Server geschickt (das Passwort oder das Secret im Klartext werden niemals übertragen!)","Der Server speichert das verschlüsselte Secret für eine Weile","Du gibst die angezeigte URL, welche die ID und das Passwort des Secrets enthält, an den Empfänger","Der Empfänger kann das Secret einmalig abrufen: Funktioniert das nicht, könnte jemand anderes es abgerufen haben!","Wenn das verschlüsselte Secret das erste Mal abgerufen wurde, wird es automatisch vom Server gelöscht"],"label-expiry":"Ablauf in:","label-secret-data":"Inhalt des Secrets:","label-secret-files":"Dateien Anhängen:","text-attached-files":"Der Absender hat Dateien an das Secret angehängt. Stell sicher, dass du dem Absender vertraust, da die Dateien nicht geprüft wurden!","text-burn-hint":"Bitte rufe die URL nicht selbst auf, da das Secret dadurch zerstört würde. Gib sie einfach weiter!","text-burn-time":"Wenn es vorher nicht eingesehen wurde, wird dieses Secret automatisch gelöscht:","text-hint-burned":"\u003cstrong\u003eAchtung:\u003c/strong\u003e Du kannst das nur einmal ansehen! Sobald du die Seite neu lädst, ist das Secret verschwunden, also besser direkt kopieren und sicher abspeichern\u0026hellip;","text-invalid-files-selected":"Mindestens eine der ausgewählten Dateien ist nicht als Anhang erlaubt.","text-max-filesize":"Maximale Größe: {maxSize}","text-max-filesize-exceeded":"Die ausgewählten Dateien übersteigen die maximale Größe: {curSize} / {maxSize}","text-powered-by":"Läuft mit","text-pre-reveal-hint":"Um das Secret anzuzeigen klicke diesen Button aber denk dran, dass das Secret nur einmal angezeigt und dabei gelöscht wird.","text-pre-url":"Dein Secret wurde angelegt und unter folgender URL gespeichert:","text-secret-burned":"Das Secret wurde zerstört.","text-secret-create-disabled":"Auf dieser Instanz wurde das Erstellen neuer Secrets deaktiviert.","title-explanation":"So funktioniert es\u0026hellip;","title-new-secret":"Erstelle ein neues Secret","title-reading-secret":"Secret auslesen\u0026hellip;","title-secret-create-disabled":"Erstellen von Secrets deaktiviert…","title-secret-created":"Secret erstellt!","tooltip-burn-secret":"Secret jetzt zerstören!","tooltip-copy-to-clipboard":"In die Zwischenablage kopieren","tooltip-download-as-file":"Als Datei herunterladen"}'), JSON.parse('{"alert-insecure-environment":"Du besuchst diese Instanz über eine unsichere Verbindung. Du kannst deswegen keine Secrets erstellen oder lesen.","alert-secret-not-found":"Das ist nicht das Secret, was du suchst\u0026hellip; - Falls du diesen Link noch nicht selbst geöffnet hast, könnte das Secret kompromittiert sein, da jemand anderes den Link geöffnet haben könnte.","alert-something-went-wrong":"Irgendwas ging schief. Entschuldigung\u0026hellip;","btn-create-secret":"Secret erstellen!","btn-create-secret-processing":"Secret wird erstellt…","btn-new-secret":"Neues Secret","btn-reveal-secret":"Zeig mir das Secret!","btn-reveal-secret-processing":"Secret wird entschlüsselt…","btn-show-explanation":"Wie funktioniert das?","btn-theme-switcher-auto":"Auto","expire-default":"Server-Standard","expire-n-days":"{n} Tag | {n} Tage","expire-n-hours":"{n} Stunde | {n} Stunden","expire-n-minutes":"{n} Minute | {n} Minuten","expire-n-seconds":"{n} Sekunde | {n} Sekunden","items-explanation":["Du gibst ein Secret auf dieser Seite ein","Dein Browser verschlüsselt das Secret mit einem generierten Passwort","Nur das verschlüsselte Secret wird an den Server geschickt (das Passwort oder das Secret im Klartext werden niemals übertragen!)","Der Server speichert das verschlüsselte Secret für eine Weile","Du gibst die angezeigte URL, welche die ID und das Passwort des Secrets enthält, an den Empfänger","Der Empfänger kann das Secret einmalig abrufen: Funktioniert das nicht, könnte jemand anderes es abgerufen haben!","Wenn das verschlüsselte Secret das erste Mal abgerufen wurde, wird es automatisch vom Server gelöscht"],"label-expiry":"Ablauf in:","label-secret-data":"Inhalt des Secrets:","label-secret-files":"Dateien Anhängen:","text-attached-files":"Der Absender hat Dateien an das Secret angehängt. Stell sicher, dass du dem Absender vertraust, da die Dateien nicht geprüft wurden!","text-burn-hint":"Bitte rufe die URL nicht selbst auf, da das Secret dadurch zerstört würde. Gib sie einfach weiter!","text-burn-time":"Wenn es vorher nicht eingesehen wurde, wird dieses Secret automatisch gelöscht:","text-hint-burned":"\u003cstrong\u003eAchtung:\u003c/strong\u003e Du kannst das nur einmal ansehen! Sobald du die Seite neu lädst, ist das Secret verschwunden, also besser direkt kopieren und sicher abspeichern\u0026hellip;","text-invalid-files-selected":"Mindestens eine der ausgewählten Dateien ist nicht als Anhang erlaubt.","text-max-filesize":"Maximale Größe: {maxSize}","text-max-filesize-exceeded":"Die ausgewählten Dateien übersteigen die maximale Größe: {curSize} / {maxSize}","text-powered-by":"Läuft mit","text-pre-reveal-hint":"Um das Secret anzuzeigen klicke diesen Button aber denk dran, dass das Secret nur einmal angezeigt und dabei gelöscht wird.","text-pre-url":"Dein Secret wurde angelegt und unter folgender URL gespeichert:","text-secret-burned":"Das Secret wurde zerstört.","text-secret-create-disabled":"Auf dieser Instanz wurde das Erstellen neuer Secrets deaktiviert.","title-explanation":"So funktioniert es\u0026hellip;","title-new-secret":"Erstelle ein neues Secret","title-reading-secret":"Secret auslesen\u0026hellip;","title-secret-create-disabled":"Erstellen von Secrets deaktiviert…","title-secret-created":"Secret erstellt!","tooltip-burn-secret":"Secret jetzt zerstören!","tooltip-copy-to-clipboard":"In die Zwischenablage kopieren","tooltip-download-as-file":"Als Datei herunterladen"}'),
), ),
'en': JSON.parse('{"alert-insecure-environment":"You are accessing this instance using an insecure connection. You will not be able to create or read secrets.","alert-secret-not-found":"This is not the secret you are looking for\u0026hellip; - If you expected the secret to be here it might be compromised as someone else might have opened the link already.","alert-something-went-wrong":"Something went wrong. I\'m very sorry about this\u0026hellip;","btn-create-secret":"Create the secret!","btn-create-secret-processing":"Secret is being created…","btn-new-secret":"New Secret","btn-reveal-secret":"Show me the secret!","btn-reveal-secret-processing":"Secret is being decrypted…","btn-show-explanation":"How does this work?","expire-default":"Default Expiry","expire-n-days":"{n} day | {n} days","expire-n-hours":"{n} hour | {n} hours","expire-n-minutes":"{n} minute | {n} minutes","expire-n-seconds":"{n} second | {n} seconds","items-explanation":["You enter a secret into the field on this page","Your browser encrypts the secret using a generated password","Only the encrypted secret is sent to the server (neither the plain secret nor the password are ever sent!)","The server stores the encrypted secret for a certain time","You pass the displayed URL containing the ID and the decryption password to the recipient","The recipient can view the secret exactly once: If they can\'t, the secret might have been viewed by someone else!","After the encrypted secret has been retrieved once, it is deleted from the server"],"label-expiry":"Expire in:","label-secret-data":"Secret data:","label-secret-files":"Attach Files:","text-attached-files":"The sender attached files to the secret. Make sure you trust the sender as the files were not checked!","text-burn-hint":"Please remember not to go to this URL yourself as that would destroy the secret. Just pass it to someone else!","text-burn-time":"If not viewed before, this secret will automatically be deleted:","text-hint-burned":"\u003cstrong\u003eAttention:\u003c/strong\u003e You\'re only seeing this once. As soon as you reload the page the secret will be gone so maybe copy it now\u0026hellip;","text-invalid-files-selected":"At least one of the selected files is not allowed as an attachment.","text-max-filesize":"Maximum size: {maxSize}","text-max-filesize-exceeded":"The file(s) you chose are too big to attach: {curSize} / {maxSize}","text-powered-by":"Powered by","text-pre-reveal-hint":"To reveal the secret click this button but be aware doing so will destroy the secret. You can only view it once!","text-pre-url":"Your secret was created and stored using this URL:","text-secret-burned":"The secret was successfully destroyed.","text-secret-create-disabled":"The creation of new secrets is disabled in this instance.","title-explanation":"This is how it works\u0026hellip;","title-new-secret":"Create a new secret","title-reading-secret":"Reading your secret\u0026hellip;","title-secret-create-disabled":"Secret creation disabled…","title-secret-created":"Secret created!","tooltip-burn-secret":"Burn Secret now!","tooltip-copy-to-clipboard":"Copy to Clipboard","tooltip-download-as-file":"Download as File"}'), 'en': JSON.parse('{"alert-insecure-environment":"You are accessing this instance using an insecure connection. You will not be able to create or read secrets.","alert-secret-not-found":"This is not the secret you are looking for\u0026hellip; - If you expected the secret to be here it might be compromised as someone else might have opened the link already.","alert-something-went-wrong":"Something went wrong. I\'m very sorry about this\u0026hellip;","btn-create-secret":"Create the secret!","btn-create-secret-processing":"Secret is being created…","btn-new-secret":"New Secret","btn-reveal-secret":"Show me the secret!","btn-reveal-secret-processing":"Secret is being decrypted…","btn-show-explanation":"How does this work?","btn-theme-switcher-auto":"Auto","expire-default":"Default Expiry","expire-n-days":"{n} day | {n} days","expire-n-hours":"{n} hour | {n} hours","expire-n-minutes":"{n} minute | {n} minutes","expire-n-seconds":"{n} second | {n} seconds","items-explanation":["You enter a secret into the field on this page","Your browser encrypts the secret using a generated password","Only the encrypted secret is sent to the server (neither the plain secret nor the password are ever sent!)","The server stores the encrypted secret for a certain time","You pass the displayed URL containing the ID and the decryption password to the recipient","The recipient can view the secret exactly once: If they can\'t, the secret might have been viewed by someone else!","After the encrypted secret has been retrieved once, it is deleted from the server"],"label-expiry":"Expire in:","label-secret-data":"Secret data:","label-secret-files":"Attach Files:","text-attached-files":"The sender attached files to the secret. Make sure you trust the sender as the files were not checked!","text-burn-hint":"Please remember not to go to this URL yourself as that would destroy the secret. Just pass it to someone else!","text-burn-time":"If not viewed before, this secret will automatically be deleted:","text-hint-burned":"\u003cstrong\u003eAttention:\u003c/strong\u003e You\'re only seeing this once. As soon as you reload the page the secret will be gone so maybe copy it now\u0026hellip;","text-invalid-files-selected":"At least one of the selected files is not allowed as an attachment.","text-max-filesize":"Maximum size: {maxSize}","text-max-filesize-exceeded":"The file(s) you chose are too big to attach: {curSize} / {maxSize}","text-powered-by":"Powered by","text-pre-reveal-hint":"To reveal the secret click this button but be aware doing so will destroy the secret. You can only view it once!","text-pre-url":"Your secret was created and stored using this URL:","text-secret-burned":"The secret was successfully destroyed.","text-secret-create-disabled":"The creation of new secrets is disabled in this instance.","title-explanation":"This is how it works\u0026hellip;","title-new-secret":"Create a new secret","title-reading-secret":"Reading your secret\u0026hellip;","title-secret-create-disabled":"Secret creation disabled…","title-secret-created":"Secret created!","tooltip-burn-secret":"Burn Secret now!","tooltip-copy-to-clipboard":"Copy to Clipboard","tooltip-download-as-file":"Download as File"}'),
'es': JSON.parse('{"alert-secret-not-found":"Este no es el secreto que buscas\u0026hellip; - Si esperabas que el secreto estuviera aquí, es posible que se haya visto comprometido, ya que otra persona podría haber abierto el enlace en tu lugar.","alert-something-went-wrong":"Algo ha salido mal. Lo sentimos mucho\u0026hellip;","btn-create-secret":"¡Crea el secreto!","btn-create-secret-processing":"El secreto se está creando...","btn-new-secret":"Nuevo secreto","btn-reveal-secret":"¡Muéstrame el secreto!","btn-reveal-secret-processing":"El secreto se está descifrando...","btn-show-explanation":"¿Cómo funciona?","expire-default":"Caducidad predeterminada","expire-n-days":"{n} día | {n} días","expire-n-hours":"{n} hora | {n} horas","expire-n-minutes":"{n} minuto | {n} minutos","expire-n-seconds":"{n} segundo | {n} segundos","items-explanation":["Introduce un secreto en el formulario que hay en esta página","Tu navegador cifra el secreto utilizando una contraseña generada","Únicamente se envía al servidor el secreto cifrado (¡nunca se envían ni el secreto sin cifrar ni la contraseña!)","El servidor almacena el secreto cifrado durante un tiempo limitado","Envía al destinatario el enlace mostrado, que contiene el identificador del secreto y la contraseña de descifrado","El destinatario puede ver el secreto una sola vez: si no puede, ¡el secreto podría haber sido visto por otra persona!","Cuando se ha obtenido por primera y única vez el secreto cifrado, se elimina del servidor"],"label-expiry":"Caduca en:","label-secret-data":"Información secreta:","label-secret-files":"Adjuntar archivos:","text-attached-files":"El remitente ha adjuntado archivos al secreto. Asegúrate de que confías en esta persona, ya que los archivos no han sido revisados.","text-burn-hint":"Por favor, recuerda no acceder a este enlace tú mismo, ya que esto destruiría el secreto. ¡Solo tienes que pasárselo a otra persona!","text-burn-time":"Si no se muestra antes, este secreto se eliminará automáticamente:","text-hint-burned":"\u003cstrong\u003eAtención:\u003c/strong\u003e Solo verás esto una vez. En cuanto recargues la página, el secreto desaparecerá, así que cópialo ya\u0026hellip;","text-max-filesize":"Tamaño máximo: {maxSize}","text-max-filesize-exceeded":"Los archivos seleccionados son demasiado grandes para adjuntarlos: {curSize} / {maxSize}","text-powered-by":"Funciona con","text-pre-reveal-hint":"Para mostrar el secreto pulsa este botón, pero ten en cuenta que al hacerlo se destruirá. ¡Solo puedes verlo una vez!","text-pre-url":"Tu secreto ha sido creado y almacenado en el siguiente enlace:","text-secret-create-disabled":"La creación de nuevos secretos está desactivada en esta instancia.","title-explanation":"Así es como funciona\u0026hellip;","title-new-secret":"Crea un nuevo secreto","title-reading-secret":"Obteniendo tu secreto\u0026hellip;","title-secret-create-disabled":"Creación de secretos desactivada...","title-secret-created":"¡Secreto creado!"}'), 'es': JSON.parse('{"alert-secret-not-found":"Este no es el secreto que buscas\u0026hellip; - Si esperabas que el secreto estuviera aquí, es posible que se haya visto comprometido, ya que otra persona podría haber abierto el enlace en tu lugar.","alert-something-went-wrong":"Algo ha salido mal. Lo sentimos mucho\u0026hellip;","btn-create-secret":"¡Crea el secreto!","btn-create-secret-processing":"El secreto se está creando...","btn-new-secret":"Nuevo secreto","btn-reveal-secret":"¡Muéstrame el secreto!","btn-reveal-secret-processing":"El secreto se está descifrando...","btn-show-explanation":"¿Cómo funciona?","expire-default":"Caducidad predeterminada","expire-n-days":"{n} día | {n} días","expire-n-hours":"{n} hora | {n} horas","expire-n-minutes":"{n} minuto | {n} minutos","expire-n-seconds":"{n} segundo | {n} segundos","items-explanation":["Introduce un secreto en el formulario que hay en esta página","Tu navegador cifra el secreto utilizando una contraseña generada","Únicamente se envía al servidor el secreto cifrado (¡nunca se envían ni el secreto sin cifrar ni la contraseña!)","El servidor almacena el secreto cifrado durante un tiempo limitado","Envía al destinatario el enlace mostrado, que contiene el identificador del secreto y la contraseña de descifrado","El destinatario puede ver el secreto una sola vez: si no puede, ¡el secreto podría haber sido visto por otra persona!","Cuando se ha obtenido por primera y única vez el secreto cifrado, se elimina del servidor"],"label-expiry":"Caduca en:","label-secret-data":"Información secreta:","label-secret-files":"Adjuntar archivos:","text-attached-files":"El remitente ha adjuntado archivos al secreto. Asegúrate de que confías en esta persona, ya que los archivos no han sido revisados.","text-burn-hint":"Por favor, recuerda no acceder a este enlace tú mismo, ya que esto destruiría el secreto. ¡Solo tienes que pasárselo a otra persona!","text-burn-time":"Si no se muestra antes, este secreto se eliminará automáticamente:","text-hint-burned":"\u003cstrong\u003eAtención:\u003c/strong\u003e Solo verás esto una vez. En cuanto recargues la página, el secreto desaparecerá, así que cópialo ya\u0026hellip;","text-max-filesize":"Tamaño máximo: {maxSize}","text-max-filesize-exceeded":"Los archivos seleccionados son demasiado grandes para adjuntarlos: {curSize} / {maxSize}","text-powered-by":"Funciona con","text-pre-reveal-hint":"Para mostrar el secreto pulsa este botón, pero ten en cuenta que al hacerlo se destruirá. ¡Solo puedes verlo una vez!","text-pre-url":"Tu secreto ha sido creado y almacenado en el siguiente enlace:","text-secret-create-disabled":"La creación de nuevos secretos está desactivada en esta instancia.","title-explanation":"Así es como funciona\u0026hellip;","title-new-secret":"Crea un nuevo secreto","title-reading-secret":"Obteniendo tu secreto\u0026hellip;","title-secret-create-disabled":"Creación de secretos desactivada...","title-secret-created":"¡Secreto creado!"}'),
@ -20,11 +20,11 @@ export default {
'lv': JSON.parse('{"alert-secret-not-found":"\u003cstrong\u003eZiņa nav atrasta!\u003c/strong\u003e\u0026hellip; - Ja ievadītā saite ir pareiza, tad ir beidzies ziņas glabāšanas laiks, vai arī tā jau vienreiz ir atvērta.","alert-something-went-wrong":"Neparedzēta sistēmas kļūda. Atvainojiet par sagādātajām neērtībām\u0026hellip;","btn-create-secret":"Šifrēt ziņu!","btn-new-secret":"Jauna ziņa","btn-reveal-secret":"Atvērt ziņu!","btn-show-explanation":"Kā tas strādā?","expire-n-days":"{n} diena | {n} dienas","expire-n-hours":"{n} stunda | {n} stundas","expire-n-minutes":"{n} minūte | {n} minūtes","expire-n-seconds":"{n} sekundes | {n} sekundes","items-explanation":["Tu ievadi ziņu ievades laukā","Pārlūks nošifrē ziņu ar uzģenerētu paroli","Tikai šifrētā ziņa tiek nosūtīta serverim (nešifrētā ziņa un parole sūtīta netiek!)","Serveris noteiktu laiku glabā šifrēto ziņu","Tu nodod URL ar ziņas ID un atšifrēšanas paroli saņēmējam","Saņēmējs var atvērt ziņu tikai vienreiz: ja tas neizdodas, iespējams, ziņu jau atvēris kāds cits!","Kad ziņa tiek atvērta pirmo reizi, tā no servera tiek dzēsta"],"label-secret-data":"Ziņa:","text-burn-hint":"Lūdzu atceries neatvērt saiti pats, jo tad ziņa tiks dzēsta. Nodod saiti ziņas saņēmējam!","text-hint-burned":"\u003cstrong\u003eUzmanību:\u003c/strong\u003e Ziņa tiek parādīta tikai vienu reizi. Līdzko lapa tiks pārlādēta, ziņa būs neatgriezeniski zaudēta, tāpēc nepieciešamības gadījumā nokopē to tagad\u0026hellip;","text-powered-by":"Darbina","text-pre-reveal-hint":"Lai parādītu ziņu nospied šo pogu, bet rēķinies ar to, ka pēc apskates ziņa vairs nebūs pieejama. To var atvērt tikai vienreiz!","text-pre-url":"Ziņa ir nošifrēta un ir atverama šajā adresē:","text-secret-create-disabled":"Šajā gadījumā jaunu noslēpumu izveide ir atspējota.","title-explanation":"Tā tas strādā\u0026hellip;","title-new-secret":"Šifrēt ziņu","title-reading-secret":"Atver ziņu\u0026hellip;","title-secret-create-disabled":"Slepena izveide atspējota...","title-secret-created":"Ziņa nošifrēta!"}'), 'lv': JSON.parse('{"alert-secret-not-found":"\u003cstrong\u003eZiņa nav atrasta!\u003c/strong\u003e\u0026hellip; - Ja ievadītā saite ir pareiza, tad ir beidzies ziņas glabāšanas laiks, vai arī tā jau vienreiz ir atvērta.","alert-something-went-wrong":"Neparedzēta sistēmas kļūda. Atvainojiet par sagādātajām neērtībām\u0026hellip;","btn-create-secret":"Šifrēt ziņu!","btn-new-secret":"Jauna ziņa","btn-reveal-secret":"Atvērt ziņu!","btn-show-explanation":"Kā tas strādā?","expire-n-days":"{n} diena | {n} dienas","expire-n-hours":"{n} stunda | {n} stundas","expire-n-minutes":"{n} minūte | {n} minūtes","expire-n-seconds":"{n} sekundes | {n} sekundes","items-explanation":["Tu ievadi ziņu ievades laukā","Pārlūks nošifrē ziņu ar uzģenerētu paroli","Tikai šifrētā ziņa tiek nosūtīta serverim (nešifrētā ziņa un parole sūtīta netiek!)","Serveris noteiktu laiku glabā šifrēto ziņu","Tu nodod URL ar ziņas ID un atšifrēšanas paroli saņēmējam","Saņēmējs var atvērt ziņu tikai vienreiz: ja tas neizdodas, iespējams, ziņu jau atvēris kāds cits!","Kad ziņa tiek atvērta pirmo reizi, tā no servera tiek dzēsta"],"label-secret-data":"Ziņa:","text-burn-hint":"Lūdzu atceries neatvērt saiti pats, jo tad ziņa tiks dzēsta. Nodod saiti ziņas saņēmējam!","text-hint-burned":"\u003cstrong\u003eUzmanību:\u003c/strong\u003e Ziņa tiek parādīta tikai vienu reizi. Līdzko lapa tiks pārlādēta, ziņa būs neatgriezeniski zaudēta, tāpēc nepieciešamības gadījumā nokopē to tagad\u0026hellip;","text-powered-by":"Darbina","text-pre-reveal-hint":"Lai parādītu ziņu nospied šo pogu, bet rēķinies ar to, ka pēc apskates ziņa vairs nebūs pieejama. To var atvērt tikai vienreiz!","text-pre-url":"Ziņa ir nošifrēta un ir atverama šajā adresē:","text-secret-create-disabled":"Šajā gadījumā jaunu noslēpumu izveide ir atspējota.","title-explanation":"Tā tas strādā\u0026hellip;","title-new-secret":"Šifrēt ziņu","title-reading-secret":"Atver ziņu\u0026hellip;","title-secret-create-disabled":"Slepena izveide atspējota...","title-secret-created":"Ziņa nošifrēta!"}'),
'nl': JSON.parse('{"alert-secret-not-found":"De gegevens die je zocht bestaan niet (meer)\u0026hellip; - Als je hier informatie verwachtte dan is de link mogelijk al door iemand anders bekeken!","alert-something-went-wrong":"Er ging iets verkeerd, sorry\u0026hellip;","btn-create-secret":"Geheim aanmaken!","btn-create-secret-processing":"Geheim wordt aangemaakt...","btn-new-secret":"Nieuw","btn-reveal-secret":"Toon mij het geheim!","btn-reveal-secret-processing":"Geheim wordt ontsleuteld...","btn-show-explanation":"Hoe werkt dit?","expire-default":"Standaard vervaltermijn","expire-n-days":"{n} dag | {n} dagen","expire-n-hours":"{n} uur | {n} uur","expire-n-minutes":"{n} minuut | {n} minuten","expire-n-seconds":"{n} seconde | {n} seconden","items-explanation":["Je vult vertrouwelijke informatie in op deze pagina.","Je browser versleutelt de ingevulde tekst via een automatisch gegenereerd wachtwoord.","Alleen de versleutelde data wordt naar de server gestuurd (de onversleutelde data of het wachtwoord worden nooit verstuurd!)","De server slaat de versleutelde data voor een bepaalde periode op","Je geeft de URL met ID en het gegenereerde wachtwoord aan de ontvanger.","De ontvanger kan de vertrouwelijke informatie exact eenmaal bekijken: indien het niet lukt heeft mogelijk iemand anders de info gezien!","Nadat het versleutelde geheim eenmalig bekeken is, wordt deze van de server verwijderd"],"label-expiry":"Verwijder na:","label-secret-data":"Vertrouwelijke info:","label-secret-files":"Bestanden toevoegen:","text-attached-files":"De afzender heeft bestanden toegevoegd. Deze zijn niet gecontroleerd, gebruik deze enkel als je de afzender vertrouwt!","text-burn-hint":"Open de URL niet zelf, deze is slechts eenmalig te gebruiken. Geef de URL aan de ontvanger.","text-burn-time":"Deze vertrouwelijke informatie wordt automatisch gewist indien niet bekeken voor:","text-hint-burned":"\u003cstrong\u003eOpgelet:\u003c/strong\u003e Je ziet deze informatie alleen nu. Je kan het niet meer opnieuw opvragen als je de pagina verlaat.","text-max-filesize":"Maximum grootte: {maxSize}","text-max-filesize-exceeded":"De bestanden die je toevoegde zijn te groot: {curSize} / {maxSize}","text-powered-by":"Mogelijk gemaakt door","text-pre-reveal-hint":"Gebruik deze knop om het geheim weer te geven. Let op: Je kan dit slechts eenmaal doen!","text-pre-url":"Het geheim kan opgevraagd worden via deze URL:","text-secret-create-disabled":"Het aanmaken van nieuwe geheimen is in deze omgeving uitgeschakeld.","title-explanation":"Dit is hoe het werkt\u0026hellip;","title-new-secret":"Nieuw geheim aanmaken","title-reading-secret":"Geheim wordt gelezen\u0026hellip;","title-secret-create-disabled":"Aanmaken geheimen uitgeschakeld...","title-secret-created":"Geheim aangemaakt!"}'), 'nl': JSON.parse('{"alert-secret-not-found":"De gegevens die je zocht bestaan niet (meer)\u0026hellip; - Als je hier informatie verwachtte dan is de link mogelijk al door iemand anders bekeken!","alert-something-went-wrong":"Er ging iets verkeerd, sorry\u0026hellip;","btn-create-secret":"Geheim aanmaken!","btn-create-secret-processing":"Geheim wordt aangemaakt...","btn-new-secret":"Nieuw","btn-reveal-secret":"Toon mij het geheim!","btn-reveal-secret-processing":"Geheim wordt ontsleuteld...","btn-show-explanation":"Hoe werkt dit?","btn-theme-switcher-auto":"Auto","expire-default":"Standaard vervaltermijn","expire-n-days":"{n} dag | {n} dagen","expire-n-hours":"{n} uur | {n} uur","expire-n-minutes":"{n} minuut | {n} minuten","expire-n-seconds":"{n} seconde | {n} seconden","items-explanation":["Je vult vertrouwelijke informatie in op deze pagina.","Je browser versleutelt de ingevulde tekst via een automatisch gegenereerd wachtwoord.","Alleen de versleutelde data wordt naar de server gestuurd (de onversleutelde data of het wachtwoord worden nooit verstuurd!)","De server slaat de versleutelde data voor een bepaalde periode op","Je geeft de URL met ID en het gegenereerde wachtwoord aan de ontvanger.","De ontvanger kan de vertrouwelijke informatie exact eenmaal bekijken: indien het niet lukt heeft mogelijk iemand anders de info gezien!","Nadat het versleutelde geheim eenmalig bekeken is, wordt deze van de server verwijderd"],"label-expiry":"Verwijder na:","label-secret-data":"Vertrouwelijke info:","label-secret-files":"Bestanden toevoegen:","text-attached-files":"De afzender heeft bestanden toegevoegd. Deze zijn niet gecontroleerd, gebruik deze enkel als je de afzender vertrouwt!","text-burn-hint":"Open de URL niet zelf, deze is slechts eenmalig te gebruiken. Geef de URL aan de ontvanger.","text-burn-time":"Deze vertrouwelijke informatie wordt automatisch gewist indien niet bekeken voor:","text-hint-burned":"\u003cstrong\u003eOpgelet:\u003c/strong\u003e Je ziet deze informatie alleen nu. Je kan het niet meer opnieuw opvragen als je de pagina verlaat.","text-max-filesize":"Maximum grootte: {maxSize}","text-max-filesize-exceeded":"De bestanden die je toevoegde zijn te groot: {curSize} / {maxSize}","text-powered-by":"Mogelijk gemaakt door","text-pre-reveal-hint":"Gebruik deze knop om het geheim weer te geven. Let op: Je kan dit slechts eenmaal doen!","text-pre-url":"Het geheim kan opgevraagd worden via deze URL:","text-secret-create-disabled":"Het aanmaken van nieuwe geheimen is in deze omgeving uitgeschakeld.","title-explanation":"Dit is hoe het werkt\u0026hellip;","title-new-secret":"Nieuw geheim aanmaken","title-reading-secret":"Geheim wordt gelezen\u0026hellip;","title-secret-create-disabled":"Aanmaken geheimen uitgeschakeld...","title-secret-created":"Geheim aangemaakt!"}'),
'nl-BE': JSON.parse('{"alert-secret-not-found":"De gegevens die je zocht bestaan niet (meer)\u0026hellip; - Als je hier informatie verwachtte dan is de link mogelijk al door iemand anders bekeken!","alert-something-went-wrong":"Er ging iets verkeerd, sorry\u0026hellip;","btn-create-secret":"Nieuwe vertrouwelijke info aanmaken!","btn-create-secret-processing":"Vertrouwelijke info wordt versleuteld...","btn-new-secret":"Nieuw","btn-reveal-secret":"Toon mij de vertrouwelijke info!","btn-reveal-secret-processing":"Vertrouwelijke info wordt ontsleuteld...","btn-show-explanation":"Hoe werkt dit?","expire-default":"Standaard termijn","expire-n-days":"{n} dag | {n} dagen","expire-n-hours":"{n} uur | {n} uur","expire-n-minutes":"{n} minuut | {n} minuten","expire-n-seconds":"{n} seconde | {n} seconden","items-explanation":["Je vult vertrouwelijke informatie in op deze pagina.","Je browser versleutelt de ingevulde tekst via een automatisch gegenereerd wachtwoord.","Alleen de versleutelde data wordt naar de server gestuurd. (De leesbare versie of het wachtwoord worden nooit verstuurd!)","De server slaat de versleutelde data gedurende een beperkte periode op.","Je geeft de URL met identificatie en het gegenereerde wachtwoord aan de ontvanger.","De ontvanger kan de vertrouwelijke informatie exact eenmaal bekijken: indien het niet lukt heeft mogelijk iemand anders de info gezien!","De versleutelde data wordt van de server gewist van zodra de ontvanger het bekeken heeft."],"label-expiry":"Verwijder na:","label-secret-data":"Vertrouwelijke info:","label-secret-files":"Bestanden toevoegen:","text-attached-files":"De afzender heeft bestanden toegevoegd. Deze werden niet gecontroleerd, gebruik deze enkel als je de afzender vertrouwt!","text-burn-hint":"Bezoek de URL niet zelf: je kan deze slechts eenmaal gebruiken. Geef de URL aan de ontvanger.","text-burn-time":"Deze vertrouwelijke informatie wordt automatisch gewist indien niet bekeken voor:","text-hint-burned":"\u003cstrong\u003eOpgelet:\u003c/strong\u003e Je ziet deze informatie alleen nu. Je kan het niet meer opnieuw opvragen als je de pagina verlaat.","text-max-filesize":"Maximum grootte: {maxSize}","text-max-filesize-exceeded":"De bestanden die je toevoegde zijn te groot: {curSize} / {maxSize}","text-powered-by":"Mogelijk gemaakt door","text-pre-reveal-hint":"Gebruik deze knop om de vertrouwelijke info op te halen. Let op: Je kan dit slechts eenmaal doen!","text-pre-url":"Je vertrouwelijke informatie kan opgevraagd worden via deze URL:","text-secret-create-disabled":"Het aanmaken van nieuwe geheimen is in dit geval uitgeschakeld.","title-explanation":"Dit is hoe het werkt\u0026hellip;","title-new-secret":"Nieuwe vertrouwelijke info opslaan","title-reading-secret":"Vertrouwelijke info lezen\u0026hellip;","title-secret-create-disabled":"Geheime creatie uitgeschakeld...","title-secret-created":"Vertrouwelijke info opgeslaan!"}'), 'nl-BE': JSON.parse('{"alert-secret-not-found":"De gegevens die je zocht bestaan niet (meer)\u0026hellip; - Als je hier informatie verwachtte dan is de link mogelijk al door iemand anders bekeken!","alert-something-went-wrong":"Er ging iets verkeerd, sorry\u0026hellip;","btn-create-secret":"Nieuwe vertrouwelijke info aanmaken!","btn-create-secret-processing":"Vertrouwelijke info wordt versleuteld...","btn-new-secret":"Nieuw","btn-reveal-secret":"Toon mij de vertrouwelijke info!","btn-reveal-secret-processing":"Vertrouwelijke info wordt ontsleuteld...","btn-show-explanation":"Hoe werkt dit?","expire-default":"Standaard termijn","expire-n-days":"{n} dag | {n} dagen","expire-n-hours":"{n} uur | {n} uur","expire-n-minutes":"{n} minuut | {n} minuten","expire-n-seconds":"{n} seconde | {n} seconden","items-explanation":["Je vult vertrouwelijke informatie in op deze pagina.","Je browser versleutelt de ingevulde tekst via een automatisch gegenereerd wachtwoord.","Alleen de versleutelde data wordt naar de server gestuurd. (De leesbare versie of het wachtwoord worden nooit verstuurd!)","De server slaat de versleutelde data gedurende een beperkte periode op.","Je geeft de URL met identificatie en het gegenereerde wachtwoord aan de ontvanger.","De ontvanger kan de vertrouwelijke informatie exact eenmaal bekijken: indien het niet lukt heeft mogelijk iemand anders de info gezien!","De versleutelde data wordt van de server gewist van zodra de ontvanger het bekeken heeft."],"label-expiry":"Verwijder na:","label-secret-data":"Vertrouwelijke info:","label-secret-files":"Bestanden toevoegen:","text-attached-files":"De afzender heeft bestanden toegevoegd. Deze werden niet gecontroleerd, gebruik deze enkel als je de afzender vertrouwt!","text-burn-hint":"Bezoek de URL niet zelf: je kan deze slechts eenmaal gebruiken. Geef de URL aan de ontvanger.","text-burn-time":"Deze vertrouwelijke informatie wordt automatisch gewist indien niet bekeken voor:","text-hint-burned":"\u003cstrong\u003eOpgelet:\u003c/strong\u003e Je ziet deze informatie alleen nu. Je kan het niet meer opnieuw opvragen als je de pagina verlaat.","text-max-filesize":"Maximum grootte: {maxSize}","text-max-filesize-exceeded":"De bestanden die je toevoegde zijn te groot: {curSize} / {maxSize}","text-powered-by":"Mogelijk gemaakt door","text-pre-reveal-hint":"Gebruik deze knop om de vertrouwelijke info op te halen. Let op: Je kan dit slechts eenmaal doen!","text-pre-url":"Je vertrouwelijke informatie kan opgevraagd worden via deze URL:","text-secret-create-disabled":"Het aanmaken van nieuwe geheimen is in dit geval uitgeschakeld.","title-explanation":"Dit is hoe het werkt\u0026hellip;","title-new-secret":"Nieuwe vertrouwelijke info opslaan","title-reading-secret":"Vertrouwelijke info lezen\u0026hellip;","title-secret-create-disabled":"Geheime creatie uitgeschakeld...","title-secret-created":"Vertrouwelijke info opgeslaan!"}'),
'pl': JSON.parse('{"alert-insecure-environment":"Odwiedzasz tę instancję przez niezabezpieczone połączenie. Nie będziesz mógł tworzyć ani odczytywać sekretów.","alert-secret-not-found":"To nie jest sekret, którego szukasz\u0026hellip; - Jeśli spodziewałeś się tu sekretu, to może być on zagrożony, ponieważ ktoś inny mógł już otworzyć ten link.","alert-something-went-wrong":"Coś poszło nie tak. Bardzo mi przykro\u0026hellip;","btn-create-secret":"Stwórz sekret!","btn-create-secret-processing":"Sekret jest tworzony...","btn-new-secret":"Nowy sekret","btn-reveal-secret":"Pokaż mi sekret!","btn-reveal-secret-processing":"Sekret jest odszyfrowywany...","btn-show-explanation":"Jak to działa?","expire-default":"Domyślne wygasanie","expire-n-days":"{n} dzień | {n} dni","expire-n-hours":"{n} godzina | {n} godzin(y)","expire-n-minutes":"{n} minuta | {n} minut(y)","expire-n-seconds":"{n} sekunda | {n} sekund(y)","items-explanation":["Wpisujesz sekret w pole na tej stronie","Twoja przeglądarka szyfruje sekret korzystając z wygenerowanego hasła","Tylko zaszyfrowany sekret jest wysyłany na serwer (ani czysty sekret, ani hasło nie są nigdy wysyłane!)","Serwer przechowuje zaszyfrowany sekret przez określony czas","Przekazujesz wyświetlony adres URL zawierający identyfikator sekretu oraz hasło deszyfrujące do odbiorcy","Odbiorca może zobaczyć sekret tylko raz: jeżeli nie może tego zrobić, to sekret mógł być już wyświetlony przez kogoś innego!","Po jednorazowym odczytaniu zaszyfrowanego sekretu, jest on usuwany z serwera"],"label-expiry":"Wygasa po:","label-secret-data":"Dane sekretu:","label-secret-files":"Dołącz pliki:","text-attached-files":"Nadawca dołączył pliki do tego sekretu. Upewnij się, że ufasz nadawcy, bo pliki nie są sprawdzane!","text-burn-hint":"Pamiętaj, aby nie przechodzić na ten adres URL samemu, ponieważ to zniszczy sekret. Po prostu przekaż go odbiorcy!","text-burn-time":"Jeżeli nie zostanie wyświetlony, ten sekret zostanie automatycznie usunięty:","text-hint-burned":"\u003cstrong\u003eUwaga:\u003c/strong\u003e Zobaczysz to tylko raz. Gdy odświeżysz stronę, to sekret nie będzie już dostępny, więc lepiej skopiuj go teraz\u0026hellip;","text-invalid-files-selected":"Co najmniej jeden z załączonych plików nie jest dopuszczalny jako załącznik.","text-max-filesize":"Maksymalny rozmiar: {maxSize}","text-max-filesize-exceeded":"Wybrane załączniki przekraczają maksymalny rozmiar: {curSize} / {maxSize}","text-powered-by":"Obsługiwane przez","text-pre-reveal-hint":"Aby odsłonić sekret, naciśnij ten przycisk, jednak wiedz, że to zniszczy sekret. Możesz go zobaczyć tylko raz!","text-pre-url":"Twój sekret został stworzony i zachowany pod tym adresem URL:","text-secret-burned":"Sekret został pomyślnie zniszczony.","text-secret-create-disabled":"Tworzenie nowych sekretów jest wyłączone na tej instancji.","title-explanation":"Oto, jak to działa\u0026hellip;","title-new-secret":"Stwórz nowy sekret","title-reading-secret":"Odczytywanie Twojego sekretu\u0026hellip;","title-secret-create-disabled":"Tworzenie sekretów wyłączone\u0026hellip;","title-secret-created":"Sekret utworzony!","tooltip-burn-secret":"Zniszcz sekret teraz!","tooltip-copy-to-clipboard":"Skopiuj do schowka","tooltip-download-as-file":"Pobierz jako plik"}'), 'pl': JSON.parse('{"alert-insecure-environment":"Odwiedzasz tę instancję przez niezabezpieczone połączenie. Nie będziesz mógł tworzyć ani odczytywać sekretów.","alert-secret-not-found":"To nie jest sekret, którego szukasz\u0026hellip; - Jeśli spodziewałeś się tu sekretu, to może być on zagrożony, ponieważ ktoś inny mógł już otworzyć ten link.","alert-something-went-wrong":"Coś poszło nie tak. Bardzo mi przykro\u0026hellip;","btn-create-secret":"Stwórz sekret!","btn-create-secret-processing":"Sekret jest tworzony...","btn-new-secret":"Nowy sekret","btn-reveal-secret":"Pokaż mi sekret!","btn-reveal-secret-processing":"Sekret jest odszyfrowywany...","btn-show-explanation":"Jak to działa?","btn-theme-switcher-auto":"Auto","expire-default":"Domyślne wygasanie","expire-n-days":"{n} dzień | {n} dni","expire-n-hours":"{n} godzina | {n} godzin(y)","expire-n-minutes":"{n} minuta | {n} minut(y)","expire-n-seconds":"{n} sekunda | {n} sekund(y)","items-explanation":["Wpisujesz sekret w pole na tej stronie","Twoja przeglądarka szyfruje sekret korzystając z wygenerowanego hasła","Tylko zaszyfrowany sekret jest wysyłany na serwer (ani czysty sekret, ani hasło nie są nigdy wysyłane!)","Serwer przechowuje zaszyfrowany sekret przez określony czas","Przekazujesz wyświetlony adres URL zawierający identyfikator sekretu oraz hasło deszyfrujące do odbiorcy","Odbiorca może zobaczyć sekret tylko raz: jeżeli nie może tego zrobić, to sekret mógł być już wyświetlony przez kogoś innego!","Po jednorazowym odczytaniu zaszyfrowanego sekretu, jest on usuwany z serwera"],"label-expiry":"Wygasa po:","label-secret-data":"Dane sekretu:","label-secret-files":"Dołącz pliki:","text-attached-files":"Nadawca dołączył pliki do tego sekretu. Upewnij się, że ufasz nadawcy, bo pliki nie są sprawdzane!","text-burn-hint":"Pamiętaj, aby nie przechodzić na ten adres URL samemu, ponieważ to zniszczy sekret. Po prostu przekaż go odbiorcy!","text-burn-time":"Jeżeli nie zostanie wyświetlony, ten sekret zostanie automatycznie usunięty:","text-hint-burned":"\u003cstrong\u003eUwaga:\u003c/strong\u003e Zobaczysz to tylko raz. Gdy odświeżysz stronę, to sekret nie będzie już dostępny, więc lepiej skopiuj go teraz\u0026hellip;","text-invalid-files-selected":"Co najmniej jeden z załączonych plików nie jest dopuszczalny jako załącznik.","text-max-filesize":"Maksymalny rozmiar: {maxSize}","text-max-filesize-exceeded":"Wybrane załączniki przekraczają maksymalny rozmiar: {curSize} / {maxSize}","text-powered-by":"Obsługiwane przez","text-pre-reveal-hint":"Aby odsłonić sekret, naciśnij ten przycisk, jednak wiedz, że to zniszczy sekret. Możesz go zobaczyć tylko raz!","text-pre-url":"Twój sekret został stworzony i zachowany pod tym adresem URL:","text-secret-burned":"Sekret został pomyślnie zniszczony.","text-secret-create-disabled":"Tworzenie nowych sekretów jest wyłączone na tej instancji.","title-explanation":"Oto, jak to działa\u0026hellip;","title-new-secret":"Stwórz nowy sekret","title-reading-secret":"Odczytywanie Twojego sekretu\u0026hellip;","title-secret-create-disabled":"Tworzenie sekretów wyłączone\u0026hellip;","title-secret-created":"Sekret utworzony!","tooltip-burn-secret":"Zniszcz sekret teraz!","tooltip-copy-to-clipboard":"Skopiuj do schowka","tooltip-download-as-file":"Pobierz jako plik"}'),
'pt-BR': JSON.parse('{"alert-secret-not-found":"Esta não é o segredo que você está procurando… - Se você esperava que o segredo estaria aqui, ele pode ter sido comprometido por alguém que já acessou o link.","alert-something-went-wrong":"Desculpe, algo deu errado…","btn-create-secret":"Criar segredo!","btn-new-secret":"Novo segredo","btn-reveal-secret":"Mostrar o segredo!","btn-show-explanation":"Como funciona?","expire-n-days":"{n} dia | {n} dias","expire-n-hours":"{n} hora | {n} horas","expire-n-minutes":"{n} minutos | {n} minutos","expire-n-seconds":"{n} segundos | {n} segundos","items-explanation":["Você insere o segredo no campo de texto desta página","Seu navegador criptografa o segredo usando uma senha gerada","Somente o segredo criptografado é enviado para o servidor (nem o segredo em texto claro, nem a senha é enviada para o servidor!)","O servidor armazena o segredo criptografado por um certo tempo","Você envia a URL mostrada contendo a ID e a senha de descriptografia para o destinatário","O destinatário pode ver o segredo apenas uma vez: se ele não pode ver, o segredo pode ter sido visto por outra pessoa!","Após o segredo ter sido obtido uma vez, o mesmo é deletado do servidor"],"label-secret-data":"Informação secreta:","text-burn-hint":"Importante você lembrar de não acessar esta URL, pois isto irá indisponibilizar o segredo. Apenas encaminhe para outra pessoa!","text-hint-burned":"Atenção: Você está vendo esta informação apenas uma vez. Logo que você recarregar a página o segredo ficará indisponível. É recomendado que você copie a informação agora…","text-powered-by":"Powered by","text-pre-reveal-hint":"Para revelar o segredo clique neste botão, mas lembre-se que esta ação vai destruir o segredo. Você só pode ver uma única vez!","text-pre-url":"Seu segredo foi criado e armazenado na seguinte URL:","text-secret-create-disabled":"A criação de novos segredos é desativada nesse caso.","title-explanation":"É assim como funciona…","title-new-secret":"Criar um novo segredo","title-reading-secret":"Lendo seu segredo…","title-secret-create-disabled":"Criação secreta desativada...","title-secret-created":"Segredo criado!"}'), 'pt-BR': JSON.parse('{"alert-secret-not-found":"Esta não é o segredo que você está procurando… - Se você esperava que o segredo estaria aqui, ele pode ter sido comprometido por alguém que já acessou o link.","alert-something-went-wrong":"Desculpe, algo deu errado…","btn-create-secret":"Criar segredo!","btn-new-secret":"Novo segredo","btn-reveal-secret":"Mostrar o segredo!","btn-show-explanation":"Como funciona?","expire-n-days":"{n} dia | {n} dias","expire-n-hours":"{n} hora | {n} horas","expire-n-minutes":"{n} minutos | {n} minutos","expire-n-seconds":"{n} segundos | {n} segundos","items-explanation":["Você insere o segredo no campo de texto desta página","Seu navegador criptografa o segredo usando uma senha gerada","Somente o segredo criptografado é enviado para o servidor (nem o segredo em texto claro, nem a senha é enviada para o servidor!)","O servidor armazena o segredo criptografado por um certo tempo","Você envia a URL mostrada contendo a ID e a senha de descriptografia para o destinatário","O destinatário pode ver o segredo apenas uma vez: se ele não pode ver, o segredo pode ter sido visto por outra pessoa!","Após o segredo ter sido obtido uma vez, o mesmo é deletado do servidor"],"label-secret-data":"Informação secreta:","text-burn-hint":"Importante você lembrar de não acessar esta URL, pois isto irá indisponibilizar o segredo. Apenas encaminhe para outra pessoa!","text-hint-burned":"Atenção: Você está vendo esta informação apenas uma vez. Logo que você recarregar a página o segredo ficará indisponível. É recomendado que você copie a informação agora…","text-powered-by":"Powered by","text-pre-reveal-hint":"Para revelar o segredo clique neste botão, mas lembre-se que esta ação vai destruir o segredo. Você só pode ver uma única vez!","text-pre-url":"Seu segredo foi criado e armazenado na seguinte URL:","text-secret-create-disabled":"A criação de novos segredos é desativada nesse caso.","title-explanation":"É assim como funciona…","title-new-secret":"Criar um novo segredo","title-reading-secret":"Lendo seu segredo…","title-secret-create-disabled":"Criação secreta desativada...","title-secret-created":"Segredo criado!"}'),

View file

@ -1,21 +0,0 @@
/* lato-regular - latin-ext_latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: url('latofont/lato-v20-latin-ext_latin-regular.woff2') format('woff2'); /* Chrome 26+, Opera 23+, Firefox 39+ */
}
/* lato-italic - latin-ext_latin */
@font-face {
font-family: 'Lato';
font-style: italic;
font-weight: 400;
src: url('latofont/lato-v20-latin-ext_latin-italic.woff2') format('woff2'); /* Chrome 26+, Opera 23+, Firefox 39+ */
}
/* lato-700 - latin-ext_latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: url('latofont/lato-v20-latin-ext_latin-700.woff2') format('woff2'); /* Chrome 26+, Opera 23+, Firefox 39+ */
}

View file

@ -1,94 +0,0 @@
Copyright (c) 2010-2015, Łukasz Dziedzic (dziedzic@typoland.com),
with Reserved Font Name Lato.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View file

@ -1,80 +0,0 @@
/* eslint-disable sort-imports */
/* global version */
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import VueRouter from 'vue-router'
import './style.scss'
import app from './app.vue'
import messages from './langs/langs.js'
import router from './router'
Vue.use(VueI18n)
Vue.use(VueRouter)
const cookieSet = Object.fromEntries(document.cookie.split('; ')
.map(el => el.split('=')
.map(el => decodeURIComponent(el))))
const i18n = new VueI18n({
fallbackLocale: 'en',
locale: cookieSet.lang || navigator?.language || 'en',
messages,
})
Vue.mixin({
beforeRouteLeave(_to, _from, next) {
// Before leaving the component, reset the errors the component displayed
this.$emit('error', null)
next()
},
})
new Vue({
components: { app },
computed: {
isSecureEnvironment() {
return Boolean(window.crypto.subtle)
},
},
data: {
customize: {},
darkTheme: false,
version,
},
el: '#app',
i18n,
methods: {
navigate(to) {
this.$router.replace(to)
.catch(err => {
if (VueRouter.isNavigationFailure(err, VueRouter.NavigationFailureType.duplicated)) {
// Hide duplicate nav errors
return
}
throw err
})
},
},
mounted() {
this.customize = window.OTSCustomize
this.darkTheme = window.getTheme() === 'dark'
},
name: 'OTS',
render: createElement => createElement('app'),
router,
watch: {
darkTheme(to) {
window.setTheme(to ? 'dark' : 'light')
},
},
})

18
src/main.ts Normal file
View file

@ -0,0 +1,18 @@
import { createApp, h } from 'vue'
import './style.scss'
import '@fortawesome/fontawesome-free/css/all.css' // All FA free icons
import appView from './app.vue'
import i18n from './i18n.ts'
import router from './router.ts'
const app = createApp({
name: 'OTS',
render() { return h(appView) },
})
app.use(i18n)
app.use(router)
app.mount('#app')

View file

@ -1,10 +1,10 @@
import { createMemoryHistory, createRouter } from 'vue-router'
import AppCreate from './components/create.vue' import AppCreate from './components/create.vue'
import AppDisplayURL from './components/display-url.vue' import AppDisplayURL from './components/display-url.vue'
import AppExplanation from './components/explanation.vue' import AppExplanation from './components/explanation.vue'
import AppSecretDisplay from './components/secret-display.vue' import AppSecretDisplay from './components/secret-display.vue'
import VueRouter from 'vue-router'
const routes = [ const routes = [
{ {
component: AppCreate, component: AppCreate,
@ -37,8 +37,8 @@ const routes = [
}, },
] ]
const router = new VueRouter({ const router = createRouter({
mode: 'abstract', history: createMemoryHistory(),
routes, routes,
}) })

View file

@ -1,8 +1,7 @@
// Force local fonts // Force local fonts
$web-font-path: ''; $web-font-path: '';
@import "../node_modules/bootstrap/dist/css/bootstrap.css"; @use "../node_modules/bootstrap/dist/css/bootstrap.css";
@import "lato";
:root { :root {
textarea { textarea {

1851
yarn.lock Normal file

File diff suppressed because it is too large Load diff