Compare commits
52 Commits
v9.1.4
..
authserver
| Author | SHA1 | Date | |
|---|---|---|---|
| cfe7bb53e6 | |||
| b2ca6206cc | |||
| 918c2f8587 | |||
| 8f851164d6 | |||
| d215d1998f | |||
| 75e3256497 | |||
| 58f5a17a83 | |||
| e7c3d797be | |||
| 34abd5b8f5 | |||
| 8b138d14bb | |||
| e23abd69b5 | |||
| 9c16ad456d | |||
| 4b851afc6a | |||
| f333148afa | |||
| 8d0160a3e7 | |||
| 4a02e988c1 | |||
| 134472cd4b | |||
| b40a10da7b | |||
| 25f5b33d17 | |||
| f57c39bba2 | |||
| 99b234eca8 | |||
| 9c3c8cc9d1 | |||
| b08e3a5128 | |||
| e48cdc85f7 | |||
| a5da68a7f9 | |||
| 7d594ab0d3 | |||
| 9ed3d668ee | |||
| 0da0a5e027 | |||
| 28eb0b65f4 | |||
| 1d29572ecd | |||
| 07e8d242d1 | |||
| 1586a286d8 | |||
| 4859059eba | |||
| f2949c1836 | |||
| cd6acfb91d | |||
| 2d5dc9a6aa | |||
| 87e7da2aff | |||
| 461eb38d88 | |||
| ba0bb62fa3 | |||
| 1ca62dd38e | |||
| 1b1328c601 | |||
| 9633036887 | |||
| e3d76ea9f4 | |||
| d7212e69b5 | |||
| ead58bd6f6 | |||
| fbe13b75df | |||
| 6085a8231f | |||
| e15cd190b3 | |||
| 3d55423deb | |||
| f62df52c1d | |||
| 7829f94ac4 | |||
| e9d42b9cdd |
@@ -4,3 +4,5 @@ installer/src/certs/server.key
|
||||
# vim swap files
|
||||
*.swp
|
||||
|
||||
.cursor
|
||||
|
||||
|
||||
@@ -3186,3 +3186,36 @@
|
||||
* passkey: fix issue where passkeys were lost on restart
|
||||
* passkey: implement passwordless login
|
||||
* oidcserver: fix jwks_rsaonly response
|
||||
|
||||
[9.1.5]
|
||||
* services: lazy start services / on demand services
|
||||
* restore: fix restore of trusted ips and blocklist
|
||||
* dashboard: wait for dashboard reload when version has changed
|
||||
* graphite: fix aggregation of block/network read/write
|
||||
* Workaround chrome quirks on file drop handling
|
||||
* notifications: add empty text, progress bar and inifinite scroll
|
||||
* rsync: throttle log messages during download
|
||||
* backup logs: make them much terse and concise
|
||||
* oidc: implement Device Authorization Grant
|
||||
* operator: fix viewing of backup progress and logs
|
||||
* notification: automatic app update failure notification
|
||||
* backup sites: identify conflicting site locations
|
||||
* update: add policy to update apps and platform separately
|
||||
* passkey: fix issue where passkeys were lost on restart
|
||||
* passkey: implement passwordless login
|
||||
* oidcserver: fix jwks_rsaonly response
|
||||
|
||||
[9.1.6]
|
||||
* apps: fix wrong disabled state for devices config
|
||||
* notifications: send email when manual platform and app update required
|
||||
* source install: support dockerfileName and build options
|
||||
* source install: persist buildConfig so restore, import, clone work correctly
|
||||
* search for matches in app links labels for apps view filter
|
||||
* restore: prune portBindings whose tcpPorts/udpPorts no longer exist
|
||||
* location: fix duplication of port bindings on submit
|
||||
* Update translations
|
||||
* location: show what DNS is being overwritten in location UI
|
||||
* backup site: remove the local disk provider
|
||||
* mail: update haraka to 3.1.4, tika to 3.3.0
|
||||
* solr: dynamically allocate java heap based on container mem
|
||||
|
||||
|
||||
@@ -4,10 +4,11 @@ import constants from './src/constants.js';
|
||||
import fs from 'node:fs';
|
||||
import ldapServer from './src/ldapserver.js';
|
||||
import net from 'node:net';
|
||||
import authServer from './src/authserver.js';
|
||||
import oidcServer from './src/oidcserver.js';
|
||||
import paths from './src/paths.js';
|
||||
import proxyAuth from './src/proxyauth.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import server from './src/server.js';
|
||||
import directoryServer from './src/directoryserver.js';
|
||||
import logger from './src/logger.js';
|
||||
@@ -72,6 +73,7 @@ process.on('SIGINT', async function () {
|
||||
await directoryServer.stop();
|
||||
await ldapServer.stop();
|
||||
await oidcServer.stop();
|
||||
await authServer.stop();
|
||||
|
||||
setTimeout(() => {
|
||||
log('Shutdown complete');
|
||||
@@ -87,6 +89,7 @@ process.on('SIGTERM', async function () {
|
||||
await directoryServer.stop();
|
||||
await ldapServer.stop();
|
||||
await oidcServer.stop();
|
||||
await authServer.stop();
|
||||
|
||||
setTimeout(() => {
|
||||
log('Shutdown complete');
|
||||
|
||||
@@ -1,37 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Confirm Device</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin-top: 25px; margin-bottom: 25px; }
|
||||
h1, h1+p { font-weight: 100; text-align: center; }
|
||||
.container { padding: 0 40px 10px; width: 274px; background-color: #f7f7f7; margin: 0 auto 10px; border-radius: 2px; box-shadow: 0 2px 2px rgba(0,0,0,.3); overflow: hidden; }
|
||||
h1 { font-size: 2.3em; }
|
||||
code { font-size: 2em; }
|
||||
button[autofocus] { width: 100%; display: block; margin-bottom: 10px; font-size: 14px; font-weight: 700; height: 36px; padding: 0 8px; border: 0; color: #fff; background-color: #4d90fe; cursor: pointer; }
|
||||
button[autofocus]:hover { background-color: #357ae8; }
|
||||
button[name=abort] { background: none; border: none; padding: 0; font: inherit; cursor: pointer; color: #666; opacity: .6; }
|
||||
.help { width: 100%; font-size: 12px; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Confirm Device</h1>
|
||||
<p>
|
||||
<strong><%= clientName %></strong>
|
||||
<br/><br/>
|
||||
The following code should be displayed on your device<br/><br/>
|
||||
<code><%= userCode %></code>
|
||||
<br/><br/>
|
||||
<small>If you did not initiate this action or the code does not match, please close this window or click abort.</small>
|
||||
</p>
|
||||
<%- form %>
|
||||
<button autofocus type="submit" form="op.deviceConfirmForm">Continue</button>
|
||||
<div class="help">
|
||||
<button type="submit" form="op.deviceConfirmForm" value="yes" name="abort">[ Abort ]</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<title>OpenID Confirm</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = <%- JSON.stringify({ name, clientName, userCode, form }) %>;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/oidcdeviceconfirm.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sign-in</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin-top: 25px; margin-bottom: 25px; }
|
||||
h1, h1+p { font-weight: 100; text-align: center; }
|
||||
.container { padding: 0 40px 10px; width: 274px; background-color: #f7f7f7; margin: 0 auto 10px; border-radius: 2px; box-shadow: 0 2px 2px rgba(0,0,0,.3); overflow: hidden; }
|
||||
h1 { font-size: 2.3em; }
|
||||
p.red { color: #d50000; }
|
||||
input[type=text] { height: 44px; font-size: 16px; width: 100%; margin-bottom: 10px; background: #fff; border: 1px solid #d9d9d9; border-top: 1px solid silver; padding: 0 8px; box-sizing: border-box; text-transform: uppercase; text-align: center; }
|
||||
input[type=text]::placeholder { text-transform: none; }
|
||||
[type=submit] { width: 100%; display: block; margin-bottom: 10px; text-align: center; font-size: 14px; font-weight: 700; height: 36px; padding: 0 8px; border: 0; color: #fff; background-color: #4d90fe; cursor: pointer; }
|
||||
[type=submit]:hover { background-color: #357ae8; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Sign-in</h1>
|
||||
<%- message %>
|
||||
<%- form %>
|
||||
<button type="submit" form="op.deviceInputForm">Continue</button>
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<title>OpenID Device Sign-in</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = <%- JSON.stringify({ name, message, form }) %>;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/oidcdeviceinput.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Success</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<style>
|
||||
body { font-family: system-ui, sans-serif; margin-top: 25px; margin-bottom: 25px; }
|
||||
h1, h1+p { font-weight: 100; text-align: center; }
|
||||
.container { padding: 0 40px 10px; width: 274px; background-color: #f7f7f7; margin: 0 auto 10px; border-radius: 2px; box-shadow: 0 2px 2px rgba(0,0,0,.3); overflow: hidden; }
|
||||
h1 { font-size: 2.3em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Success</h1>
|
||||
<p>Your device has been authorized. You can close this window.</p>
|
||||
</div>
|
||||
</body>
|
||||
<head>
|
||||
<title>OpenID Device Success</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = <%- JSON.stringify({ name }) %>;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/oidcdevicesuccess.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,13 +4,7 @@
|
||||
<title><%= name %> OpenID Error</title>
|
||||
|
||||
<script>
|
||||
window.cloudron = <%- JSON.stringify({
|
||||
iconUrl: iconUrl,
|
||||
name: name,
|
||||
errorMessage: errorMessage,
|
||||
footer: footer,
|
||||
language: language
|
||||
}) %>;
|
||||
window.cloudron = <%- JSON.stringify({ iconUrl, name, errorMessage, footer, language }) %>;
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
Generated
+173
-202
@@ -5,7 +5,7 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@cloudron/pankow": "^4.1.5",
|
||||
"@cloudron/pankow": "^4.1.8",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
"@simplewebauthn/browser": "^13.3.0",
|
||||
@@ -17,16 +17,16 @@
|
||||
"async": "^3.2.6",
|
||||
"chart.js": "^4.5.1",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint": "^10.1.0",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"marked": "^17.0.4",
|
||||
"marked": "^17.0.5",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"vite": "^8.0.0",
|
||||
"moment-timezone": "^0.6.1",
|
||||
"vite": "^8.0.3",
|
||||
"vite-plugin-singlefile": "^2.3.2",
|
||||
"vue": "^3.5.30",
|
||||
"vue": "^3.5.31",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^5.0.3"
|
||||
"vue-router": "^5.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
@@ -92,43 +92,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cloudron/pankow": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-4.1.5.tgz",
|
||||
"integrity": "sha512-VPnRXQ6m9jSV07e8Hwg/t+If/POYlzym+fuSd9L/13uP+uDovUzVqvXWmPPjYeyNkRvXQAxJv6IMXZ8Q74wFxw==",
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@cloudron/pankow/-/pankow-4.1.8.tgz",
|
||||
"integrity": "sha512-JahcrQPVJPGvw+c+6T1k/KVqm4oTHIux5kHvGbk9O+gECoCbkYEhdLDKN6oGWb7UlvtfRRNYPN/gxpcPeCJCfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
"filesize": "^11.0.13",
|
||||
"filesize": "^11.0.15",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"online-3d-viewer": "^0.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz",
|
||||
"integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.2.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz",
|
||||
"integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
|
||||
"integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
||||
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -413,43 +392,36 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
|
||||
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz",
|
||||
"integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/runtime": {
|
||||
"version": "0.115.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.115.0.tgz",
|
||||
"integrity": "sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-project/types": {
|
||||
"version": "0.115.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.115.0.tgz",
|
||||
"integrity": "sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==",
|
||||
"version": "0.122.0",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz",
|
||||
"integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-android-arm64": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -463,9 +435,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -479,9 +451,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-darwin-x64": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -495,9 +467,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -511,9 +483,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -527,9 +499,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -543,9 +515,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -559,9 +531,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -575,9 +547,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -591,9 +563,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -607,9 +579,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -623,9 +595,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -639,9 +611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
@@ -655,9 +627,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -671,9 +643,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1112,39 +1084,39 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.30.tgz",
|
||||
"integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz",
|
||||
"integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@vue/shared": "3.5.30",
|
||||
"@babel/parser": "^7.29.2",
|
||||
"@vue/shared": "3.5.31",
|
||||
"entities": "^7.0.1",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz",
|
||||
"integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz",
|
||||
"integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.30",
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/compiler-core": "3.5.31",
|
||||
"@vue/shared": "3.5.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz",
|
||||
"integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz",
|
||||
"integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.29.0",
|
||||
"@vue/compiler-core": "3.5.30",
|
||||
"@vue/compiler-dom": "3.5.30",
|
||||
"@vue/compiler-ssr": "3.5.30",
|
||||
"@vue/shared": "3.5.30",
|
||||
"@babel/parser": "^7.29.2",
|
||||
"@vue/compiler-core": "3.5.31",
|
||||
"@vue/compiler-dom": "3.5.31",
|
||||
"@vue/compiler-ssr": "3.5.31",
|
||||
"@vue/shared": "3.5.31",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.21",
|
||||
"postcss": "^8.5.8",
|
||||
@@ -1152,13 +1124,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz",
|
||||
"integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz",
|
||||
"integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.30",
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/compiler-dom": "3.5.31",
|
||||
"@vue/shared": "3.5.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
@@ -1186,53 +1158,53 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.30.tgz",
|
||||
"integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz",
|
||||
"integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/shared": "3.5.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.30.tgz",
|
||||
"integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz",
|
||||
"integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.30",
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/reactivity": "3.5.31",
|
||||
"@vue/shared": "3.5.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz",
|
||||
"integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz",
|
||||
"integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.30",
|
||||
"@vue/runtime-core": "3.5.30",
|
||||
"@vue/shared": "3.5.30",
|
||||
"@vue/reactivity": "3.5.31",
|
||||
"@vue/runtime-core": "3.5.31",
|
||||
"@vue/shared": "3.5.31",
|
||||
"csstype": "^3.2.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.30.tgz",
|
||||
"integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz",
|
||||
"integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.30",
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/compiler-ssr": "3.5.31",
|
||||
"@vue/shared": "3.5.31"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.30"
|
||||
"vue": "3.5.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.30.tgz",
|
||||
"integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz",
|
||||
"integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@xterm/addon-attach": {
|
||||
@@ -1538,16 +1510,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.3.tgz",
|
||||
"integrity": "sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.1.0.tgz",
|
||||
"integrity": "sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.8.0",
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@eslint/config-array": "^0.23.3",
|
||||
"@eslint/config-helpers": "^0.5.2",
|
||||
"@eslint/config-helpers": "^0.5.3",
|
||||
"@eslint/core": "^1.1.1",
|
||||
"@eslint/plugin-kit": "^0.6.1",
|
||||
"@humanfs/node": "^0.16.6",
|
||||
@@ -1560,7 +1532,7 @@
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"eslint-scope": "^9.1.2",
|
||||
"eslint-visitor-keys": "^5.0.1",
|
||||
"espree": "^11.1.1",
|
||||
"espree": "^11.2.0",
|
||||
"esquery": "^1.7.0",
|
||||
"esutils": "^2.0.2",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
@@ -1779,9 +1751,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/filesize": {
|
||||
"version": "11.0.13",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.13.tgz",
|
||||
"integrity": "sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw==",
|
||||
"version": "11.0.15",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-11.0.15.tgz",
|
||||
"integrity": "sha512-30TpbYxQxCpi4XdVjkwXYQ37CzZltV38+P7MYroQ+4NK/Dmx9mxixFNrolzcmEIBsjT/uowC9T7kiy2+C12r1A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 10.8.0"
|
||||
@@ -2290,9 +2262,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "17.0.4",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.4.tgz",
|
||||
"integrity": "sha512-NOmVMM+KAokHMvjWmC5N/ZOvgmSWuqJB8FoYI019j4ogb/PeRMKoKIjReZ2w3376kkA8dSJIP8uD993Kxc0iRQ==",
|
||||
"version": "17.0.5",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-17.0.5.tgz",
|
||||
"integrity": "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
@@ -2380,9 +2352,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/moment-timezone": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.0.tgz",
|
||||
"integrity": "sha512-ldA5lRNm3iJCWZcBCab4pnNL3HSZYXVb/3TYr75/1WCTWYuTqYUb5f/S384pncYjJ88lbO8Z4uPDvmoluHJc8Q==",
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.6.1.tgz",
|
||||
"integrity": "sha512-1B9lmAhB9D9/sHaPC1N7wLFEVUoFldxOpOO96lOD1PvJ43vCd0ozDPbu0FEL3++VvawOlDkq8YD373tJmP5JHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"moment": "^2.29.4"
|
||||
@@ -2562,9 +2534,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -2673,13 +2645,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@oxc-project/types": "=0.115.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.9"
|
||||
"@oxc-project/types": "=0.122.0",
|
||||
"@rolldown/pluginutils": "1.0.0-rc.12"
|
||||
},
|
||||
"bin": {
|
||||
"rolldown": "bin/cli.mjs"
|
||||
@@ -2688,27 +2660,27 @@
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.9",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.9",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.9",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.9",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.9",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.9",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.9",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.9",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.9"
|
||||
"@rolldown/binding-android-arm64": "1.0.0-rc.12",
|
||||
"@rolldown/binding-darwin-arm64": "1.0.0-rc.12",
|
||||
"@rolldown/binding-darwin-x64": "1.0.0-rc.12",
|
||||
"@rolldown/binding-freebsd-x64": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12",
|
||||
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.12",
|
||||
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.12",
|
||||
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.12",
|
||||
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12",
|
||||
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12"
|
||||
}
|
||||
},
|
||||
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-rc.9",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.9.tgz",
|
||||
"integrity": "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==",
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
@@ -2909,17 +2881,16 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.0.tgz",
|
||||
"integrity": "sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==",
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz",
|
||||
"integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@oxc-project/runtime": "0.115.0",
|
||||
"lightningcss": "^1.32.0",
|
||||
"picomatch": "^4.0.3",
|
||||
"picomatch": "^4.0.4",
|
||||
"postcss": "^8.5.8",
|
||||
"rolldown": "1.0.0-rc.9",
|
||||
"rolldown": "1.0.0-rc.12",
|
||||
"tinyglobby": "^0.2.15"
|
||||
},
|
||||
"bin": {
|
||||
@@ -2936,7 +2907,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^20.19.0 || >=22.12.0",
|
||||
"@vitejs/devtools": "^0.0.0-alpha.31",
|
||||
"@vitejs/devtools": "^0.1.0",
|
||||
"esbuild": "^0.27.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "^4.0.0",
|
||||
@@ -3004,17 +2975,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.30",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.30.tgz",
|
||||
"integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==",
|
||||
"version": "3.5.31",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz",
|
||||
"integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.30",
|
||||
"@vue/compiler-sfc": "3.5.30",
|
||||
"@vue/runtime-dom": "3.5.30",
|
||||
"@vue/server-renderer": "3.5.30",
|
||||
"@vue/shared": "3.5.30"
|
||||
"@vue/compiler-dom": "3.5.31",
|
||||
"@vue/compiler-sfc": "3.5.31",
|
||||
"@vue/runtime-dom": "3.5.31",
|
||||
"@vue/server-renderer": "3.5.31",
|
||||
"@vue/shared": "3.5.31"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -3071,9 +3042,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.3.tgz",
|
||||
"integrity": "sha512-nG1c7aAFac7NYj8Hluo68WyWfc41xkEjaR0ViLHCa3oDvTQ/nIuLJlXJX1NUPw/DXzx/8+OKMng045HHQKQKWw==",
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-5.0.4.tgz",
|
||||
"integrity": "sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/generator": "^7.28.6",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@simplewebauthn/browser": "^13.3.0",
|
||||
"@cloudron/pankow": "^4.1.5",
|
||||
"@cloudron/pankow": "^4.1.8",
|
||||
"@fontsource/inter": "^5.2.8",
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
"@vitejs/plugin-vue": "^6.0.5",
|
||||
@@ -19,15 +19,15 @@
|
||||
"async": "^3.2.6",
|
||||
"chart.js": "^4.5.1",
|
||||
"chartjs-plugin-annotation": "^3.1.0",
|
||||
"eslint": "^10.0.3",
|
||||
"eslint": "^10.1.0",
|
||||
"eslint-plugin-vue": "^10.8.0",
|
||||
"marked": "^17.0.4",
|
||||
"marked": "^17.0.5",
|
||||
"moment": "^2.30.1",
|
||||
"moment-timezone": "^0.6.0",
|
||||
"vite": "^8.0.0",
|
||||
"moment-timezone": "^0.6.1",
|
||||
"vite": "^8.0.3",
|
||||
"vite-plugin-singlefile": "^2.3.2",
|
||||
"vue": "^3.5.30",
|
||||
"vue": "^3.5.31",
|
||||
"vue-i18n": "^11.3.0",
|
||||
"vue-router": "^5.0.3"
|
||||
"vue-router": "^5.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,9 @@
|
||||
"configure": "Nakonfigurovat",
|
||||
"restart": "Restartovat",
|
||||
"reset": "Zresetovat",
|
||||
"loadMore": "Načíst více"
|
||||
"loadMore": "Načíst více",
|
||||
"setup": "Nastavit",
|
||||
"disable": "Zakázat"
|
||||
},
|
||||
"rebootDialog": {
|
||||
"title": "Restart serveru",
|
||||
@@ -361,8 +363,23 @@
|
||||
},
|
||||
"twoFactorAuth": {
|
||||
"title": "Dvoufaktorová autentizace",
|
||||
"totpEnabled": "Použít časově omezené jednorázové heslo (TOTP)",
|
||||
"passkeyEnabled": "Použít passkey"
|
||||
"totpEnabled": "Povoleno",
|
||||
"passkeyEnabled": "Povoleno",
|
||||
"totpTitle": "TOTP",
|
||||
"passkeyTitle": "Passkey"
|
||||
},
|
||||
"notSet": "nenastaveno",
|
||||
"enablePasskey": {
|
||||
"title": "Povolit passkey"
|
||||
},
|
||||
"enableTotp": {
|
||||
"title": "Povolit TOTP"
|
||||
},
|
||||
"disableTotp": {
|
||||
"title": "Zakázat TOTP"
|
||||
},
|
||||
"disablePasskey": {
|
||||
"title": "Zakázat passkey"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
@@ -648,7 +665,10 @@
|
||||
"stopUpdateAction": "Zastavit aktualizaci",
|
||||
"disabled": "Zakázáno",
|
||||
"onLatest": "poslední",
|
||||
"description": "Aktualizace platformy a aplikací se aplikují v nastavený čas podle <a href=\"/#/system-settings\">časové zóny systému</a>."
|
||||
"description": "Aktualizace se aplikují v nastavený čas podle <a href=\"/#/system-settings\">časové zóny systému</a>.",
|
||||
"config": "Automatické aktualizace",
|
||||
"platformAndApps": "Platforma a aplikace",
|
||||
"appsOnly": "Pouze aplikace"
|
||||
},
|
||||
"updateScheduleDialog": {
|
||||
"disableCheckbox": "Zakázat automatické aktualizace",
|
||||
@@ -673,6 +693,14 @@
|
||||
"registryConfig": {
|
||||
"provider": "Poskytovatel docker registrů",
|
||||
"providerOther": "Jiné"
|
||||
},
|
||||
"configureUpdates": {
|
||||
"title": "Konfigurovat automatické aktualizace",
|
||||
"policy": "Politika",
|
||||
"policyDescription": "Vyberte, co se bude automaticky aktualizovat",
|
||||
"days": "Dnů/y",
|
||||
"hours": "Hodin/y",
|
||||
"schedule": "Plány záloh"
|
||||
}
|
||||
},
|
||||
"branding": {
|
||||
@@ -884,7 +912,8 @@
|
||||
"rebootRequired": "Vyžadován restart serveru",
|
||||
"cloudronUpdateFailed": "Aktualizace Cloudronu selhala",
|
||||
"diskSpace": "Nedostatek místa na disku",
|
||||
"appAutoUpdateFailed": "Automatická aktualizace aplikace selhala"
|
||||
"appAutoUpdateFailed": "Automatická aktualizace aplikace selhala",
|
||||
"manualUpdateRequired": "Platforma nebo aplikace vyžaduje ruční aktualizaci"
|
||||
},
|
||||
"settingsDialog": {
|
||||
"description": "Na váš primární e-mail bude odeslán e-mail souhrn těchto vybraných událostí."
|
||||
@@ -941,7 +970,6 @@
|
||||
"noRedirections": "Žádné přesměrované domény",
|
||||
"addRedirectionAction": "Přidat přesměrování",
|
||||
"saveAction": "Uložit",
|
||||
"dnsoverwrite": "Některé DNS záznamy již existují. Potvrďte přepsání.",
|
||||
"aliases": "Aliasy",
|
||||
"addAliasAction": "Přidat alias",
|
||||
"noAliases": "Žádné aliasy pro domény"
|
||||
@@ -1594,7 +1622,9 @@
|
||||
"errorIncorrect2FAToken": "Token 2FA je neplatný",
|
||||
"errorInternal": "Interní chyba, zkuste akci opakovat později",
|
||||
"loginAction": "Přihlásit se",
|
||||
"usePasskeyAction": "Použít passkey"
|
||||
"usePasskeyAction": "Použít passkey",
|
||||
"passkeyAction": "Přihlásit se přes passkey",
|
||||
"errorPasskeyFailed": "Přihlášení pomocí passkey selhalo"
|
||||
},
|
||||
"passwordReset": {
|
||||
"title": "Reset hesla",
|
||||
|
||||
@@ -1414,8 +1414,7 @@
|
||||
"addRedirectionAction": "Eine Weiterleitung hinzufügen",
|
||||
"noAliases": "Keine Aliasse",
|
||||
"addAliasAction": "Alias hinzufügen",
|
||||
"aliases": "Aliasse",
|
||||
"dnsoverwrite": "Einige DNS-Einträge existieren bereits. Mit dem Überschreiben einverstanden."
|
||||
"aliases": "Aliasse"
|
||||
},
|
||||
"updateDialog": {
|
||||
"subscriptionExpired": "Das Cloudron-Abonnement ist abgelaufen. Bitte ein Abonnement einrichten, um die Anwendung zu aktualisieren.",
|
||||
|
||||
@@ -912,7 +912,8 @@
|
||||
"rebootRequired": "Server reboot required",
|
||||
"cloudronUpdateFailed": "Cloudron update failed",
|
||||
"diskSpace": "Low disk space",
|
||||
"appAutoUpdateFailed": "App automatic update failed"
|
||||
"appAutoUpdateFailed": "App automatic update failed",
|
||||
"manualUpdateRequired": "Platform or app requires manual update"
|
||||
},
|
||||
"settingsDialog": {
|
||||
"description": "An email will be sent for the selected events to your primary email."
|
||||
@@ -1229,7 +1230,7 @@
|
||||
"aliases": "Aliases",
|
||||
"addAliasAction": "Add an alias",
|
||||
"noAliases": "No alias domains",
|
||||
"dnsoverwrite": "Some DNS records already exist. Agree to overwrite."
|
||||
"overwriteDns": "Overwrite existing DNS records of {domains}"
|
||||
},
|
||||
"accessControl": {
|
||||
"userManagement": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,9 @@
|
||||
"restart": "Mulai ulang",
|
||||
"reset": "Atur Ulang",
|
||||
"logs": "Log",
|
||||
"loadMore": "Muat lebih banyak"
|
||||
"loadMore": "Muat lebih banyak",
|
||||
"setup": "Siapkan",
|
||||
"disable": "Nonaktifkan"
|
||||
},
|
||||
"searchPlaceholder": "Cari",
|
||||
"actions": "Tindakan",
|
||||
@@ -361,8 +363,23 @@
|
||||
},
|
||||
"twoFactorAuth": {
|
||||
"title": "Autentikasi dua faktor",
|
||||
"totpEnabled": "Menggunakan kata sandi sekali pakai berbasis waktu (TOTP)",
|
||||
"passkeyEnabled": "Menggunakan passkey"
|
||||
"totpEnabled": "Diaktifkan",
|
||||
"passkeyEnabled": "Diaktifkan",
|
||||
"totpTitle": "TOTP",
|
||||
"passkeyTitle": "Passkey"
|
||||
},
|
||||
"notSet": "Belum diatur",
|
||||
"enablePasskey": {
|
||||
"title": "Aktifkan passkey"
|
||||
},
|
||||
"enableTotp": {
|
||||
"title": "Aktifkan TOTP"
|
||||
},
|
||||
"disableTotp": {
|
||||
"title": "Nonaktifkan TOTP"
|
||||
},
|
||||
"disablePasskey": {
|
||||
"title": "Nonaktifkan Passkey"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
@@ -724,8 +741,11 @@
|
||||
"updateAvailableAction": "Pembaruan tersedia",
|
||||
"stopUpdateAction": "Hentikan pembaruan",
|
||||
"disabled": "Dinonaktifkan",
|
||||
"description": "Pembaruan platform dan aplikasi diterapkan sesuai jadwal yang telah dikonfigurasi, menggunakan <a href=\"/#/system-settings\">Zona waktu sistem</a>.",
|
||||
"onLatest": "terbaru"
|
||||
"description": "Pembaruan diterapkan sesuai jadwal yang telah dikonfigurasi, menggunakan <a href=\"/#/system-settings\">Zona waktu sistem</a>.",
|
||||
"onLatest": "terbaru",
|
||||
"config": "Pembaruan otomatis",
|
||||
"appsOnly": "Hanya aplikasi",
|
||||
"platformAndApps": "Platform & aplikasi"
|
||||
},
|
||||
"updateScheduleDialog": {
|
||||
"disableCheckbox": "Nonaktifkan pembaruan otomatis",
|
||||
@@ -750,6 +770,14 @@
|
||||
"registryConfig": {
|
||||
"provider": "Penyedia registri Docker",
|
||||
"providerOther": "Lainnya"
|
||||
},
|
||||
"configureUpdates": {
|
||||
"title": "Konfigurasi Pembaruan Otomatis",
|
||||
"policy": "Kebijakan",
|
||||
"policyDescription": "Pilih apa yang diperbarui secara otomatis",
|
||||
"days": "Hari",
|
||||
"hours": "Jam",
|
||||
"schedule": "Jadwal"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
@@ -1179,8 +1207,7 @@
|
||||
"saveAction": "Simpan",
|
||||
"aliases": "Alias",
|
||||
"addAliasAction": "Tambahkan alias",
|
||||
"noAliases": "Tidak ada domain alias",
|
||||
"dnsoverwrite": "Beberapa catatan DNS sudah ada. Setuju untuk menimpa."
|
||||
"noAliases": "Tidak ada domain alias"
|
||||
},
|
||||
"accessControl": {
|
||||
"userManagement": {
|
||||
@@ -1662,7 +1689,9 @@
|
||||
"appDown": "Aplikasi sedang tidak berfungsi",
|
||||
"rebootRequired": "Diperlukan menyalakan ulang server",
|
||||
"cloudronUpdateFailed": "Pembaruan Cloudron gagal",
|
||||
"diskSpace": "Ruang disk hampir penuh"
|
||||
"diskSpace": "Ruang disk hampir penuh",
|
||||
"appAutoUpdateFailed": "Pembaruan otomatis aplikasi gagal",
|
||||
"manualUpdateRequired": "Platform atau aplikasi memerlukan pembaruan manual"
|
||||
},
|
||||
"settingsDialog": {
|
||||
"description": "E-mail akan dikirimkan ke e-mail utama Anda untuk acara-acara yang dipilih."
|
||||
@@ -1688,7 +1717,9 @@
|
||||
"errorIncorrect2FAToken": "Token 2FA tidak valid",
|
||||
"errorInternal": "Terjadi kesalahan internal, coba lagi nanti",
|
||||
"loginAction": "Masuk",
|
||||
"usePasskeyAction": "Gunakan passkey"
|
||||
"usePasskeyAction": "Gunakan passkey",
|
||||
"errorPasskeyFailed": "Gagal masuk dengan passkey",
|
||||
"passkeyAction": "Masuk dengan passkey"
|
||||
},
|
||||
"passwordReset": {
|
||||
"title": "Pengaturan ulang kata sandi",
|
||||
|
||||
@@ -47,7 +47,9 @@
|
||||
"configure": "Configureer",
|
||||
"restart": "Herstart",
|
||||
"reset": "Reset",
|
||||
"loadMore": "Laad meer"
|
||||
"loadMore": "Laad meer",
|
||||
"setup": "Instellen",
|
||||
"disable": "Uitschakelen"
|
||||
},
|
||||
"rebootDialog": {
|
||||
"title": "Herstart Server",
|
||||
@@ -361,8 +363,23 @@
|
||||
},
|
||||
"twoFactorAuth": {
|
||||
"title": "Twee-Factor (2FA) authenticatie",
|
||||
"totpEnabled": "Gebruikt tijdgebaseerd eenmalige wachtwoord (TOTP)",
|
||||
"passkeyEnabled": "Gebruikt passkey"
|
||||
"totpEnabled": "Ingeschakeld",
|
||||
"passkeyEnabled": "Ingeschakeld",
|
||||
"totpTitle": "TOTP",
|
||||
"passkeyTitle": "Passkey"
|
||||
},
|
||||
"notSet": "Niet ingesteld",
|
||||
"enablePasskey": {
|
||||
"title": "Passkey activeren"
|
||||
},
|
||||
"enableTotp": {
|
||||
"title": "TOTP activeren"
|
||||
},
|
||||
"disableTotp": {
|
||||
"title": "TOTP Uitschakelen"
|
||||
},
|
||||
"disablePasskey": {
|
||||
"title": "Passkey uitschakelen"
|
||||
}
|
||||
},
|
||||
"backups": {
|
||||
@@ -768,8 +785,7 @@
|
||||
"noRedirections": "Geen domein-omleidingen",
|
||||
"noAliases": "Geen alias-domeinen",
|
||||
"addAliasAction": "Alias toevoegen",
|
||||
"aliases": "Aliassen",
|
||||
"dnsoverwrite": "Sommige DNS records bestaan al. Weet je zeker dat ze overschreven moeten worden?"
|
||||
"aliases": "Aliassen"
|
||||
},
|
||||
"accessControl": {
|
||||
"userManagement": {
|
||||
@@ -1141,9 +1157,12 @@
|
||||
"checkForUpdatesAction": "Controleer op updates",
|
||||
"updateAvailableAction": "Update beschikbaar",
|
||||
"stopUpdateAction": "Stop update",
|
||||
"description": "Platform en app updates worden toegepast met de geconfigureerde planning met deze <a href=\"/#/system-locale\">Systeem tijdzone</a>.",
|
||||
"description": "Updates worden toegepast volgens het geconfigureerde schema, met behulp van de <a href=\"/#/system-settings\">System time zone</a>.",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"onLatest": "Laatste"
|
||||
"onLatest": "Laatste",
|
||||
"config": "Automatische updates",
|
||||
"appsOnly": "Alleen Apps",
|
||||
"platformAndApps": "Platform & Apps"
|
||||
},
|
||||
"updateScheduleDialog": {
|
||||
"disableCheckbox": "Automatische updates uitschakelen",
|
||||
@@ -1169,6 +1188,14 @@
|
||||
"registryConfig": {
|
||||
"provider": "Docker registry aanbieder",
|
||||
"providerOther": "Anders"
|
||||
},
|
||||
"configureUpdates": {
|
||||
"title": "Automatische updates configureren",
|
||||
"policy": "Beleid",
|
||||
"policyDescription": "Kies wat er automatisch wordt bijgewerkt",
|
||||
"days": "Dagen",
|
||||
"hours": "Uren",
|
||||
"schedule": "Planning"
|
||||
}
|
||||
},
|
||||
"support": {
|
||||
@@ -1226,7 +1253,9 @@
|
||||
"appDown": "App werkt niet",
|
||||
"rebootRequired": "Server herstart noodzakelijk",
|
||||
"cloudronUpdateFailed": "Cloudron update mislukt",
|
||||
"diskSpace": "Weinig diskruimte"
|
||||
"diskSpace": "Weinig diskruimte",
|
||||
"appAutoUpdateFailed": "Automatische update van de app is mislukt",
|
||||
"manualUpdateRequired": "Platform of app moet handmatig geüpdatet worden"
|
||||
},
|
||||
"settingsDialog": {
|
||||
"description": "Een e-mail wordt verstuurd voor de geselecteerde gebeurtenissen naar je primaire e-mail."
|
||||
@@ -1513,7 +1542,9 @@
|
||||
"errorIncorrect2FAToken": "2FA token is niet geldig",
|
||||
"errorInternal": "Interne fout, probeer later opnieuw",
|
||||
"loginAction": "Inloggen",
|
||||
"usePasskeyAction": "Gebruik een passkey"
|
||||
"usePasskeyAction": "Gebruik een passkey",
|
||||
"errorPasskeyFailed": "Inloggen met passkey mislukt",
|
||||
"passkeyAction": "Inloggen met een passkey"
|
||||
},
|
||||
"passwordReset": {
|
||||
"title": "Wachtwoord herstellen",
|
||||
|
||||
@@ -419,8 +419,7 @@
|
||||
"saveAction": "Сохранить",
|
||||
"aliases": "Псевдонимы",
|
||||
"addAliasAction": "Добавить псевдоним",
|
||||
"noAliases": "Домены-псевдонимы отсутствуют",
|
||||
"dnsoverwrite": "Некоторые DNS записи уже существуют. Подтвердите перезапись."
|
||||
"noAliases": "Домены-псевдонимы отсутствуют"
|
||||
},
|
||||
"accessControl": {
|
||||
"sftp": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+16
-2
@@ -14,6 +14,7 @@ import ProvisionModel from './models/ProvisionModel.js';
|
||||
import NotificationsModel from './models/NotificationsModel.js';
|
||||
import DashboardModel from './models/DashboardModel.js';
|
||||
import BrandingModel from './models/BrandingModel.js';
|
||||
import AppstoreModel from './models/AppstoreModel.js';
|
||||
import Headerbar from './components/Headerbar.vue';
|
||||
import SubscriptionRequiredDialog from './components/SubscriptionRequiredDialog.vue';
|
||||
import RequestErrorDialog from './components/RequestErrorDialog.vue';
|
||||
@@ -277,6 +278,7 @@ const dashboardModel = DashboardModel.create();
|
||||
const profileModel = ProfileModel.create();
|
||||
const provisionModel = ProvisionModel.create();
|
||||
const notificationModel = NotificationsModel.create();
|
||||
const appstoreModel = AppstoreModel.create();
|
||||
|
||||
const inputDialog = useTemplateRef('inputDialog');
|
||||
const subscriptionRequiredDialog = useTemplateRef('subscriptionRequiredDialog');
|
||||
@@ -405,6 +407,14 @@ async function refreshNotifications() {
|
||||
notificationCount.value = result.length;
|
||||
}
|
||||
|
||||
async function refreshSubscription() {
|
||||
const [error, result] = await appstoreModel.getSubscription();
|
||||
if (error && error.status === 402) console.error('Not yet registered');
|
||||
else if (error && error.status === 412) window.location.href = ''
|
||||
else if (error) console.error(error);
|
||||
else subscription.value = result;
|
||||
}
|
||||
|
||||
async function onOnline() {
|
||||
ready.value = true;
|
||||
await refreshConfigAndFeatures(); // reload dashboard if needed after an update
|
||||
@@ -416,6 +426,7 @@ function checkForMobile() {
|
||||
}
|
||||
|
||||
provide('subscriptionRequiredDialog', subscriptionRequiredDialog);
|
||||
provide('subscription', subscription);
|
||||
provide('features', features);
|
||||
provide('profile', profile);
|
||||
provide('refreshProfile', refreshProfile);
|
||||
@@ -455,7 +466,10 @@ onMounted(async () => {
|
||||
|
||||
console.log(`Cloudron dashboard v${config.value.version}`);
|
||||
|
||||
if (profile.value.isAtLeastAdmin) refreshNotifications();
|
||||
if (profile.value.isAtLeastAdmin) {
|
||||
refreshNotifications();
|
||||
refreshSubscription();
|
||||
}
|
||||
|
||||
ready.value = true;
|
||||
|
||||
@@ -483,7 +497,7 @@ onUnmounted(() => {
|
||||
<SideBar v-if="profile.isAtLeastUserManager" :items="menuItems" :cloudron-name="config.cloudronName" :cloudron-avatar-url="avatarUrl"/>
|
||||
|
||||
<div style="flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; height: 100%;">
|
||||
<Headerbar :config="config" :subscription="subscription" :notification-count="notificationCount"/>
|
||||
<Headerbar :config="config" :notification-count="notificationCount"/>
|
||||
|
||||
<div style="display: flex; justify-content: center; overflow: auto; flex-grow: 1; padding: 0; margin: 0 10px; position: relative;">
|
||||
<KeepAlive>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ref, computed, useTemplateRef, onMounted, inject, watch } from 'vue';
|
||||
import { marked } from 'marked';
|
||||
import { Button, Dialog, SingleSelect, FormGroup, TextInput, InputGroup, Spinner } from '@cloudron/pankow';
|
||||
import { Button, Checkbox, Dialog, SingleSelect, FormGroup, TextInput, InputGroup, Spinner } from '@cloudron/pankow';
|
||||
import { prettyDate, prettyBinarySize } from '@cloudron/pankow/utils';
|
||||
import AccessControl from './AccessControl.vue';
|
||||
import PortBindings from './PortBindings.vue';
|
||||
@@ -42,6 +42,12 @@ const domains = ref([]);
|
||||
|
||||
const form = ref(null); // assigned via "Function Ref" because it is inside v-if
|
||||
const isFormValid = ref(false);
|
||||
function resetDnsOverwrite() {
|
||||
needsOverwriteDns.value = [];
|
||||
overwriteDns.value = false;
|
||||
formError.value = {};
|
||||
}
|
||||
|
||||
async function checkValidity() {
|
||||
isFormValid.value = form.value ? form.value.checkValidity() : false;
|
||||
|
||||
@@ -89,7 +95,8 @@ const tcpPorts = ref({});
|
||||
const udpPorts = ref({});
|
||||
const secondaryDomains = ref({});
|
||||
const upstreamUri = ref('');
|
||||
const needsOverwriteDns = ref(false);
|
||||
const overwriteDns = ref(false);
|
||||
const needsOverwriteDns = ref([]);
|
||||
const users = ref([]);
|
||||
const groups = ref([]);
|
||||
|
||||
@@ -98,7 +105,7 @@ function onDomainChange() {
|
||||
domainProvider.value = tmp ? tmp.provider : '';
|
||||
}
|
||||
|
||||
async function onSubmit(overwriteDns) {
|
||||
async function onSubmit() {
|
||||
if (!form.value.reportValidity()) return;
|
||||
|
||||
formError.value = {};
|
||||
@@ -111,6 +118,7 @@ async function onSubmit(overwriteDns) {
|
||||
|
||||
for (const d in secondaryDomains.value) checkForDomains.push({ domain: secondaryDomains.value[d].domain, subdomain: secondaryDomains.value[d].value });
|
||||
|
||||
const conflicting = [];
|
||||
for (const d of checkForDomains) {
|
||||
const [error, result] = await domainsModel.checkRecords(d.domain, d.subdomain);
|
||||
if (error) {
|
||||
@@ -119,12 +127,14 @@ async function onSubmit(overwriteDns) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
if (result.needsOverwrite && !overwriteDns) {
|
||||
busy.value = false;
|
||||
needsOverwriteDns.value = true;
|
||||
formError.value.dnsExists = `DNS record for ${d.subdomain}.${d.domain} already exists`;
|
||||
return;
|
||||
}
|
||||
if (result.needsOverwrite) conflicting.push((d.subdomain ? d.subdomain + '.' : '') + d.domain);
|
||||
}
|
||||
|
||||
if (conflicting.length > 0 && !overwriteDns.value) {
|
||||
busy.value = false;
|
||||
needsOverwriteDns.value = conflicting;
|
||||
formError.value.generic = `DNS records of ${conflicting.join(', ')} already exist`;
|
||||
return;
|
||||
}
|
||||
|
||||
const config = {
|
||||
@@ -133,7 +143,7 @@ async function onSubmit(overwriteDns) {
|
||||
accessRestriction: accessRestrictionOption.value === ACL_OPTIONS.ANY ? null : (accessRestrictionOption.value === ACL_OPTIONS.NOSSO ? null : accessRestrictionAcl.value)
|
||||
};
|
||||
|
||||
if (overwriteDns) config.overwriteDns = true;
|
||||
if (overwriteDns.value) config.overwriteDns = true;
|
||||
|
||||
if (manifest.value.optionalSso) config.sso = accessRestrictionOption.value !== ACL_OPTIONS.NOSSO;
|
||||
|
||||
@@ -185,7 +195,7 @@ function onClose() {
|
||||
onMounted(async () => {
|
||||
let [error, result] = await usersModel.list();
|
||||
if (error) return console.error(error);
|
||||
result.forEach(u => { u.label = u.displayName || u.username || u.email });
|
||||
result.forEach(u => { u.label = u.displayName || u.username || u.email; });
|
||||
users.value = result;
|
||||
|
||||
[error, result] = await groupsModel.list();
|
||||
@@ -225,7 +235,8 @@ defineExpose({
|
||||
accessRestrictionAcl.value = { users: [], groups: [] };
|
||||
domainProvider.value = '';
|
||||
upstreamUri.value = '';
|
||||
needsOverwriteDns.value = '';
|
||||
overwriteDns.value = false;
|
||||
needsOverwriteDns.value = [];
|
||||
|
||||
domainList.forEach(d => {
|
||||
d.label = '.' + d.domain;
|
||||
@@ -296,18 +307,15 @@ defineExpose({
|
||||
<div class="description" v-html="description"></div>
|
||||
</div>
|
||||
<div v-else-if="step === STEP.INSTALL">
|
||||
<div class="error-label" v-if="formError.generic">{{ formError.generic }}</div>
|
||||
<div class="error-label" v-if="formError.dnsExists">{{ formError.dnsExists }}</div>
|
||||
|
||||
<form :ref="(el) => { form = el; }" @submit.prevent="onSubmit(false)" autocomplete="off" @input="checkValidity()">
|
||||
<form :ref="(el) => { form = el; }" @submit.prevent="onSubmit()" autocomplete="off" @input="checkValidity()">
|
||||
<fieldset :disabled="busy">
|
||||
<input style="display: none;" type="submit" :disabled="busy" />
|
||||
|
||||
<FormGroup :class="{ 'has-error': formError.location }">
|
||||
<label for="location">{{ $t('appstore.installDialog.location') }}</label>
|
||||
<InputGroup>
|
||||
<TextInput id="location" ref="locationInput" v-model="location" style="flex-grow: 1"/>
|
||||
<SingleSelect v-model="domain" :options="domains" option-label="label" option-key="domain" @select="onDomainChange()" :search-threshold="10" required/>
|
||||
<TextInput id="location" ref="locationInput" v-model="location" @input="resetDnsOverwrite()" style="flex-grow: 1"/>
|
||||
<SingleSelect v-model="domain" :options="domains" option-label="label" option-key="domain" @select="onDomainChange(); resetDnsOverwrite()" :search-threshold="10" required/>
|
||||
</InputGroup>
|
||||
<div class="warning-label" v-show="domainProvider === 'noop' || domainProvider === 'manual'" v-html="$t('appstore.installDialog.manualWarning', { location: ((location ? location + '.' : '') + domain) })"></div>
|
||||
<div class="error-label" v-if="formError.location">{{ formError.location }}</div>
|
||||
@@ -317,8 +325,8 @@ defineExpose({
|
||||
<label :for="'secondaryDomainInput' + key">{{ port.title }}</label>
|
||||
<small>{{ port.description }}</small>
|
||||
<InputGroup>
|
||||
<TextInput :id="'secondaryDomainInput' + key" v-model="port.value" :placeholder="$t('appstore.installDialog.locationPlaceholder')" style="flex-grow: 1"/>
|
||||
<SingleSelect v-model="port.domain" :options="domains" option-label="label" option-key="domain" required/>
|
||||
<TextInput :id="'secondaryDomainInput' + key" v-model="port.value" @input="resetDnsOverwrite()" :placeholder="$t('appstore.installDialog.locationPlaceholder')" style="flex-grow: 1"/>
|
||||
<SingleSelect v-model="port.domain" :options="domains" option-label="label" option-key="domain" @select="resetDnsOverwrite()" required/>
|
||||
</InputGroup>
|
||||
</FormGroup>
|
||||
|
||||
@@ -330,9 +338,13 @@ defineExpose({
|
||||
<PortBindings v-model:tcp="tcpPorts" v-model:udp="udpPorts" :error="formError" :domain-provider="domainProvider"/>
|
||||
<AccessControl v-model:option="accessRestrictionOption" v-model:acl="accessRestrictionAcl" :manifest="manifest" :users="users" :groups="groups" :installation="true"/>
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="text-danger" v-if="formError.generic">{{ formError.generic }}</div>
|
||||
<Checkbox v-if="needsOverwriteDns.length" v-model="overwriteDns" style="margin-top: 10px" :label="$t('app.location.overwriteDns', { domains: needsOverwriteDns.join(', ') })"/>
|
||||
|
||||
<div class="bottom-button-bar">
|
||||
<Button v-if="needsOverwriteDns" danger @click="onSubmit(true)" icon="fa-solid fa-circle-down" :disabled="busy || !isFormValid" :loading="busy">Install {{ manifest.title }} and overwrite DNS</Button>
|
||||
<Button v-else @click="onSubmit(false)" icon="fa-solid fa-circle-down" :disabled="busy || !isFormValid" :loading="busy">Install {{ manifest.title }}</Button>
|
||||
<Button @click="onSubmit()" icon="fa-solid fa-circle-down" :disabled="busy || !isFormValid || (needsOverwriteDns.length > 0 && !overwriteDns)" :loading="busy">Install {{ manifest.title }}</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -172,12 +172,6 @@ onMounted(async () => {
|
||||
<SingleSelect id="blockDevicePath" v-if="provider === 'xfs'" v-model="providerConfig.mountOptionDiskPath" :options="xfsBlockDevices" option-label="label" option-key="path"/>
|
||||
</FormGroup>
|
||||
|
||||
<!-- Disk -->
|
||||
<FormGroup v-if="provider === 'disk'">
|
||||
<label class="control-label">{{ $t('backups.configureBackupStorage.diskPath') }}</label>
|
||||
<TextInput id="mountOptionDiskPathInput" v-model="providerConfig.mountOptionDiskPath" placeholder="/dev/disk/by-uuid/uuid" required />
|
||||
</FormGroup>
|
||||
|
||||
<!-- SSHFS -->
|
||||
<FormGroup v-if="provider === 'sshfs'">
|
||||
<label for="mountOptionPortInput">{{ $t('backups.configureBackupStorage.port') }}</label>
|
||||
|
||||
@@ -124,7 +124,7 @@ async function onSubmit() {
|
||||
data.mountOptions.privateKey = providerConfig.value.mountOptionPrivateKey;
|
||||
data.preserveAttributes = true;
|
||||
}
|
||||
} else if (provider.value === 'ext4' || provider.value === 'xfs' || provider.value === 'disk') {
|
||||
} else if (provider.value === 'ext4' || provider.value === 'xfs') {
|
||||
data.mountOptions.diskPath = providerConfig.value.mountOptionDiskPath;
|
||||
data.preserveAttributes = true;
|
||||
} else if (provider.value === 'mountpoint') {
|
||||
|
||||
@@ -10,6 +10,7 @@ const emit = defineEmits([ 'success' ]);
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const form = useTemplateRef('form');
|
||||
const urlInput = useTemplateRef('urlInput');
|
||||
|
||||
const formError = ref({});
|
||||
const versionsUrl = ref('');
|
||||
@@ -56,6 +57,7 @@ defineExpose({
|
||||
unstable.value = false;
|
||||
dialog.value.open();
|
||||
setTimeout(validateForm, 100); // update state of the confirm button
|
||||
setTimeout(() => urlInput.value.focus(), 500);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -81,7 +83,7 @@ defineExpose({
|
||||
|
||||
<FormGroup>
|
||||
<label for="urlInput">CloudronVersions.json URL</label>
|
||||
<TextInput id="urlInput" v-model="versionsUrl" required placeholder="https://example.com/CloudronVersions.json"/>
|
||||
<TextInput id="urlInput" ref="urlInput" v-model="versionsUrl" required placeholder="https://example.com/CloudronVersions.json"/>
|
||||
<div class="error-label" v-if="formError.generic">{{ formError.generic }}</div>
|
||||
</FormGroup>
|
||||
</fieldset>
|
||||
|
||||
@@ -319,7 +319,7 @@ function onGcdnsFileInputChange(event) {
|
||||
<Checkbox v-if="showAdvanced" v-model="customNameservers" :label="$t('domains.domainDialog.customNameservers')" />
|
||||
|
||||
<FormGroup v-if="showAdvanced">
|
||||
<label>Certificate provider <sup><a href="https://docs.cloudron.io/certificates/#certificate-providers" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<label>Certificate provider <sup><a href="https://docs.cloudron.io/domains#certificates" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
<SingleSelect v-model="tlsProvider" :options="tlsProviders" option-key="value" option-label="name" required/>
|
||||
</FormGroup>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, reactive, computed, onMounted, watch, nextTick, useTemplateRef } from 'vue';
|
||||
import { ref, computed, onMounted, watch, nextTick, useTemplateRef } from 'vue';
|
||||
import { Button, TextInput, MultiSelect, Popover, FormGroup, DateTimeInput } from '@cloudron/pankow';
|
||||
import { useDebouncedRef, prettyLongDate } from '@cloudron/pankow/utils';
|
||||
import AppsModel from '../models/AppsModel.js';
|
||||
@@ -21,7 +21,7 @@ const refreshBusy = ref(false);
|
||||
const page = ref(1);
|
||||
const perPage = ref(100);
|
||||
const eventlogContainer = useTemplateRef('eventlogContainer');
|
||||
const actions = reactive([]);
|
||||
const actions = ref([]);
|
||||
|
||||
const highlight = useDebouncedRef('', 300);
|
||||
const currentMatchPosition = ref(-1);
|
||||
@@ -107,7 +107,7 @@ async function goToNextMatch() {
|
||||
|
||||
function buildFilter() {
|
||||
const filter = {};
|
||||
if (actions.length) filter.actions = actions.join(',');
|
||||
if (actions.value.length) filter.actions = actions.value.join(',');
|
||||
if (filterFrom.value) filter.from = new Date(filterFrom.value + 'T00:00:00').toISOString();
|
||||
if (filterTo.value) filter.to = new Date(filterTo.value + 'T23:59:59.999').toISOString();
|
||||
return filter;
|
||||
@@ -142,7 +142,7 @@ function onOpenDateFilter(event) {
|
||||
dateFilterPopover.value.open(event, dateFilterButton.value.$el || dateFilterButton.value);
|
||||
}
|
||||
|
||||
watch(actions, onRefresh);
|
||||
watch(actions.value, onRefresh);
|
||||
watch(filterFrom, onRefresh);
|
||||
watch(filterTo, onRefresh);
|
||||
watch(highlight, async () => {
|
||||
@@ -202,8 +202,8 @@ defineExpose({ refresh: onRefresh, setHighlight });
|
||||
<table class="eventlog-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 160px;">{{ $t('eventlog.time') }}</th>
|
||||
<th style="width: 15%;">{{ $t('eventlog.source') }}</th>
|
||||
<th style="width: 190px;">{{ $t('eventlog.time') }}</th>
|
||||
<th style="width: 100px;">{{ $t('eventlog.source') }}</th>
|
||||
<th>{{ $t('eventlog.details') }}</th>
|
||||
<th style="width: 40px;"></th>
|
||||
</tr>
|
||||
@@ -211,9 +211,9 @@ defineExpose({ refresh: onRefresh, setHighlight });
|
||||
<tbody>
|
||||
<template v-for="(eventlog, index) in eventlogs" :key="eventlog.id">
|
||||
<tr :data-index="index" :class="{ 'active': eventlog.isOpen, 'eventlog-match': highlight && isMatch(eventlog, highlight), 'eventlog-match-current': matchIndices[currentMatchPosition] === index }" @click="eventlog.isOpen = !eventlog.isOpen">
|
||||
<td style="white-space: nowrap;">{{ prettyLongDate(eventlog.raw.creationTime) }}</td>
|
||||
<td>{{ prettyLongDate(eventlog.raw.creationTime) }}</td>
|
||||
<td class="eventlog-source">{{ eventlog.source }}</td>
|
||||
<td style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" v-html="eventlog.details"></td>
|
||||
<td v-html="eventlog.details"></td>
|
||||
<td><Button v-if="eventlog.raw.data.taskId" @click.stop plain small tool :href="app ? `/logs.html?appId=${app.id}&taskId=${eventlog.raw.data.taskId}` : `/logs.html?taskId=${eventlog.raw.data.taskId}`" target="_blank">Logs</Button></td>
|
||||
</tr>
|
||||
<tr v-show="eventlog.isOpen">
|
||||
@@ -243,6 +243,9 @@ defineExpose({ refresh: onRefresh, setHighlight });
|
||||
.eventlog-table th,
|
||||
.eventlog-table td {
|
||||
padding: 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.eventlog-table tbody tr.active,
|
||||
|
||||
@@ -10,9 +10,10 @@ import { Menu, Popover, Icon, InputDialog, Spinner } from '@cloudron/pankow';
|
||||
import ServicesModel from '../models/ServicesModel.js';
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
|
||||
defineProps(['config', 'subscription', 'notificationCount']);
|
||||
defineProps(['config', 'notificationCount']);
|
||||
|
||||
const profile = inject('profile');
|
||||
const subscription = inject('subscription');
|
||||
|
||||
const helpButton = useTemplateRef('helpButton');
|
||||
const helpPopover = useTemplateRef('helpPopover');
|
||||
@@ -115,8 +116,9 @@ onUnmounted(() => {
|
||||
<Icon :icon="'fa fa-exclamation-triangle'"/> {{ $t('main.platform.startupFailed') }}
|
||||
</div>
|
||||
|
||||
<!-- Warnings if subscription is expired or unpaid -->
|
||||
<div v-if="profile.isAtLeastOwner && subscription.plan.id === 'expired'" class="headerbar-action subscription-expired" style="gap: 6px" @click="onSubscriptionRequired()">Subscription Expired</div>
|
||||
<!-- Warnings if subscription is expired, unpaid or canceled -->
|
||||
<a v-if="profile.isAtLeastOwner && subscription.plan.id === 'expired'" class="headerbar-action subscription-expired" href="/#/cloudron-account">Subscription Expired</a>
|
||||
<a v-else-if="profile.isAtLeastOwner && (subscription.cancel_at || subscription.status === 'canceled')" class="headerbar-action subscription-canceled" href="/#/cloudron-account">Subscription Canceled</a>
|
||||
|
||||
<a class="headerbar-action" v-if="profile.isAtLeastAdmin" href="/#/notifications"><Icon :icon="notificationCount > 0 ? 'fas fa-bell' : 'far fa-bell'"/> {{ notificationCount > 99 ? '99+' : notificationCount }}</a>
|
||||
<div class="headerbar-action pankow-no-mobile" v-if="profile.isAtLeastAdmin" ref="helpButton" @click="onOpenHelp(helpPopover, $event, helpButton)"><Icon icon="fa fa-question"/></div>
|
||||
@@ -168,13 +170,16 @@ onUnmounted(() => {
|
||||
border-bottom: 1px solid var(--pankow-input-border-color);
|
||||
}
|
||||
|
||||
.subscription-expired {
|
||||
.subscription-expired,
|
||||
.subscription-canceled {
|
||||
background-color: var(--pankow-color-danger);
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.subscription-expired:hover {
|
||||
.subscription-expired:hover,
|
||||
.subscription-canceled:hover {
|
||||
color: white;
|
||||
background-color: var(--pankow-color-danger-hover);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ const appAutoUpdateFailed = ref(false);
|
||||
const certificateRenewalFailed = ref(false);
|
||||
const diskSpace = ref(false);
|
||||
const cloudronUpdateFailed = ref(false);
|
||||
const manualUpdateRequired = ref(false);
|
||||
const reboot = ref(false);
|
||||
|
||||
async function onSubmit() {
|
||||
@@ -31,6 +32,7 @@ async function onSubmit() {
|
||||
if (certificateRenewalFailed.value) config.push('certificateRenewalFailed');
|
||||
if (diskSpace.value) config.push('diskSpace');
|
||||
if (cloudronUpdateFailed.value) config.push('cloudronUpdateFailed');
|
||||
if (manualUpdateRequired.value) config.push('manualUpdateRequired');
|
||||
if (reboot.value) config.push('reboot');
|
||||
|
||||
const [error] = await profileModel.setNotificationConfig(config);
|
||||
@@ -55,6 +57,7 @@ async function open() {
|
||||
certificateRenewalFailed.value = config.indexOf('certificateRenewalFailed') !== -1;
|
||||
diskSpace.value = config.indexOf('diskSpace') !== -1;
|
||||
cloudronUpdateFailed.value = config.indexOf('cloudronUpdateFailed') !== -1;
|
||||
manualUpdateRequired.value = config.indexOf('manualUpdateRequired') !== -1;
|
||||
reboot.value = config.indexOf('reboot') !== -1;
|
||||
|
||||
dialogItem.value.open();
|
||||
@@ -121,6 +124,11 @@ defineExpose({
|
||||
<Switch v-model="cloudronUpdateFailed" :disabled="busy"/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem>
|
||||
<div>{{ $t('notifications.settings.manualUpdateRequired') }}</div>
|
||||
<Switch v-model="manualUpdateRequired" :disabled="busy"/>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem>
|
||||
<div>{{ $t('notifications.settings.rebootRequired') }}</div>
|
||||
<Switch v-model="reboot" :disabled="busy"/>
|
||||
|
||||
@@ -105,6 +105,7 @@ onUnmounted(() => {
|
||||
margin-bottom: 15px;
|
||||
padding: 10px 15px;
|
||||
padding-bottom: 25px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header-title-badge {
|
||||
|
||||
@@ -47,7 +47,7 @@ function validateForm() {
|
||||
}
|
||||
|
||||
async function onSubmit() {
|
||||
if (!form.value.reportValidity()) return;
|
||||
if (!form.value.reportValidity() || !isFormValid.value) return;
|
||||
|
||||
busy.value = true;
|
||||
formError.value = {};
|
||||
|
||||
@@ -204,6 +204,7 @@ function onEdit(backup) {
|
||||
editLabel.value = backup.label || '';
|
||||
editError.value = '';
|
||||
editDialog.value.open();
|
||||
setTimeout(() => document.getElementById('labelInput').focus(), 500);
|
||||
}
|
||||
|
||||
async function onEditSubmit() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const props = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { Button, Radiobutton, InputGroup, FormGroup, TextInput, SingleSelect } from '@cloudron/pankow';
|
||||
@@ -55,6 +55,7 @@ async function onSendmailSubmit() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await props.refreshApp();
|
||||
sendmailBusy.value = false;
|
||||
}
|
||||
|
||||
@@ -78,6 +79,7 @@ async function onRecvmailSubmit() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await props.refreshApp();
|
||||
recvmailBusy.value = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import PortBindings from '../PortBindings.vue';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
import DomainsModel from '../../models/DomainsModel.js';
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const props = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const domainsModel = DomainsModel.create();
|
||||
@@ -18,7 +18,7 @@ const busy = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const errorObject = ref({});
|
||||
const overwriteDns = ref(false);
|
||||
const needsOverwriteDns = ref(false);
|
||||
const needsOverwriteDns = ref([]);
|
||||
const domain = ref('');
|
||||
const subdomain = ref('');
|
||||
const secondaryDomains = ref({});
|
||||
@@ -56,6 +56,12 @@ function onAddRedirect() {
|
||||
|
||||
const form = useTemplateRef('form');
|
||||
const isFormValid = ref(false);
|
||||
function resetDnsOverwrite() {
|
||||
needsOverwriteDns.value = [];
|
||||
overwriteDns.value = false;
|
||||
errorMessage.value = '';
|
||||
}
|
||||
|
||||
function checkValidity() {
|
||||
isFormValid.value = form.value ? form.value.checkValidity() : false;
|
||||
|
||||
@@ -87,7 +93,7 @@ async function onSubmit() {
|
||||
busy.value = true;
|
||||
errorMessage.value = '';
|
||||
errorObject.value = {};
|
||||
needsOverwriteDns.value = false;
|
||||
needsOverwriteDns.value = [];
|
||||
|
||||
const checkForDomains = [{
|
||||
domain: domain.value,
|
||||
@@ -98,6 +104,7 @@ async function onSubmit() {
|
||||
for (const d of aliases.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
|
||||
for (const d of redirects.value) checkForDomains.push({ domain: d.domain, subdomain: d.subdomain });
|
||||
|
||||
const conflicting = [];
|
||||
for (const d of checkForDomains) {
|
||||
const [error, result] = await domainsModel.checkRecords(d.domain, d.subdomain);
|
||||
if (error) {
|
||||
@@ -106,16 +113,19 @@ async function onSubmit() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
if (result.needsOverwrite && !overwriteDns.value) {
|
||||
busy.value = false;
|
||||
needsOverwriteDns.value = true;
|
||||
return;
|
||||
}
|
||||
if (result.needsOverwrite) conflicting.push((d.subdomain ? d.subdomain + '.' : '') + d.domain);
|
||||
}
|
||||
|
||||
if (conflicting.length > 0 && !overwriteDns.value) {
|
||||
busy.value = false;
|
||||
needsOverwriteDns.value = conflicting;
|
||||
errorMessage.value = `DNS records of ${conflicting.join(', ')} already exist`;
|
||||
return;
|
||||
}
|
||||
|
||||
// only use enabled ports
|
||||
const ports = {};
|
||||
const portsCombined = Object.assign(tcpPorts.value || {}, udpPorts.value || {});
|
||||
const portsCombined = Object.assign({}, tcpPorts.value || {}, udpPorts.value || {});
|
||||
for (const env in portsCombined) {
|
||||
if (portsCombined[env].enabled) {
|
||||
ports[env] = portsCombined[env].value;
|
||||
@@ -139,6 +149,7 @@ async function onSubmit() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await props.refreshApp();
|
||||
busy.value = false;
|
||||
}
|
||||
|
||||
@@ -206,8 +217,8 @@ onMounted(async () => {
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="label" :search-threshold="10" required/>
|
||||
<TextInput style="flex-grow: 1" v-model="subdomain" @input="resetDnsOverwrite()" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="domain" option-key="domain" option-label="label" @select="resetDnsOverwrite()" :search-threshold="10" required/>
|
||||
</InputGroup>
|
||||
<div class="warning-label" v-if="isNoopOrManual(domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((subdomain ? subdomain + '.' : '') + domain) })"></div>
|
||||
<!-- Button just to offset the same margin on the right to align location input when alias or redirects are visible -->
|
||||
@@ -219,8 +230,8 @@ onMounted(async () => {
|
||||
<label :for="'secondaryDomainInput' + item.containerPort">{{ item.title }}</label>
|
||||
<small>{{ item.description }}</small>
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + item.containerPort" v-model="item.subdomain" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10" required/>
|
||||
<TextInput style="flex-grow: 1" :id="'secondaryDomainInput' + item.containerPort" v-model="item.subdomain" @input="resetDnsOverwrite()" :placeholder="$t('appstore.installDialog.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" @select="resetDnsOverwrite()" :search-threshold="10" required/>
|
||||
</InputGroup>
|
||||
<div class="warning-label" v-if="isNoopOrManual(item.domain)" v-html="$t('appstore.installDialog.manualWarning', { location: ((item.subdomain ? item.subdomain + '.' : '') + item.domain) })"></div>
|
||||
</FormGroup>
|
||||
@@ -233,8 +244,8 @@ onMounted(async () => {
|
||||
<div v-for="(item, index) in aliases" :key="item" style="margin-bottom: 10px">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10"/>
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" @input="resetDnsOverwrite()" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" @select="resetDnsOverwrite()" :search-threshold="10"/>
|
||||
</InputGroup>
|
||||
<Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveAlias(index)"/>
|
||||
</div>
|
||||
@@ -252,8 +263,8 @@ onMounted(async () => {
|
||||
<div v-for="(item, index) in redirects" :key="item" style="margin-bottom: 10px;">
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<InputGroup style="flex-grow: 1">
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" :search-threshold="10"/>
|
||||
<TextInput style="flex-grow: 1" v-model="item.subdomain" @input="resetDnsOverwrite()" :placeholder="$t('app.location.locationPlaceholder')"/>
|
||||
<SingleSelect :disabled="busy" :options="domains" v-model="item.domain" option-key="domain" option-label="label" @select="resetDnsOverwrite()" :search-threshold="10"/>
|
||||
</InputGroup>
|
||||
<Button danger tool :disabled="busy" icon="fa-solid fa-trash" @click="onRemoveRedirect(index)"/>
|
||||
</div>
|
||||
@@ -271,13 +282,11 @@ onMounted(async () => {
|
||||
|
||||
<br/>
|
||||
|
||||
<div class="error-label" v-if="errorMessage">{{ errorMessage }}</div>
|
||||
<br v-if="errorMessage"/>
|
||||
<div class="text-danger" v-if="errorMessage">{{ errorMessage }}</div>
|
||||
<Checkbox v-if="needsOverwriteDns.length" v-model="overwriteDns" :label="$t('app.location.overwriteDns', { domains: needsOverwriteDns.join(', ') })"/>
|
||||
<br v-if="needsOverwriteDns.length"/>
|
||||
|
||||
<Checkbox v-if="needsOverwriteDns" v-model="overwriteDns" :label="$t('app.location.dnsoverwrite')"/>
|
||||
<br v-if="needsOverwriteDns"/>
|
||||
|
||||
<Button @click="onSubmit()" :loading="busy" :disabled="busy || (app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !isFormValid">{{ $t('app.location.saveAction') }}</Button>
|
||||
<Button @click="onSubmit()" :loading="busy" :disabled="busy || (app.error && app.error.installationState !== ISTATES.PENDING_LOCATION_CHANGE) || app.taskId || !isFormValid || (needsOverwriteDns.length > 0 && !overwriteDns)">{{ $t('app.location.saveAction') }}</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { taskNameFromInstallationState } from '../../utils.js';
|
||||
import { ISTATES } from '../../constants.js';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const props = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const busyRepair = ref(false);
|
||||
@@ -28,8 +28,8 @@ async function onToggleDebugMode() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// let the task start
|
||||
setTimeout(() => { debugModeBusy.value = false; }, 4000);
|
||||
await props.refreshApp();
|
||||
debugModeBusy.value = false;
|
||||
}
|
||||
|
||||
async function onRepair() {
|
||||
@@ -42,7 +42,8 @@ async function onRepair() {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => { busyRepair.value = false; }, 4000);
|
||||
await props.refreshApp();
|
||||
busyRepair.value = false;
|
||||
}
|
||||
|
||||
async function onRestart() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import SystemModel from '../../models/SystemModel.js';
|
||||
const appsModel = AppsModel.create();
|
||||
const systemModel = SystemModel.create();
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const props = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
const memoryLimitBusy = ref(false);
|
||||
const memoryLimit = ref(0);
|
||||
@@ -33,8 +33,8 @@ async function onSubmitMemoryLimit() {
|
||||
const [error] = await appsModel.configure(props.app.id, 'memory_limit', { memoryLimit: limit });
|
||||
if (error) return console.error(error);
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => memoryLimitBusy.value = false, 4000);
|
||||
await props.refreshApp();
|
||||
memoryLimitBusy.value = false;
|
||||
}
|
||||
|
||||
async function onSubmitCpuQuota() {
|
||||
@@ -44,9 +44,8 @@ async function onSubmitCpuQuota() {
|
||||
if (error) return console.error(error);
|
||||
|
||||
currentCpuQuota.value = parseInt(cpuQuota.value);
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => cpuQuotaBusy.value = false, 4000);
|
||||
await props.refreshApp();
|
||||
cpuQuotaBusy.value = false;
|
||||
}
|
||||
|
||||
async function onSubmitDevices() {
|
||||
@@ -70,11 +69,9 @@ async function onSubmitDevices() {
|
||||
return;
|
||||
}
|
||||
|
||||
// give polling some time
|
||||
setTimeout(() => {
|
||||
devicesBusy.value = false;
|
||||
currentDevices.value = Object.keys(devs);
|
||||
}, 4000);
|
||||
currentDevices.value = Object.keys(devs);
|
||||
await props.refreshApp();
|
||||
devicesBusy.value = false;
|
||||
}
|
||||
|
||||
const devicesChanged = computed(() => {
|
||||
@@ -142,7 +139,7 @@ onMounted(async () => {
|
||||
<hr style="margin-top: 20px"/>
|
||||
|
||||
<form @submit.prevent="onSubmitDevices()" autocomplete="off">
|
||||
<fieldset :disabled="devicesBusy || (!app.error && !devicesChanged) || (app.error && app.error.installationState !== ISTATES.PENDING_RECREATE_CONTAINER) || app.taskId">
|
||||
<fieldset :disabled="devicesBusy || (app.error && app.error.installationState !== ISTATES.PENDING_RECREATE_CONTAINER) || app.taskId">
|
||||
<input style="display: none;" type="submit"/>
|
||||
<FormGroup>
|
||||
<label for="devicesInput">{{ $t('app.resources.devices.label') }} <sup><a href="https://docs.cloudron.io/apps/#devices" class="help" target="_blank"><i class="fa fa-question-circle"></i></a></sup></label>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { ISTATES } from '../../constants.js';
|
||||
import SettingsItem from '../SettingsItem.vue';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
|
||||
const { app } = defineProps([ 'app' ]);
|
||||
const { app, refreshApp } = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
|
||||
@@ -24,6 +24,7 @@ async function onTurnChange(value) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await refreshApp();
|
||||
turnBusy.value = false;
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ async function onRedisChange(value) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await refreshApp();
|
||||
redisBusy.value = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ISTATES } from '../../constants.js';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
import VolumesModel from '../../models/VolumesModel.js';
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
const props = defineProps([ 'app', 'refreshApp' ]);
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const volumesModel = VolumesModel.create();
|
||||
@@ -56,9 +56,8 @@ async function onSubmitMove() {
|
||||
}
|
||||
|
||||
originalVolumeId.value = volumeId.value;
|
||||
|
||||
// give app refresh some time, ideally we wait for the task
|
||||
setTimeout(() => moveBusy.value = false, 4000);
|
||||
await props.refreshApp();
|
||||
moveBusy.value = false;
|
||||
}
|
||||
|
||||
function onMountAdd() {
|
||||
@@ -90,10 +89,9 @@ async function onSubmitMounts() {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
// make a copy, cannot clone due to Proxy objects
|
||||
originalMounts.value = mounts.value.map(m => { return { volumeId: m.volumeId, readOnly: m.readOnly }; });
|
||||
|
||||
setTimeout(() => mountsBusy.value = false, 2000);
|
||||
await props.refreshApp();
|
||||
mountsBusy.value = false;
|
||||
}
|
||||
|
||||
const mountsValid = computed(() => {
|
||||
|
||||
@@ -26,11 +26,14 @@ async function onUninstall() {
|
||||
confirmLabel: t('app.uninstallDialog.uninstallAction'),
|
||||
rejectLabel: t('main.dialog.cancel'),
|
||||
rejectStyle: 'secondary',
|
||||
autoCloseOnConfirm: false,
|
||||
});
|
||||
|
||||
if (!yes) return;
|
||||
|
||||
const [error] = await appsModel.uninstall(props.app.id);
|
||||
inputDialog.value.close();
|
||||
|
||||
if (error) return console.error(error);
|
||||
|
||||
window.location.href = '/#/apps';
|
||||
@@ -44,12 +47,15 @@ async function onArchive() {
|
||||
message: t('app.archiveDialog.description', { app: (props.app.label || props.app.fqdn), date: prettyLongDate(latestBackup.value.creationTime) }),
|
||||
confirmStyle: 'danger',
|
||||
confirmLabel: t('app.archive.action'),
|
||||
rejectLabel: t('main.dialog.cancel')
|
||||
rejectLabel: t('main.dialog.cancel'),
|
||||
autoCloseOnConfirm: false,
|
||||
});
|
||||
|
||||
if (!yes) return;
|
||||
|
||||
const [error] = await appsModel.archive(props.app.id, latestBackup.value.id);
|
||||
inputDialog.value.close();
|
||||
|
||||
if (error) return console.error(error);
|
||||
|
||||
window.location.href = '/#/apps';
|
||||
|
||||
@@ -293,7 +293,6 @@ const STORAGE_PROVIDERS = [
|
||||
{ name: 'Cloudflare R2', value: 'cloudflare-r2' },
|
||||
{ name: 'Contabo Object Storage', value: 'contabo-objectstorage', regions: REGIONS_CONTABO },
|
||||
{ name: 'DigitalOcean Spaces', value: 'digitalocean-spaces', regions: REGIONS_DIGITALOCEAN },
|
||||
{ name: 'External/Local Disk (EXT4 or XFS)', value: 'disk' },
|
||||
{ name: 'EXT4 Disk', value: 'ext4' },
|
||||
{ name: 'Exoscale SOS', value: 'exoscale-sos', regions: REGIONS_EXOSCALE },
|
||||
{ name: 'Filesystem', value: 'filesystem' },
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import '@fontsource/inter';
|
||||
|
||||
import i18n from './i18n.js';
|
||||
import OidcDeviceConfirmView from './views/OidcDeviceConfirmView.vue';
|
||||
|
||||
import './style.css';
|
||||
|
||||
(async function init() {
|
||||
const app = createApp(OidcDeviceConfirmView);
|
||||
|
||||
app.use(await i18n());
|
||||
|
||||
app.mount('#app');
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import '@fontsource/inter';
|
||||
|
||||
import i18n from './i18n.js';
|
||||
import OidcDeviceInputView from './views/OidcDeviceInputView.vue';
|
||||
|
||||
import './style.css';
|
||||
|
||||
(async function init() {
|
||||
const app = createApp(OidcDeviceInputView);
|
||||
|
||||
app.use(await i18n());
|
||||
|
||||
app.mount('#app');
|
||||
})();
|
||||
@@ -0,0 +1,16 @@
|
||||
import { createApp } from 'vue';
|
||||
|
||||
import '@fontsource/inter';
|
||||
|
||||
import i18n from './i18n.js';
|
||||
import OidcDeviceSuccessView from './views/OidcDeviceSuccessView.vue';
|
||||
|
||||
import './style.css';
|
||||
|
||||
(async function init() {
|
||||
const app = createApp(OidcDeviceSuccessView);
|
||||
|
||||
app.use(await i18n());
|
||||
|
||||
app.mount('#app');
|
||||
})();
|
||||
@@ -43,7 +43,7 @@ function download(filename, text) {
|
||||
}
|
||||
|
||||
function mountlike(provider) {
|
||||
return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs' || provider === 'disk';
|
||||
return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs';
|
||||
}
|
||||
|
||||
function s3like(provider) {
|
||||
|
||||
@@ -48,6 +48,8 @@ const sftpInfoDialog = useTemplateRef('sftpInfoDialog');
|
||||
|
||||
let refreshTimer = null;
|
||||
async function refresh() {
|
||||
clearTimeout(refreshTimer);
|
||||
|
||||
const [error, result] = await appsModel.get(id.value);
|
||||
if (error) {
|
||||
if (error.status === 403) return window.location.hash = '/';
|
||||
@@ -340,19 +342,19 @@ onBeforeUnmount(() => {
|
||||
<Transition name="slide-fade" mode="out-in">
|
||||
<Info v-if="currentView === 'info'" :app="app"/>
|
||||
<Display v-else-if="currentView === 'display'" :app="app"/>
|
||||
<Location v-else-if="currentView === 'location'" :app="app"/>
|
||||
<Location v-else-if="currentView === 'location'" :app="app" :refresh-app="refresh"/>
|
||||
<Proxy v-else-if="currentView === 'proxy'" :app="app"/>
|
||||
<Access v-else-if="currentView === 'access'" :app="app"/>
|
||||
<Resources v-else-if="currentView === 'resources'" :app="app"/>
|
||||
<Services v-else-if="currentView === 'services'" :app="app"/>
|
||||
<Storage v-else-if="currentView === 'storage'" :app="app"/>
|
||||
<Resources v-else-if="currentView === 'resources'" :app="app" :refresh-app="refresh"/>
|
||||
<Services v-else-if="currentView === 'services'" :app="app" :refresh-app="refresh"/>
|
||||
<Storage v-else-if="currentView === 'storage'" :app="app" :refresh-app="refresh"/>
|
||||
<Graphs v-else-if="currentView === 'graphs'" :app="app"/>
|
||||
<Security v-else-if="currentView === 'security'" :app="app"/>
|
||||
<Email v-else-if="currentView === 'email'" :app="app"/>
|
||||
<Email v-else-if="currentView === 'email'" :app="app" :refresh-app="refresh"/>
|
||||
<Cron v-else-if="currentView === 'cron'" :app="app"/>
|
||||
<Updates v-else-if="currentView === 'updates'" :app="app" :refresh-app="refresh"/>
|
||||
<Backups v-else-if="currentView === 'backups'" :app="app"/>
|
||||
<Repair v-else-if="currentView === 'repair'" :app="app"/>
|
||||
<Repair v-else-if="currentView === 'repair'" :app="app" :refresh-app="refresh"/>
|
||||
<Eventlog v-else-if="currentView === 'eventlog'" :app="app"/>
|
||||
<Uninstall v-else-if="currentView === 'uninstall'" :app="app"/>
|
||||
</Transition>
|
||||
|
||||
@@ -171,7 +171,8 @@ function createAppLinkActionMenu(app) {
|
||||
const filteredApps = computed(() => {
|
||||
return apps.value.filter(a => {
|
||||
if (a.type === APP_TYPES.LINK) {
|
||||
return a.upstreamUri.includes(filter.value);
|
||||
return a.upstreamUri.includes(filter.value)
|
||||
|| (a.label ? a.label.toLowerCase().indexOf(filter.value.toLocaleLowerCase()) !== -1 : false);
|
||||
} else { // app or proxy
|
||||
return a.fqdn.includes(filter.value)
|
||||
|| a.secondaryDomains.some(sd => sd.fqdn.includes(filter.value))
|
||||
|
||||
@@ -163,13 +163,17 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.section-body {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.notification-list {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-direction: column;
|
||||
padding-bottom: 10px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.notification-item {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup>
|
||||
|
||||
// keep this to load pankow Button css
|
||||
import { Button } from '@cloudron/pankow';
|
||||
|
||||
import PublicPageLayout from '../components/PublicPageLayout.vue';
|
||||
|
||||
// coming from oidc_device_confirm.html server-side rendered
|
||||
const clientName = window.cloudron.clientName;
|
||||
const userCode = window.cloudron.userCode;
|
||||
const form = window.cloudron.form;
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PublicPageLayout>
|
||||
<div>
|
||||
<h2>Authorize {{ clientName }}</h2>
|
||||
<p>Verify the code below</p>
|
||||
<div class="user-code">{{ userCode }}</div>
|
||||
<p class="code-hint">If you did not initiate this action or the code does not match, please close this window or cancel.</p>
|
||||
|
||||
<!-- injected form for submission from oidcserver.js -->
|
||||
<div v-html="form"></div>
|
||||
|
||||
<button class="pankow-button" type="submit" form="op.deviceConfirmForm">Continue</button>
|
||||
<button type="submit" form="op.deviceConfirmForm" value="yes" name="abort" class="cancel-button">Cancel</button>
|
||||
</div>
|
||||
</PublicPageLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
p {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.user-code {
|
||||
font-size: 26px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.code-hint {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
margin-left: 15px;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: var(--pankow-color-dark);
|
||||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
color: var(--pankow-color-primary-hover);
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,26 @@
|
||||
<script setup>
|
||||
|
||||
import PublicPageLayout from '../components/PublicPageLayout.vue';
|
||||
|
||||
// coming from oidc_device_input.html server-side rendered
|
||||
const message = window.cloudron.message;
|
||||
const form = window.cloudron.form;
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PublicPageLayout>
|
||||
<div>
|
||||
<h2>Authorization canceled</h2>
|
||||
|
||||
<!-- in theory one can enter the code manually there, but we don't support this right now
|
||||
<div v-html="message"></div>
|
||||
<div v-html="form"></div>
|
||||
<button type="submit" form="op.deviceInputForm">Continue</button>
|
||||
-->
|
||||
</div>
|
||||
</PublicPageLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,17 @@
|
||||
<script setup>
|
||||
|
||||
import PublicPageLayout from '../components/PublicPageLayout.vue';
|
||||
|
||||
// coming from oidc_device_success.html server-side rendered (optional)
|
||||
const cloudronName = window.cloudron.name;
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PublicPageLayout :cloudron-name="cloudronName">
|
||||
<div style="max-width: 300px;">
|
||||
<h2>Success</h2>
|
||||
<p>Your device has been authorized. You can close this window.</p>
|
||||
</div>
|
||||
</PublicPageLayout>
|
||||
</template>
|
||||
@@ -35,7 +35,7 @@ async function onPasswordReset() {
|
||||
if (res.status === 409) {
|
||||
error.value.generic = res.body.message;
|
||||
} else if (res.status === 202) {
|
||||
mode.value = MODE.NEW_PASSWORD_DONE;
|
||||
mode.value = MODE.RESET_PASSWORD_DONE;
|
||||
}
|
||||
} catch (error) {
|
||||
error.value.generic = error;
|
||||
@@ -46,6 +46,8 @@ async function onPasswordReset() {
|
||||
}
|
||||
|
||||
async function onNewPassword() {
|
||||
if (newPassword.value !== newPasswordRepeat.value) return;
|
||||
|
||||
busy.value = true;
|
||||
error.value = {};
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ async function onSubmit() {
|
||||
config.mountOptions.privateKey = providerConfig.value.mountOptionPrivateKey;
|
||||
config.preserveAttributes = true;
|
||||
}
|
||||
} else if (provider.value === 'ext4' || provider.value === 'xfs' || provider.value === 'disk') {
|
||||
} else if (provider.value === 'ext4' || provider.value === 'xfs') {
|
||||
config.mountOptions.diskPath = providerConfig.value.mountOptionDiskPath;
|
||||
config.preserveAttributes = true;
|
||||
} else if (provider.value === 'mountpoint') {
|
||||
|
||||
@@ -327,7 +327,7 @@ onMounted(async () =>{
|
||||
<TableView :columns="columns" :model="volumes" :busy="busy" :fixed-layout="true" :placeholder="$t('volumes.emptyPlaceholder')">
|
||||
<template #target="{ item:volume }">
|
||||
<span v-if="volume.mountType === 'mountpoint' || volume.mountType === 'filesystem'">{{ volume.hostPath }}</span>
|
||||
<span v-else-if="volume.mountType === 'ext4' || volume.mountType === 'xfs' || volume.mountType === 'disk'">{{ volume.mountOptions.diskPath }}</span>
|
||||
<span v-else-if="volume.mountType === 'ext4' || volume.mountType === 'xfs'">{{ volume.mountOptions.diskPath }}</span>
|
||||
<span v-else-if="volume.mountType === 'sshfs'">{{ volume.mountOptions.host + '/' + volume.mountOptions.remoteDir }}</span>
|
||||
<!-- cifs/nfs -->
|
||||
<span v-else>{{ volume.mountOptions.host + volume.mountOptions.remoteDir }}</span>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
var safe = require('safetydance');
|
||||
var safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
var tz = safe.fs.readFileSync('/etc/timezone', 'utf8');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
// first check precondtion of domain entry in settings
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async'),
|
||||
safe = require('safetydance'),
|
||||
safe = require('@cloudron/safetydance').default,
|
||||
tld = require('tldjs');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -5,7 +5,7 @@ var async = require('async'),
|
||||
fs = require('node:fs'),
|
||||
os = require('node:os'),
|
||||
path = require('node:path'),
|
||||
safe = require('safetydance'),
|
||||
safe = require('@cloudron/safetydance').default,
|
||||
tldjs = require('tldjs');
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.runSql('ALTER TABLE domains ADD COLUMN wellKnownJson TEXT', function (error) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const async = require('async'),
|
||||
fs = require('node:fs'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const BOX_DATA_DIR = '/home/yellowtent/boxdata';
|
||||
const PLATFORM_DATA_DIR = '/home/yellowtent/platformdata';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const async = require('async'),
|
||||
fs = require('node:fs'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const BOX_DATA_DIR = '/home/yellowtent/boxdata';
|
||||
const PLATFORM_DATA_DIR = '/home/yellowtent/platformdata';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const CERTS_DIR = '/home/yellowtent/boxdata/certs',
|
||||
PLATFORM_CERTS_DIR = '/home/yellowtent/platformdata/nginx/cert';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const async = require('async'),
|
||||
fs = require('node:fs'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const CERTS_DIR = '/home/yellowtent/boxdata/certs';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const async = require('async'),
|
||||
child_process = require('node:child_process'),
|
||||
fs = require('node:fs'),
|
||||
path = require('node:path'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const OLD_CERTS_DIR = '/home/yellowtent/boxdata/certs';
|
||||
const NEW_CERTS_DIR = '/home/yellowtent/platformdata/nginx/cert';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.all('SELECT * FROM volumes', function (error, volumes) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = function(db, callback) {
|
||||
db.all('SELECT * from domains', [], function (error, results) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const async = require('async'),
|
||||
openssl = require('../src/openssl.js').default,
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const NGINX_CERT_DIR = '/home/yellowtent/platformdata/nginx/cert';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const async = require('async'),
|
||||
fs = require('node:fs'),
|
||||
path = require('node:path'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const MAIL_DATA_DIR = '/home/yellowtent/boxdata/mail';
|
||||
const DKIM_DIR = `${MAIL_DATA_DIR}/dkim`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const safe = require('safetydance');
|
||||
const safe = require('@cloudron/safetydance').default;
|
||||
|
||||
const PROXY_AUTH_TOKEN_SECRET_FILE = '/home/yellowtent/platformdata/proxy-auth-token-secret';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const async = require('async'),
|
||||
mail = require('../src/mail.js').default,
|
||||
safe = require('safetydance'),
|
||||
safe = require('@cloudron/safetydance').default,
|
||||
util = require('node:util');
|
||||
|
||||
// it seems some mail domains do not have dkimKey in the database for some reason because of some previous bad migration
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const crypto = require('node:crypto'),
|
||||
path = require('node:path'),
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
function getMountPoint(dataDir) {
|
||||
const output = safe.child_process.execSync(`df --output=target "${dataDir}" | tail -1`, { encoding: 'utf8' });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const safe = require('safetydance');
|
||||
const safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = async function (db) {
|
||||
const mailDomains = await db.runSql('SELECT * FROM mail', []);
|
||||
|
||||
@@ -4,7 +4,7 @@ const crypto = require('node:crypto'),
|
||||
fs = require('node:fs'),
|
||||
path = require('node:path'),
|
||||
paths = require('../src/paths.js').default,
|
||||
safe = require('safetydance');
|
||||
safe = require('@cloudron/safetydance').default;
|
||||
|
||||
exports.up = async function(db) {
|
||||
const backups = await db.runSql('SELECT format, COUNT(*) AS count FROM backups GROUP BY format WITH ROLLUP', []); // https://dev.mysql.com/doc/refman/8.4/en/group-by-modifiers.html
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
exports.up = async function(db) {
|
||||
const results = await db.runSql('SELECT * FROM settings WHERE name=?', ['autoupdate_pattern']);
|
||||
if (results.length === 0) return; // use defaults from box code
|
||||
|
||||
let policy, schedule;
|
||||
|
||||
if (results.length === 0 || results[0].value === 'never') {
|
||||
if (results[0].value === 'never') {
|
||||
policy = 'never';
|
||||
schedule = '00 00 1,3,5,23 * * *';
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
exports.up = async function (db) {
|
||||
await db.runSql('ALTER TABLE apps ADD COLUMN buildConfigJson TEXT');
|
||||
};
|
||||
|
||||
exports.down = async function (db) {
|
||||
await db.runSql('ALTER TABLE apps DROP COLUMN buildConfigJson');
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
const child_process = require('node:child_process');
|
||||
|
||||
function patchMountFile(mountPath, fstype) {
|
||||
const mountFilename = child_process.execSync(`systemd-escape -p --suffix=mount "${mountPath}"`, { encoding: 'utf8' }).trim();
|
||||
const mountFile = `/etc/systemd/system/${mountFilename}`;
|
||||
|
||||
try {
|
||||
child_process.execSync(`sed -i 's/^Type=auto$/Type=${fstype}/' "${mountFile}"`);
|
||||
child_process.execSync('systemctl daemon-reload');
|
||||
console.log(`Patched ${mountFile}: Type=auto -> Type=${fstype}`);
|
||||
} catch (e) {
|
||||
console.log(`Warning: failed to patch ${mountFile}: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
exports.up = async function (db) {
|
||||
const results = await db.runSql('SELECT id, configJson FROM backupSites WHERE provider = ?', ['disk']);
|
||||
|
||||
for (const row of results) {
|
||||
const config = JSON.parse(row.configJson);
|
||||
|
||||
let fstype = 'ext4';
|
||||
try {
|
||||
const diskPath = config.mountOptions.diskPath;
|
||||
const output = child_process.execSync(`lsblk --paths --json --list --fs ${diskPath}`, { encoding: 'utf8' });
|
||||
const info = JSON.parse(output);
|
||||
if (info.blockdevices[0].fstype === 'xfs') fstype = 'xfs';
|
||||
} catch (e) {
|
||||
console.log(`Could not detect filesystem type for backup site ${row.id}, defaulting to ext4: ${e.message}`);
|
||||
}
|
||||
|
||||
config._provider = fstype;
|
||||
console.log(`Migrating backup site ${row.id} from disk to ${fstype}`);
|
||||
await db.runSql('UPDATE backupSites SET provider = ?, configJson = ? WHERE id = ?', [fstype, JSON.stringify(config), row.id]);
|
||||
|
||||
if (config._managedMountPath) {
|
||||
patchMountFile(config._managedMountPath, fstype);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = function (db, callback) {
|
||||
callback();
|
||||
};
|
||||
@@ -109,6 +109,7 @@ CREATE TABLE IF NOT EXISTS apps(
|
||||
crontab TEXT,
|
||||
upstreamUri VARCHAR(256) DEFAULT "",
|
||||
checklistJson TEXT, // checklist for admins
|
||||
buildConfigJson TEXT, // { buildArgs, dockerfileName } for source installs
|
||||
|
||||
FOREIGN KEY(mailboxDomain) REFERENCES domains(domain),
|
||||
FOREIGN KEY(inboxDomain) REFERENCES domains(domain),
|
||||
|
||||
Generated
+1
-10
@@ -14,6 +14,7 @@
|
||||
"@cloudron/connect-lastmile": "^3.0.0",
|
||||
"@cloudron/manifest-format": "^6.1.0",
|
||||
"@cloudron/pipework": "^2.1.2",
|
||||
"@cloudron/safetydance": "^3.0.1",
|
||||
"@cloudron/superagent": "^2.1.1",
|
||||
"@google-cloud/dns": "^5.3.1",
|
||||
"@google-cloud/storage": "^7.19.0",
|
||||
@@ -46,7 +47,6 @@
|
||||
"oidc-provider": "^9.6.1",
|
||||
"ovh": "^2.0.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"safetydance": "^2.5.1",
|
||||
"semver": "^7.7.4",
|
||||
"speakeasy": "^2.0.0",
|
||||
"tar-stream": "^3.1.8",
|
||||
@@ -7774,15 +7774,6 @@
|
||||
"version": "2.1.2",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safetydance": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-2.5.1.tgz",
|
||||
"integrity": "sha512-loeEErOTR8rhC2ICec1C1dRyfOtjFomS2A1JG8rl5qNQWVHJMJNKdL/kinaSq58yu1mOak0UW+pKY1cb/t4BCg==",
|
||||
"engines": [
|
||||
"node >= 4.0.0"
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.1",
|
||||
"license": "ISC"
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
"oidc-provider": "^9.6.1",
|
||||
"ovh": "^2.0.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"safetydance": "^2.5.1",
|
||||
"@cloudron/safetydance": "^3.0.1",
|
||||
"semver": "^7.7.4",
|
||||
"speakeasy": "^2.0.0",
|
||||
"tar-stream": "^3.1.8",
|
||||
|
||||
@@ -885,8 +885,17 @@ function check_services() {
|
||||
local service_port=("3000" "3000" "3000" "3000" "2003" "3000")
|
||||
|
||||
for service in "${!services[@]}"; do
|
||||
if [[ $(docker inspect ${services[$service]} --format={{.State.Status}}) != "running" ]]; then
|
||||
fail "Service '${services[$service]}' container is not running!"
|
||||
local service_name="${services[$service]}"
|
||||
local service_state
|
||||
if ! service_state="$(docker inspect "${service_name}" --format={{.State.Status}} 2>/dev/null)"; then
|
||||
service_state="missing"
|
||||
fi
|
||||
if [[ "${service_state}" != "running" ]]; then
|
||||
if [[ "${service_state}" == "exited" ]] && [[ "${service_name}" == "mysql" || "${service_name}" == "postgresql" || "${service_name}" == "mongodb" ]]; then
|
||||
warn "Service '${service_name}' is not running (may be lazy-stopped)"
|
||||
else
|
||||
fail "Service '${service_name}' container is not running (state: ${service_state})!"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import net from 'node:net';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { program } from 'commander';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import { Client as SshClient } from 'ssh2';
|
||||
import util from 'node:util';
|
||||
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import net from 'net';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { program } from 'commander';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import semver from 'semver';
|
||||
import superagent from '@cloudron/superagent';
|
||||
import Table from 'easy-table';
|
||||
|
||||
@@ -113,7 +113,7 @@ $ip6tables -t filter -A CLOUDRON -p udp --sport 547 --dport 546 -j ACCEPT
|
||||
|
||||
ipxtables -t filter -A CLOUDRON -p udp --sport 53 -j ACCEPT
|
||||
# for ldap,dockerproxy server (ipv4 only) to accept connections from apps. for connecting to addons and mail container ports, docker already has rules
|
||||
$iptables -t filter -A CLOUDRON -p tcp -s 172.18.0.0/16 -d 172.18.0.1 -m multiport --dports 3002,3003 -j ACCEPT
|
||||
$iptables -t filter -A CLOUDRON -p tcp -s 172.18.0.0/16 -d 172.18.0.1 -m multiport --dports 3002,3003,3006 -j ACCEPT
|
||||
$iptables -t filter -A CLOUDRON -p udp -s 172.18.0.0/16 --dport 53 -j ACCEPT # dns responses from docker (127.0.0.11)
|
||||
ipxtables -t filter -A CLOUDRON -i lo -j ACCEPT # required for localhost connections (mysql)
|
||||
|
||||
|
||||
+8
-8
@@ -7,8 +7,8 @@ import dns from './dns.js';
|
||||
import openssl from './openssl.js';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import promiseRetry from './promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import retry from './retry.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import superagent from '@cloudron/superagent';
|
||||
import users from './users.js';
|
||||
|
||||
@@ -244,7 +244,7 @@ Acme2.prototype.waitForOrder = async function (orderUrl) {
|
||||
|
||||
log(`waitForOrder: ${orderUrl}`);
|
||||
|
||||
return await promiseRetry({ times: 15, interval: 20000, log }, async () => {
|
||||
return await retry({ times: 15, interval: 20000, log }, async () => {
|
||||
log('waitForOrder: getting status');
|
||||
|
||||
const result = await this.postAsGet(orderUrl);
|
||||
@@ -297,7 +297,7 @@ Acme2.prototype.waitForChallenge = async function (challenge) {
|
||||
|
||||
log(`waitingForChallenge: ${JSON.stringify(challenge)}`);
|
||||
|
||||
await promiseRetry({ times: 15, interval: 20000, log }, async () => {
|
||||
await retry({ times: 15, interval: 20000, log }, async () => {
|
||||
log('waitingForChallenge: getting status');
|
||||
|
||||
const result = await this.postAsGet(challenge.url);
|
||||
@@ -335,7 +335,7 @@ Acme2.prototype.signCertificate = async function (finalizationUrl, csrPem) {
|
||||
Acme2.prototype.downloadCertificate = async function (certUrl) {
|
||||
assert.strictEqual(typeof certUrl, 'string');
|
||||
|
||||
return await promiseRetry({ times: 5, interval: 20000, log }, async () => {
|
||||
return await retry({ times: 5, interval: 20000, log }, async () => {
|
||||
log(`downloadCertificate: downloading certificate of ${this.cn}`);
|
||||
|
||||
const result = await this.postAsGet(certUrl);
|
||||
@@ -471,7 +471,7 @@ Acme2.prototype.acmeFlow = async function () {
|
||||
const challenge = await this.prepareChallenge(cn, authorization);
|
||||
await this.notifyChallengeReady(challenge);
|
||||
await this.waitForChallenge(challenge);
|
||||
await safe(this.cleanupChallenge(cn, challenge), { log });
|
||||
await safe(this.cleanupChallenge(cn, challenge), { debug: log });
|
||||
}
|
||||
|
||||
const csr = await openssl.createCsr(this.key, this.cn, this.altNames);
|
||||
@@ -485,7 +485,7 @@ Acme2.prototype.acmeFlow = async function () {
|
||||
};
|
||||
|
||||
Acme2.prototype.loadDirectory = async function () {
|
||||
await promiseRetry({ times: 3, interval: 20000, log }, async () => {
|
||||
await retry({ times: 3, interval: 20000, log }, async () => {
|
||||
const response = await superagent.get(this.caDirectory).timeout(30000).ok(() => true);
|
||||
|
||||
if (response.status !== 200) throw new BoxError(BoxError.ACME_ERROR, `Invalid response code when fetching directory : ${response.status}`);
|
||||
@@ -522,7 +522,7 @@ async function getCertificate(fqdn, domainObject, key) {
|
||||
const owner = await users.getOwner();
|
||||
const email = owner?.email || 'webmaster@cloudron.io'; // can error if not activated yet
|
||||
|
||||
return await promiseRetry({ times: 3, interval: 0, log }, async function () {
|
||||
return await retry({ times: 3, interval: 0, log }, async function () {
|
||||
log(`getCertificate: for fqdn ${fqdn} and domain ${domainObject.domain}`);
|
||||
|
||||
const acme = new Acme2(fqdn, domainObject, email, key, { /* profile: 'shortlived' */ });
|
||||
|
||||
@@ -6,7 +6,7 @@ import constants from './constants.js';
|
||||
import logger from './logger.js';
|
||||
import docker from './docker.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import superagent from '@cloudron/superagent';
|
||||
|
||||
const { log } = logger('apphealthmonitor');
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import crypto from 'node:crypto';
|
||||
import database from './database.js';
|
||||
import logger from './logger.js';
|
||||
import jsdom from 'jsdom';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import superagent from '@cloudron/superagent';
|
||||
|
||||
const { log } = logger('applinks');
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import BoxError from './boxerror.js';
|
||||
import crypto from 'node:crypto';
|
||||
import database from './database.js';
|
||||
import hat from './hat.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import _ from './underscore.js';
|
||||
|
||||
|
||||
|
||||
+59
-27
@@ -29,7 +29,7 @@ import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import { PassThrough } from 'node:stream';
|
||||
import reverseProxy from './reverseproxy.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import semver from 'semver';
|
||||
import services from './services.js';
|
||||
import shellModule from './shell.js';
|
||||
@@ -78,7 +78,7 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.versionsUrl',
|
||||
'apps.sso', 'apps.devicesJson', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.containerIp', 'apps.crontab',
|
||||
'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', 'apps.upstreamUri', 'apps.checklistJson', 'apps.updateInfoJson',
|
||||
'apps.enableMailbox', 'apps.mailboxDisplayName', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain',
|
||||
'apps.enableTurn', 'apps.enableRedis', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.packageIcon IS NOT NULL) AS hasPackageIcon' ].join(',');
|
||||
'apps.enableTurn', 'apps.enableRedis', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', 'apps.buildConfigJson', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.packageIcon IS NOT NULL) AS hasPackageIcon' ].join(',');
|
||||
|
||||
// const PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId', 'count' ].join(',');
|
||||
const LOCATION_FIELDS = [ 'appId', 'subdomain', 'domain', 'type', 'certificateJson' ];
|
||||
@@ -412,6 +412,10 @@ function postProcess(result) {
|
||||
result.servicesConfig = safe.JSON.parse(result.servicesConfigJson) || {};
|
||||
delete result.servicesConfigJson;
|
||||
|
||||
assert(result.buildConfigJson === null || typeof result.buildConfigJson === 'string');
|
||||
result.buildConfig = safe.JSON.parse(result.buildConfigJson) || { buildArgs: [], dockerfileName: null };
|
||||
delete result.buildConfigJson;
|
||||
|
||||
const subdomains = JSON.parse(result.subdomains),
|
||||
parsedDomains = JSON.parse(result.domains),
|
||||
subdomainTypes = JSON.parse(result.subdomainTypes),
|
||||
@@ -524,7 +528,7 @@ function pickFields(app, level) {
|
||||
'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags',
|
||||
'label', 'notes', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'devices', 'env', 'enableAutomaticUpdate',
|
||||
'storageVolumeId', 'storageVolumePrefix', 'mounts', 'enableTurn', 'enableRedis', 'checklist',
|
||||
'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain', 'updateInfo']);
|
||||
'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain', 'updateInfo', 'buildConfig']);
|
||||
}
|
||||
|
||||
// remove private certificate key
|
||||
@@ -606,7 +610,8 @@ async function add(id, appStoreId, versionsUrl, manifest, subdomain, domain, por
|
||||
notes = data.notes || null,
|
||||
crontab = data.crontab || null,
|
||||
enableBackup = 'enableBackup' in data ? data.enableBackup : true,
|
||||
enableAutomaticUpdate = 'enableAutomaticUpdate' in data ? data.enableAutomaticUpdate : true;
|
||||
enableAutomaticUpdate = 'enableAutomaticUpdate' in data ? data.enableAutomaticUpdate : true,
|
||||
buildConfigJson = data.buildConfig ? JSON.stringify(data.buildConfig) : null;
|
||||
|
||||
// when redis is optional, do not enable it by default. it's mostly used for caching in those setups
|
||||
const enableRedis = 'enableRedis' in data ? data.enableRedis : !manifest.addons?.redis?.optional;
|
||||
@@ -618,12 +623,12 @@ async function add(id, appStoreId, versionsUrl, manifest, subdomain, domain, por
|
||||
queries.push({
|
||||
query: 'INSERT INTO apps (id, appStoreId, versionsUrl, manifestJson, installationState, runState, accessRestrictionJson, operatorsJson, memoryLimit, cpuQuota, '
|
||||
+ 'sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, checklistJson, servicesConfigJson, icon, '
|
||||
+ 'enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis, devicesJson, notes, crontab, enableBackup, enableAutomaticUpdate) '
|
||||
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
+ 'enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis, devicesJson, notes, crontab, enableBackup, enableAutomaticUpdate, buildConfigJson) '
|
||||
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
args: [ id, appStoreId, versionsUrl, manifestJson, installationState, runState, accessRestrictionJson, operatorsJson, memoryLimit, cpuQuota,
|
||||
sso, debugModeJson, mailboxName, mailboxDomain, label, tagsJson, reverseProxyConfigJson, checklistJson, servicesConfigJson, icon,
|
||||
enableMailbox, mailboxDisplayName, upstreamUri, enableTurn, enableRedis, devicesJson, notes, crontab,
|
||||
enableBackup, enableAutomaticUpdate
|
||||
enableBackup, enableAutomaticUpdate, buildConfigJson
|
||||
]
|
||||
});
|
||||
|
||||
@@ -744,24 +749,27 @@ async function updateWithConstraints(id, app, constraints) {
|
||||
if ('subdomain' in app && 'domain' in app) { // must be updated together as they are unique together
|
||||
queries.push({ query: 'DELETE FROM locations WHERE appId = ?', args: [ id ]}); // all locations of an app must be updated together
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, app.domain, app.subdomain, Location.TYPE_PRIMARY ]});
|
||||
}
|
||||
|
||||
if ('secondaryDomains' in app) {
|
||||
app.secondaryDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type, environmentVariable) VALUES (?, ?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_SECONDARY, d.environmentVariable ]});
|
||||
});
|
||||
}
|
||||
if ('secondaryDomains' in app) {
|
||||
queries.push({ query: 'DELETE FROM locations WHERE appId = ? AND type = ?', args: [ id, Location.TYPE_SECONDARY ]});
|
||||
app.secondaryDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type, environmentVariable) VALUES (?, ?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_SECONDARY, d.environmentVariable ]});
|
||||
});
|
||||
}
|
||||
|
||||
if ('redirectDomains' in app) {
|
||||
app.redirectDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_REDIRECT ]});
|
||||
});
|
||||
}
|
||||
if ('redirectDomains' in app) {
|
||||
queries.push({ query: 'DELETE FROM locations WHERE appId = ? AND type = ?', args: [ id, Location.TYPE_REDIRECT ]});
|
||||
app.redirectDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_REDIRECT ]});
|
||||
});
|
||||
}
|
||||
|
||||
if ('aliasDomains' in app) {
|
||||
app.aliasDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_ALIAS ]});
|
||||
});
|
||||
}
|
||||
if ('aliasDomains' in app) {
|
||||
queries.push({ query: 'DELETE FROM locations WHERE appId = ? AND type = ?', args: [ id, Location.TYPE_ALIAS ]});
|
||||
app.aliasDomains.forEach(function (d) {
|
||||
queries.push({ query: 'INSERT INTO locations (appId, domain, subdomain, type) VALUES (?, ?, ?, ?)', args: [ id, d.domain, d.subdomain, Location.TYPE_ALIAS ]});
|
||||
});
|
||||
}
|
||||
|
||||
if ('mounts' in app) {
|
||||
@@ -774,7 +782,7 @@ async function updateWithConstraints(id, app, constraints) {
|
||||
const fields = [ ], values = [ ];
|
||||
for (const p in app) {
|
||||
if (p === 'manifest' || p === 'tags' || p === 'checklist' || p === 'accessRestriction' || p === 'devices' || p === 'debugMode' || p === 'error'
|
||||
|| p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators' || p === 'updateInfo') {
|
||||
|| p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators' || p === 'updateInfo' || p === 'buildConfig') {
|
||||
fields.push(`${p}Json = ?`);
|
||||
values.push(JSON.stringify(app[p]));
|
||||
} else if (p !== 'portBindings' && p !== 'subdomain' && p !== 'domain' && p !== 'secondaryDomains' && p !== 'redirectDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') {
|
||||
@@ -1605,7 +1613,7 @@ async function loadConfig(app) {
|
||||
const appConfig = safe.JSON.parse(safe.fs.readFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/config.json')));
|
||||
let data = {};
|
||||
if (appConfig) {
|
||||
data = _.pick(appConfig, ['memoryLimit', 'cpuQuota', 'enableBackup', 'reverseProxyConfig', 'env', 'servicesConfig', 'label', 'tags', 'enableAutomaticUpdate']);
|
||||
data = _.pick(appConfig, ['memoryLimit', 'cpuQuota', 'enableBackup', 'reverseProxyConfig', 'env', 'servicesConfig', 'label', 'tags', 'enableAutomaticUpdate', 'buildConfig']);
|
||||
}
|
||||
|
||||
const icon = safe.fs.readFileSync(path.join(paths.APPS_DATA_DIR, app.id + '/icon.png'));
|
||||
@@ -1921,9 +1929,14 @@ async function install(data, auditSource) {
|
||||
|
||||
if (data.sourceArchiveFilePath) await fileUtils.renameFile(data.sourceArchiveFilePath, `${paths.SOURCE_ARCHIVES_DIR}/${appId}.tar.gz`);
|
||||
|
||||
const buildConfig = {
|
||||
buildArgs: data.buildConfig?.buildArgs || [],
|
||||
dockerfileName: data.buildConfig?.dockerfileName || null
|
||||
};
|
||||
|
||||
const task = {
|
||||
args: { restoreConfig: null, skipDnsSetup, overwriteDns },
|
||||
values: { },
|
||||
values: { buildConfig },
|
||||
requiredState: app.installationState
|
||||
};
|
||||
|
||||
@@ -2340,6 +2353,10 @@ async function updateApp(app, data, auditSource) {
|
||||
const updateConfig = { skipBackup, manifest }; // this will clear appStoreId/versionsUrl when updating from a repo and set it if passed in for update route
|
||||
if ('appStoreId' in data) updateConfig.appStoreId = data.appStoreId;
|
||||
if ('versionsUrl' in data) updateConfig.versionsUrl = data.versionsUrl;
|
||||
updateConfig.buildConfig = {
|
||||
buildArgs: data.buildConfig?.buildArgs || [],
|
||||
dockerfileName: data.buildConfig?.dockerfileName || null
|
||||
};
|
||||
|
||||
// prevent user from installing a app with different manifest id over an existing app
|
||||
// this allows cloudron install -f --app <appid> for an app installed from the appStore
|
||||
@@ -2388,6 +2405,8 @@ async function updateApp(app, data, auditSource) {
|
||||
|
||||
if (data.sourceArchiveFilePath) await fileUtils.renameFile(data.sourceArchiveFilePath, `${paths.SOURCE_ARCHIVES_DIR}/${appId}.tar.gz`);
|
||||
|
||||
values.buildConfig = updateConfig.buildConfig;
|
||||
|
||||
const task = {
|
||||
args: { updateConfig },
|
||||
values
|
||||
@@ -2483,6 +2502,19 @@ async function restore(app, backupId, auditSource) {
|
||||
values.inboxName = values.inboxDomain = null;
|
||||
}
|
||||
|
||||
// prune secondaryDomains whose httpPorts no longer exist in the restored manifest
|
||||
const newHttpPorts = manifest.httpPorts || {};
|
||||
values.secondaryDomains = app.secondaryDomains.filter(sd => sd.environmentVariable in newHttpPorts);
|
||||
|
||||
// prune portBindings whose tcpPorts/udpPorts no longer exist in the restored manifest
|
||||
const newTcpPorts = manifest.tcpPorts || {};
|
||||
const newUdpPorts = manifest.udpPorts || {};
|
||||
const portBindings = {};
|
||||
for (const portName in app.portBindings) {
|
||||
if (portName in newTcpPorts || portName in newUdpPorts) portBindings[portName] = app.portBindings[portName];
|
||||
}
|
||||
values.portBindings = portBindings;
|
||||
|
||||
const restoreConfig = { backupId: restoreBackup.id };
|
||||
|
||||
const task = {
|
||||
@@ -2624,7 +2656,7 @@ async function clone(app, data, user, auditSource) {
|
||||
const dolly = _.pick(cloneBackup.appConfig || app, ['memoryLimit', 'cpuQuota', 'crontab', 'reverseProxyConfig', 'env', 'servicesConfig', 'tags', 'devices',
|
||||
'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain', 'debugMode',
|
||||
'enableTurn', 'enableRedis', 'mounts', 'enableBackup', 'enableAutomaticUpdate', 'accessRestriction', 'operators', 'sso',
|
||||
'notes', 'checklist']);
|
||||
'notes', 'checklist', 'buildConfig']);
|
||||
|
||||
if (!manifest.addons?.recvmail) dolly.inboxDomain = null; // needed because we are cloning _current_ app settings with old manifest
|
||||
|
||||
@@ -2700,7 +2732,7 @@ async function unarchive(archiveEntry, data, auditSource) {
|
||||
const dolly = _.pick(archiveBackup.appConfig || {}, ['memoryLimit', 'cpuQuota', 'crontab', 'reverseProxyConfig', 'env', 'servicesConfig',
|
||||
'tags', 'label', 'enableMailbox', 'mailboxDisplayName', 'mailboxName', 'mailboxDomain', 'enableInbox', 'inboxName', 'inboxDomain', 'devices',
|
||||
'enableTurn', 'enableRedis', 'mounts', 'enableBackup', 'enableAutomaticUpdate', 'accessRestriction', 'operators', 'sso',
|
||||
'notes', 'checklist']);
|
||||
'notes', 'checklist', 'buildConfig']);
|
||||
|
||||
// intentionally not filled up: redirectDomain, aliasDomains, mailboxDomain
|
||||
const obj = Object.assign(dolly, {
|
||||
|
||||
+3
-3
@@ -14,8 +14,8 @@ import mail from './mail.js';
|
||||
import manifestFormat from '@cloudron/manifest-format';
|
||||
import oidcClients from './oidcclients.js';
|
||||
import paths from './paths.js';
|
||||
import promiseRetry from './promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import retry from './retry.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import semver from 'semver';
|
||||
import settings from './settings.js';
|
||||
import superagent from '@cloudron/superagent';
|
||||
@@ -388,7 +388,7 @@ async function getApp(appId) {
|
||||
async function downloadIcon(appStoreId, version) {
|
||||
const iconUrl = `${await getApiServerOrigin()}/api/v1/apps/${appStoreId}/versions/${version}/icon`;
|
||||
|
||||
return await promiseRetry({ times: 10, interval: 5000, log }, async function () {
|
||||
return await retry({ times: 10, interval: 5000, log }, async function () {
|
||||
const [networkError, response] = await safe(superagent.get(iconUrl)
|
||||
.timeout(60 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
+22
-14
@@ -21,9 +21,9 @@ import manifestFormat from '@cloudron/manifest-format';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import promiseRetry from './promise-retry.js';
|
||||
import retry from './retry.js';
|
||||
import reverseProxy from './reverseproxy.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import services from './services.js';
|
||||
import shellModule from './shell.js';
|
||||
import _ from './underscore.js';
|
||||
@@ -63,7 +63,7 @@ async function allocateContainerIp(app) {
|
||||
|
||||
if (app.manifest.id === constants.PROXY_APP_APPSTORE_ID) return;
|
||||
|
||||
await promiseRetry({ times: 10, interval: 0, log }, async function () {
|
||||
await retry({ times: 10, interval: 0, log }, async function () {
|
||||
const iprange = iputils.intFromIp(constants.APPS_IPv4_END) - iputils.intFromIp(constants.APPS_IPv4_START);
|
||||
const rnd = Math.floor(Math.random() * iprange);
|
||||
const containerIp = iputils.ipFromInt(iputils.intFromIp(constants.APPS_IPv4_START) + rnd);
|
||||
@@ -198,8 +198,6 @@ async function downloadImage(manifest) {
|
||||
async function buildLocalImage(app) {
|
||||
assert.strictEqual(typeof app, 'object');
|
||||
|
||||
// TODO some precondition checks like downloadImage maybe
|
||||
|
||||
const sourceFilePath = path.join(paths.APPS_DATA_DIR, app.id, 'source.tar.gz');
|
||||
|
||||
// if we have a newly uploaded source archive, use that
|
||||
@@ -220,7 +218,7 @@ async function buildLocalImage(app) {
|
||||
}
|
||||
}
|
||||
|
||||
await docker.buildImage(app.manifest.dockerImage, sourceFilePath);
|
||||
await docker.buildImage(app.manifest.dockerImage, sourceFilePath, app.buildConfig);
|
||||
}
|
||||
|
||||
async function updateChecklist(app, newChecks, acknowledged = false) {
|
||||
@@ -331,7 +329,8 @@ async function uninstallCommand(app, args, progressCallback) {
|
||||
await deleteContainers(app, {});
|
||||
|
||||
await progressCallback({ percent: 30, message: 'Teardown addons' });
|
||||
await services.teardownAddons(app, app.manifest.addons);
|
||||
// if install/clone/restore/import failed early (e.g. invalid image), services may not have started
|
||||
await services.teardownAddons(app, app.manifest.addons, { ignoreError: true });
|
||||
await services.teardownPersistentDirs(app);
|
||||
|
||||
await progressCallback({ percent: 40, message: 'Cleanup file manager' });
|
||||
@@ -408,7 +407,7 @@ async function installCommand(app, args, progressCallback) {
|
||||
// when restoring, app does not require these addons anymore. remove carefully to preserve the db passwords
|
||||
if (oldManifest) {
|
||||
const addonsToRemove = _.omit(oldManifest.addons, Object.keys(app.manifest.addons));
|
||||
await services.teardownAddons(app, addonsToRemove);
|
||||
await services.teardownAddons(app, addonsToRemove, {});
|
||||
}
|
||||
|
||||
if (!restoreConfig || restoreConfig.remotePath || restoreConfig.backupId) { // install/import/restore but not in-place import should delete data dir
|
||||
@@ -602,7 +601,7 @@ async function changeServicesCommand(app, args, progressCallback) {
|
||||
if (app.manifest.addons?.redis && !app.enableRedis) unusedAddons.redis = app.manifest.addons.redis;
|
||||
|
||||
await progressCallback({ percent: 20, message: 'Removing unused addons' });
|
||||
await services.teardownAddons(app, unusedAddons);
|
||||
await services.teardownAddons(app, unusedAddons, {});
|
||||
|
||||
await progressCallback({ percent: 40, message: 'Setting up addons' });
|
||||
await services.setupAddons(app, app.manifest.addons);
|
||||
@@ -658,11 +657,16 @@ async function configureCommand(app, args, progressCallback) {
|
||||
await progressCallback({ percent: 40, message: 'Downloading image' });
|
||||
await downloadImage(app.manifest);
|
||||
|
||||
await progressCallback({ percent: 45, message: 'Ensuring app data directory' });
|
||||
if (app.manifest.dockerImage.indexOf('local/') === 0) {
|
||||
await progressCallback({ percent: 45, message: 'Building image' });
|
||||
await buildLocalImage(app);
|
||||
}
|
||||
|
||||
await progressCallback({ percent: 50, message: 'Ensuring app data directory' });
|
||||
await createAppDir(app);
|
||||
|
||||
// re-setup addons since they rely on the app's fqdn (e.g oauth)
|
||||
await progressCallback({ percent: 50, message: 'Setting up addons' });
|
||||
await progressCallback({ percent: 55, message: 'Setting up addons' });
|
||||
await services.setupAddons(app, app.manifest.addons);
|
||||
|
||||
await progressCallback({ percent: 60, message: 'Creating container' });
|
||||
@@ -688,6 +692,7 @@ async function updateCommand(app, args, progressCallback) {
|
||||
// FIXME: this does not handle option changes (like multipleDatabases)
|
||||
const unusedAddons = _.omit(app.manifest.addons, Object.keys(updateConfig.manifest.addons));
|
||||
const httpPortChanged = app.manifest.httpPort !== updateConfig.manifest.httpPort;
|
||||
const httpPortsChanged = !_.isEqual(app.manifest.httpPorts, updateConfig.manifest.httpPorts);
|
||||
const proxyAuthChanged = !_.isEqual(app.manifest.addons?.proxyAuth, updateConfig.manifest.addons?.proxyAuth);
|
||||
|
||||
// this protects against the theoretical possibility of an app being marked for update from
|
||||
@@ -727,7 +732,7 @@ async function updateCommand(app, args, progressCallback) {
|
||||
if (app.manifest.dockerImage !== updateConfig.manifest.dockerImage) await docker.deleteImage(app.manifest.dockerImage);
|
||||
|
||||
// only delete unused addons after backup
|
||||
await services.teardownAddons(app, unusedAddons);
|
||||
await services.teardownAddons(app, unusedAddons, {});
|
||||
if (Object.keys(unusedAddons).includes('localstorage')) await updateApp(app, { storageVolumeId: null, storageVolumePrefix: null }); // lose reference
|
||||
|
||||
// teardown persistent dirs removed in the new manifest
|
||||
@@ -758,6 +763,7 @@ async function updateCommand(app, args, progressCallback) {
|
||||
if (environmentVariable in newHttpPorts) continue; // domain still in use
|
||||
secondaryDomains.splice(i, 1); // remove domain
|
||||
}
|
||||
const removedSecondaryDomains = app.secondaryDomains.filter(sd => !secondaryDomains.some(nsd => nsd.subdomain === sd.subdomain && nsd.domain === sd.domain));
|
||||
|
||||
const values = {
|
||||
manifest: updateConfig.manifest,
|
||||
@@ -774,6 +780,8 @@ async function updateCommand(app, args, progressCallback) {
|
||||
|
||||
await updateApp(app, values); // switch over to the new config
|
||||
|
||||
if (removedSecondaryDomains.length) await dns.unregisterLocations(removedSecondaryDomains, progressCallback);
|
||||
|
||||
await progressCallback({ percent: 45, message: 'Downloading icon' });
|
||||
await downloadIcon(app);
|
||||
|
||||
@@ -793,7 +801,7 @@ async function updateCommand(app, args, progressCallback) {
|
||||
await startApp(app);
|
||||
|
||||
await progressCallback({ percent: 90, message: 'Configuring reverse proxy' });
|
||||
if (proxyAuthChanged || httpPortChanged) {
|
||||
if (proxyAuthChanged || httpPortChanged || httpPortsChanged || removedSecondaryDomains.length) {
|
||||
await reverseProxy.configureApp(app, AuditSource.APPTASK);
|
||||
}
|
||||
|
||||
@@ -867,7 +875,7 @@ async function run(appId, args, progressCallback) {
|
||||
log('run: update aborted because backup failed');
|
||||
await safe(updateApp(app, { installationState: apps.ISTATE_INSTALLED, error: null, health: null }, { log }));
|
||||
} else {
|
||||
await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) }), { log });
|
||||
await safe(updateApp(app, { installationState: apps.ISTATE_ERROR, error: makeTaskError(error, app) }), { debug: log });
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
@@ -5,7 +5,7 @@ import fs from 'node:fs';
|
||||
import locks from './locks.js';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import scheduler from './scheduler.js';
|
||||
import tasks from './tasks.js';
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import BoxError from './boxerror.js';
|
||||
import crypto from 'node:crypto';
|
||||
import database from './database.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
|
||||
|
||||
const ARCHIVE_FIELDS = [ 'archives.id', 'backupId', 'archives.creationTime', 'backups.remotePath', 'backups.siteId', 'backups.manifestJson', 'backups.appConfigJson', '(archives.icon IS NOT NULL) AS hasIcon', '(archives.packageIcon IS NOT NULL) AS hasPackageIcon' ];
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import logger from './logger.js';
|
||||
import EventEmitter from 'node:events';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
|
||||
const { log } = logger('asynctask');
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import assert from 'node:assert';
|
||||
import apps from './apps.js';
|
||||
import BoxError from './boxerror.js';
|
||||
import constants from './constants.js';
|
||||
import express from 'express';
|
||||
import http from 'node:http';
|
||||
import logger from './logger.js';
|
||||
import middleware from './middleware/index.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import users from './users.js';
|
||||
import util from 'node:util';
|
||||
import { HttpError } from '@cloudron/connect-lastmile';
|
||||
|
||||
const { trace, log } = logger('authserver');
|
||||
|
||||
let gHttpServer = null;
|
||||
|
||||
// Internal loopback-only password check; skipTotpCheck matches LDAP bind behavior for automated callers.
|
||||
const VERIFY_OPTIONS = { skipTotpCheck: true };
|
||||
|
||||
function peerIpForAppLookup(req) {
|
||||
let ip = req.socket.remoteAddress;
|
||||
if (!ip) return '';
|
||||
if (ip.startsWith('::ffff:')) ip = ip.slice(7);
|
||||
return ip;
|
||||
}
|
||||
|
||||
async function verifyPost(req, res, next) {
|
||||
if (!req.body || typeof req.body !== 'object') return next(new HttpError(400, 'Body must be a JSON object'));
|
||||
|
||||
const { identifier, password } = req.body;
|
||||
if (typeof identifier !== 'string' || identifier.length === 0) return next(new HttpError(400, 'identifier must be a non-empty string'));
|
||||
if (typeof password !== 'string' || password.length === 0) return next(new HttpError(400, 'password must be a non-empty string'));
|
||||
|
||||
trace(`verifyPost: attempt for ${identifier}`);
|
||||
|
||||
let verifyFunc;
|
||||
if (identifier.startsWith('uid-')) verifyFunc = users.verifyWithId;
|
||||
else if (identifier.includes('@')) verifyFunc = users.verifyWithEmail;
|
||||
else verifyFunc = users.verifyWithUsername;
|
||||
|
||||
const [, connectingApp] = await safe(apps.getByIpAddress(peerIpForAppLookup(req)));
|
||||
const appId = connectingApp ? connectingApp.id : '';
|
||||
|
||||
const [verifyError, user] = await safe(verifyFunc(identifier, password, appId, VERIFY_OPTIONS));
|
||||
|
||||
if (verifyError && verifyError.reason === BoxError.INVALID_CREDENTIALS) return next(new HttpError(401, verifyError.message));
|
||||
if (verifyError && verifyError.reason === BoxError.NOT_FOUND) return next(new HttpError(401, 'Username and password does not match'));
|
||||
if (verifyError) return next(new HttpError(500, verifyError));
|
||||
if (!user) return next(new HttpError(401, 'Username and password does not match'));
|
||||
|
||||
res.status(200).json({ ok: true });
|
||||
}
|
||||
|
||||
async function start() {
|
||||
assert(gHttpServer === null, 'Auth server already started');
|
||||
|
||||
const app = express();
|
||||
app.enable('trust proxy');
|
||||
|
||||
const json = express.json({ strict: true, limit: '2mb' });
|
||||
|
||||
app.post('/verify-credentials', json, verifyPost);
|
||||
app.use(middleware.lastMile());
|
||||
|
||||
gHttpServer = http.createServer(app);
|
||||
|
||||
log(`start: listening on ${constants.DOCKER_IPv4_GATEWAY}:${constants.AUTH_PORT}`);
|
||||
await util.promisify(gHttpServer.listen.bind(gHttpServer))(constants.AUTH_PORT, constants.DOCKER_IPv4_GATEWAY);
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
if (!gHttpServer) return;
|
||||
|
||||
await util.promisify(gHttpServer.close.bind(gHttpServer))();
|
||||
gHttpServer = null;
|
||||
}
|
||||
|
||||
export default {
|
||||
start,
|
||||
stop,
|
||||
};
|
||||
|
||||
export { peerIpForAppLookup };
|
||||
@@ -9,7 +9,7 @@ import constants from './constants.js';
|
||||
import logger from './logger.js';
|
||||
import moment from 'moment';
|
||||
import path from 'node:path';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
|
||||
const { log } = logger('backupcleaner');
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import path from 'node:path';
|
||||
import paths from '../paths.js';
|
||||
import { pipeline } from 'node:stream/promises';
|
||||
import ProgressStream from '../progress-stream.js';
|
||||
import promiseRetry from '../promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import retry from '../retry.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import shellModule from '../shell.js';
|
||||
import syncer from '../syncer.js';
|
||||
import util from 'node:util';
|
||||
@@ -193,7 +193,7 @@ async function sync(backupSite, remotePath, dataLayout, progressCallback) {
|
||||
} else if (change.operation === 'remove') {
|
||||
await backupSites.storageApi(backupSite).remove(backupSite.config, fullPath);
|
||||
} else if (change.operation === 'add') {
|
||||
await promiseRetry({ times: 5, interval: 20000, log }, async (retryCount) => {
|
||||
await retry({ times: 5, interval: 20000, log }, async (retryCount) => {
|
||||
reportUploadProgress(`Current: ${change.path}` + (retryCount > 1 ? ` (Try ${retryCount})` : ''));
|
||||
if (retryCount > 1) log(`sync: retrying ${change.path} position ${change.position} try ${retryCount}`);
|
||||
const uploader = await backupSites.storageApi(backupSite).upload(backupSite.config, backupSite.limits, fullPath);
|
||||
@@ -259,7 +259,7 @@ async function downloadDir(backupSite, remotePath, dataLayout, progressCallback)
|
||||
const [mkdirError] = await safe(fs.promises.mkdir(path.dirname(destFilePath), { recursive: true }));
|
||||
if (mkdirError) throw new BoxError(BoxError.FS_ERROR, mkdirError.message);
|
||||
|
||||
await promiseRetry({ times: 3, interval: 20000 }, async function () {
|
||||
await retry({ times: 3, interval: 20000 }, async function () {
|
||||
reportProgress(`Current: ${relativePath}`);
|
||||
|
||||
const [downloadError, sourceStream] = await safe(backupSites.storageApi(backupSite).download(backupSite.config, entry.path));
|
||||
|
||||
@@ -9,8 +9,8 @@ import fs from 'node:fs';
|
||||
import HashStream from '../hash-stream.js';
|
||||
import path from 'node:path';
|
||||
import ProgressStream from '../progress-stream.js';
|
||||
import promiseRetry from '../promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import retry from '../retry.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import stream from 'stream/promises';
|
||||
import { Transform } from 'node:stream';
|
||||
import tar from 'tar-stream';
|
||||
@@ -74,7 +74,7 @@ function addEntryToPack(pack, header, options) {
|
||||
|
||||
if (options?.input) {
|
||||
const ensureFileSizeStream = new EnsureFileSizeStream({ name: header.name, size: header.size });
|
||||
safe(stream.pipeline(options.input, ensureFileSizeStream, packEntry), { log }); // background. rely on pack.entry callback for promise completion
|
||||
safe(stream.pipeline(options.input, ensureFileSizeStream, packEntry), { debug: log }); // background. rely on pack.entry callback for promise completion
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -163,7 +163,7 @@ async function tarPack(dataLayout, encryption, uploader, progressCallback) {
|
||||
|
||||
let fileCount = 0;
|
||||
for (const localPath of dataLayout.localPaths()) {
|
||||
const [error, stats] = await safe(addPathToPack(pack, localPath, dataLayout), { log });
|
||||
const [error, stats] = await safe(addPathToPack(pack, localPath, dataLayout), { debug: log });
|
||||
if (error) break; // the pipeline will error and we will retry the whole packing all over
|
||||
fileCount += stats.fileCount;
|
||||
}
|
||||
@@ -253,7 +253,7 @@ async function download(backupSite, remotePath, dataLayout, progressCallback) {
|
||||
|
||||
log(`download: Downloading ${remotePath} to ${dataLayout.toString()}`);
|
||||
|
||||
await promiseRetry({ times: 3, interval: 20000, log }, async () => {
|
||||
await retry({ times: 3, interval: 20000, log }, async () => {
|
||||
progressCallback({ message: `Downloading backup ${remotePath}` });
|
||||
|
||||
const sourceStream = await backupSites.storageApi(backupSite).download(backupSite.config, remotePath);
|
||||
@@ -269,7 +269,7 @@ async function upload(backupSite, remotePath, dataLayout, progressCallback) {
|
||||
|
||||
log(`upload: uploading to site ${backupSite.id} path ${remotePath} (encrypted: ${!!backupSite.encryption}) dataLayout ${dataLayout.toString()}`);
|
||||
|
||||
return await promiseRetry({ times: 5, interval: 20000, log }, async () => {
|
||||
return await retry({ times: 5, interval: 20000, log }, async () => {
|
||||
progressCallback({ message: `Uploading backup ${remotePath}` });
|
||||
|
||||
const uploader = await backupSites.storageApi(backupSite).upload(backupSite.config, backupSite.limits, remotePath);
|
||||
|
||||
@@ -6,7 +6,7 @@ import BoxError from './boxerror.js';
|
||||
import consumers from 'node:stream/consumers';
|
||||
import crypto from 'node:crypto';
|
||||
import logger from './logger.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
|
||||
const { log } = logger('backupintegrity');
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import database from './database.js';
|
||||
import logger from './logger.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import hat from './hat.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import tasks from './tasks.js';
|
||||
|
||||
const { log } = logger('backups');
|
||||
|
||||
+9
-4
@@ -12,7 +12,7 @@ import hush from './hush.js';
|
||||
import locks from './locks.js';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import tasks from './tasks.js';
|
||||
import storageFilesystem from './storage/filesystem.js';
|
||||
import storageS3 from './storage/s3.js';
|
||||
@@ -26,7 +26,7 @@ const { log } = logger('backups');
|
||||
// config: depends on the 'provider' field. 'provider' is not stored in config object. but it is injected when calling the api backends
|
||||
// s3 providers - accessKeyId, secretAccessKey, bucket, prefix etc . see s3.js
|
||||
// gcs - bucket, prefix, projectId, credentials . see gcs.js
|
||||
// ext4/xfs/disk (managed providers) - mountOptions (diskPath), prefix, noHardlinks. disk is legacy.
|
||||
// ext4/xfs (managed providers) - mountOptions (diskPath), prefix, noHardlinks
|
||||
// nfs/cifs/sshfs (managed providers) - mountOptions (host/username/password/seal/privateKey etc), prefix, noHardlinks
|
||||
// filesystem - backupDir, noHardlinks
|
||||
// mountpoint - mountPoint, prefix, noHardlinks
|
||||
@@ -35,7 +35,7 @@ const BACKUP_TARGET_FIELDS = [ 'id', 'name', 'provider', 'configJson', 'limitsJs
|
||||
|
||||
const STORAGE_PROVIDERS = {
|
||||
nfs: storageFilesystem, cifs: storageFilesystem, sshfs: storageFilesystem,
|
||||
mountpoint: storageFilesystem, disk: storageFilesystem, ext4: storageFilesystem,
|
||||
mountpoint: storageFilesystem, ext4: storageFilesystem,
|
||||
xfs: storageFilesystem, filesystem: storageFilesystem,
|
||||
s3: storageS3, minio: storageS3, 's3-v4-compat': storageS3,
|
||||
'digitalocean-spaces': storageS3, 'exoscale-sos': storageS3, wasabi: storageS3,
|
||||
@@ -565,6 +565,10 @@ async function reinitAll() {
|
||||
}
|
||||
}
|
||||
|
||||
async function clear() {
|
||||
await database.query('DELETE FROM backupSites');
|
||||
}
|
||||
|
||||
export default {
|
||||
get,
|
||||
list,
|
||||
@@ -601,5 +605,6 @@ export default {
|
||||
|
||||
createPseudo,
|
||||
|
||||
reinitAll
|
||||
reinitAll,
|
||||
clear
|
||||
};
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import locks from './locks.js';
|
||||
import path from 'node:path';
|
||||
import paths from './paths.js';
|
||||
import { Readable } from 'node:stream';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import services from './services.js';
|
||||
import shellModule from './shell.js';
|
||||
import stream from 'stream/promises';
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import constants from './constants.js';
|
||||
import logger from './logger.js';
|
||||
import eventlog from './eventlog.js';
|
||||
import paths from './paths.js';
|
||||
import safe from 'safetydance';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import settings from './settings.js';
|
||||
|
||||
const { log } = logger('branding');
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@ import assert from 'node:assert';
|
||||
import BoxError from './boxerror.js';
|
||||
import logger from './logger.js';
|
||||
import manifestFormat from '@cloudron/manifest-format';
|
||||
import promiseRetry from './promise-retry.js';
|
||||
import safe from 'safetydance';
|
||||
import retry from './retry.js';
|
||||
import safe from '@cloudron/safetydance';
|
||||
import superagent from '@cloudron/superagent';
|
||||
|
||||
const { log } = logger('community');
|
||||
@@ -170,7 +170,7 @@ async function getAppUpdate(app, options) {
|
||||
}
|
||||
|
||||
async function downloadIcon(manifest) {
|
||||
return await promiseRetry({ times: 10, interval: 5000, log }, async function () {
|
||||
return await retry({ times: 10, interval: 5000, log }, async function () {
|
||||
const [networkError, response] = await safe(superagent.get(manifest.iconUrl)
|
||||
.timeout(60 * 1000)
|
||||
.ok(() => true));
|
||||
|
||||
@@ -30,6 +30,7 @@ export default {
|
||||
DOCKER_PROXY_PORT: 3003,
|
||||
USER_DIRECTORY_LDAPS_PORT: 3004, // user directory LDAP with TLS rerouting in iptables, public port is 636
|
||||
OIDC_PORT: 3005,
|
||||
AUTH_PORT: 3006,
|
||||
TURN_PORT: 3478, // tcp and udp
|
||||
TURN_TLS_PORT: 5349, // tcp and udp
|
||||
TURN_UDP_PORT_START: 50000,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user