Compare commits

...

40 Commits

Author SHA1 Message Date
b-Nollet
838576c8be adding default message for unexisting VMs in backups 2024-01-29 07:59:23 +01:00
b-Nollet
e8bc723f98 fix(backup): prevent task creation for empty directories 2024-01-29 07:43:24 +01:00
OlivierFL
8e65ef7dbc feat(xo-web/logs): transform objects UUIDs into clickable links (#7300)
In Settings/Logs modals : transform objects UUIDs into clickable links, leading
to the corresponding object page.
For objects that are not found, UUID can be copied to clipboard.
2024-01-26 17:28:30 +01:00
Mathieu
0c0251082d feat(xo-web/pool): ability to do a rolling pool reboot (#7243)
Fixes #6885
See #7242
2024-01-26 17:08:52 +01:00
Pierre Donias
c250cd9b89 feat(xo-web/VM): ability to add custom notes (#7322)
Fixes #5792
2024-01-26 14:59:32 +01:00
Florent BEAUCHAMP
d6abdb246b feat(xo-server): implement rolling pool reboot (#7242) 2024-01-25 17:50:34 +01:00
Pierre Donias
5769da3ebc feat(xo-web/tags): add tooltips on xo:no-bak and xo:notify-on-snapshot tags (#7335) 2024-01-25 10:27:42 +01:00
Julien Fontanet
4f383635ef feat(xo-server/rest-api): validate params 2024-01-24 11:41:22 +01:00
Julien Fontanet
8a7abc2e54 feat(xo-cli/rest): display error response body 2024-01-24 11:23:43 +01:00
Julien Fontanet
af1650bd14 chore: update dev deps 2024-01-24 10:54:02 +01:00
Julien Fontanet
c6fdef33c4 feat(xo-server): web signin with auth token in query string (#7314)
Potential issue: the token stays in the browser history.
2024-01-24 10:02:38 +01:00
Florent BEAUCHAMP
5f73f09f59 feat(fuse-vhd): implement cli (#7310) 2024-01-23 17:14:18 +01:00
Thierry Goettelmann
1b0fc62e2e feat(xo6): introducing @xen-orchestra/web (#7309)
Introduces `@xen-orchestra/web`, which will be the home for the new incoming
Xen Orchestra v6.

It uses `@xen-orchestra/web-core` as a foundation.

Upgraded common dependencies of Lite/Web/Core.
2024-01-23 11:25:18 +01:00
Julien Fontanet
aa2dc9206d chore: format with Prettier
Introduced by 85ec26194
2024-01-23 11:02:28 +01:00
Mathieu
2b1562da81 fix(xo-server/PIF): IPv4 reconfiguration only worked when mode was updated (#7324) 2024-01-23 10:55:37 +01:00
Mathieu
25e270edb4 feat(xo-web,xo-server/tag): add colored tag (#7262) 2024-01-23 09:46:06 +01:00
MlssFrncJrg
51c11c15a8 feat(xo-web/pool): disable Rolling Pool Update if pool has 1 host (#7286)
See #6415
2024-01-22 11:04:38 +01:00
Julien Fontanet
47922dee56 fix(xo-server/patching): fix typo
Introduced by 901f7b3fe

Fixes #7325
2024-01-20 09:35:48 +01:00
Julien Fontanet
3dda4dbaad chore: format with Prettier 2024-01-19 16:42:10 +01:00
Julien Fontanet
901f7b3fe2 chore(xo-server): remove object properties decorator syntax
This syntax is not supported by Prettier.
2024-01-19 16:42:10 +01:00
Julien Fontanet
774d66512e feat(decorate-with): decorateObject() 2024-01-19 16:42:10 +01:00
b-Nollet
ec1669a32e feat(xo-server-load-balancer): limit concurrent migrations (#7297)
Fixes #7084
2024-01-19 16:37:55 +01:00
Florent BEAUCHAMP
bbcd4184b0 feat(xo-web,backups): fix dynamic disks count with suspend_VDI (#7315)
`suspend_VDI` was counted as a dynamic VHD.

This commit ignores it in the computation, but add a tag if the backup is with memory.
2024-01-19 16:34:06 +01:00
Mathieu
85ec26194b feat(xo-web/host): ask for confirmation to reboot the updated slave host if the master is not (#7293)
Fixes #7059

In case a slave host requires a reboot to apply updates and the master is using
the same version as the slave host, a confirmation modal is triggered.
2024-01-19 11:18:40 +01:00
MlssFrncJrg
f0242380ca feat(xo-web/tab-advanced): allow to update VM creator (#7276)
Related to [forum#7313](https://xcp-ng.org/forum/topic/7313/change-created-by-and-date-information)
2024-01-19 10:40:08 +01:00
Julien Fontanet
a624330818 fix(usage-to-readme): fix multiple .USAGE.md changes 2024-01-19 10:23:52 +01:00
Julien Fontanet
3892efcca2 feat(xo-web/plugins): auto-load follow load/unload (#7317)
Loading, or unloading, will respectively enable, or disable, _Auto-load at server start_,
this should lead to least surprising behaviors.
2024-01-19 09:55:36 +01:00
Mathieu
c1c122d92c feat(xo-web/pool/host): add warning if hosts don't have the same version (#7280)
Fixes #7059
2024-01-18 17:13:57 +01:00
Julien Fontanet
b7a66e9f73 chore(web-core): add missing npmignore
Follow-up of d92d2efc7
2024-01-18 13:29:30 +01:00
Julien Fontanet
d92d2efc78 chore(web-core): normalize package.json 2024-01-18 12:39:39 +01:00
Julien Fontanet
c2cb51a470 feat(lint-staged): add .USAGE.md → README.md
So that it's not needed to manually runs from `normalize-packages.js`.
2024-01-18 12:34:23 +01:00
Thierry Goettelmann
5242affdc1 feat(lite): introducing @xen-orchestra/web-core (#7302)
This PR introduces `@xen-orchestra/web-core`, which will be the common base for
XO Lite and XO 6.

This package is not meant to be distributed and will be used as-is in other
packages thanks to Yarn Workspace. This mean that the files of XO Web Core will
not be built by themselves but by either the package which use it.

Styles have been moved from XO Lite to XO Web Core.

Colors variable have been renamed and updated according to the new Design
System. XO Lite has been updated accordingly.
- `extra-blue` → `purple`
- `green-infra` → `green`
- `orange-world` → `orange`
- `red-vates` → `red`
- `blue-scale` → `grey`

⚠️ A new intermediate shade has been introduced (`--color-grey-400`). So
`--color-blue-scale-400` is now `--color-grey-500` and `--color-blue-scale-500`
is now `--color-grey-600`.

PostCSS color function plugin is used to generate the shades of color like it is
done on the Figma mockup (with blending the base color with black or white at
different degrees).

PostCSS custom media are now loaded globally thanks to a plugin and no longer
require to import `_responsive.pcss` file manually in each file where a custom
media was needed.
2024-01-18 10:04:25 +01:00
Dom Del Nano
71f3be288b feat(xo-server): add xenStoreData to XO VM objects (#7316)
The initial support added in #7055 to support terraform resource support doesn't provide read access to a VM's state.

Since Tterraform's model requires read and write access to the resource it's managing, this PR implements the missing piece for terra-farm/terraform-provider-xenorchestra#261.
2024-01-17 10:24:08 +01:00
OlivierFL
58769815b0 fix(xo-web/modal): close modal when navigating to another url (#7301) 2024-01-16 20:47:21 +01:00
Florent BEAUCHAMP
c81c23c0d0 fix(fuse-vhd): potential race condition in mount/unmount (#7312)
The code was not properly waiting mount/unmount to be done.
2024-01-16 18:27:30 +01:00
Julien Fontanet
f06f89b5b4 feat(self-signed): readCert utility (#7282)
Expired certificates are not automatically detected, which is not a big deal for user certificates because they can still be used and it's their responsibility to update them.

But automatic certificates must be regenerated in that case which was not the case until now.

This commit unifies certificate/key reading, checking and generation for both xo-server and xo-proxy.
2024-01-16 16:58:15 +01:00
Julien Fontanet
fa748ed9de feat(xo-server): load plugins from mono-repo
Contrary to 3e3ce543a8, which was reverted,
this implentation properly handle duplicates.
2024-01-16 15:54:12 +01:00
Julien Fontanet
cd753acff7 feat(xo-server): move plugin lookup paths to config 2024-01-16 15:51:45 +01:00
Julien Fontanet
8ff861e2be feat(xo-server): find plugins sequentially
This provides a deterministic order.

In case of duplicate plugins (with the same name), the first found plugin takes precedence.
2024-01-16 15:31:03 +01:00
Julien Fontanet
95ccb2e0ae feat(gitignore): ignore .tap/ 2024-01-16 14:02:33 +01:00
182 changed files with 4130 additions and 2296 deletions

View File

@@ -48,7 +48,7 @@ module.exports = {
},
},
{
files: ['@xen-orchestra/lite/**/*.{vue,ts}'],
files: ['@xen-orchestra/{web-core,lite,web}/**/*.{vue,ts}'],
parserOptions: {
sourceType: 'module',
},

3
.gitignore vendored
View File

@@ -36,3 +36,6 @@ yarn-error.log.*
.nyc_output/
coverage/
.turbo/
# https://node-tap.org/dot-tap-folder/
.tap/

View File

@@ -62,6 +62,42 @@ decorateClass(Foo, {
})
```
### `decorateObject(object, map)`
Decorates an object the same way `decorateClass()` decorates a class:
```js
import { decorateObject } from '@vates/decorate-with'
const object = {
get bar() {
// body
},
set bar(value) {
// body
},
baz() {
// body
},
}
decorateObject(object, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
### `perInstance(fn, ...args)`
Helper to decorate the method by instance instead of for the whole class.

View File

@@ -80,6 +80,42 @@ decorateClass(Foo, {
})
```
### `decorateObject(object, map)`
Decorates an object the same way `decorateClass()` decorates a class:
```js
import { decorateObject } from '@vates/decorate-with'
const object = {
get bar() {
// body
},
set bar(value) {
// body
},
baz() {
// body
},
}
decorateObject(object, {
// getter and/or setter
bar: {
// without arguments
get: lodash.memoize,
// with arguments
set: [lodash.debounce, 150],
},
// method (with or without arguments)
baz: lodash.curry,
})
```
### `perInstance(fn, ...args)`
Helper to decorate the method by instance instead of for the whole class.

View File

@@ -14,10 +14,13 @@ function applyDecorator(decorator, value) {
}
exports.decorateClass = exports.decorateMethodsWith = function decorateClass(klass, map) {
const { prototype } = klass
return decorateObject(klass.prototype, map)
}
function decorateObject(object, map) {
for (const name of Object.keys(map)) {
const decorator = map[name]
const descriptor = getOwnPropertyDescriptor(prototype, name)
const descriptor = getOwnPropertyDescriptor(object, name)
if (typeof decorator === 'function' || Array.isArray(decorator)) {
descriptor.value = applyDecorator(decorator, descriptor.value)
} else {
@@ -30,10 +33,11 @@ exports.decorateClass = exports.decorateMethodsWith = function decorateClass(kla
}
}
defineProperty(prototype, name, descriptor)
defineProperty(object, name, descriptor)
}
return klass
return object
}
exports.decorateObject = decorateObject
exports.perInstance = function perInstance(fn, decorator, ...args) {
const map = new WeakMap()

28
@vates/fuse-vhd/.USAGE.md Normal file
View File

@@ -0,0 +1,28 @@
Mount a vhd generated by xen-orchestra to filesystem
### Library
```js
import { mount } from 'fuse-vhd'
// return a disposable, see promise-toolbox/Disposable
// unmount automatically when disposable is disposed
// in case of differencing VHD, it mounts the full chain
await mount(handler, diskId, mountPoint)
```
### cli
From the install folder :
```
cli.mjs <remoteUrl> <vhdPathInRemote> <mountPoint>
```
After installing the package
```
xo-fuse-vhd <remoteUrl> <vhdPathInRemote> <mountPoint>
```
remoteUrl can be found by using cli in `@xen-orchestra/fs` , for example a local remote will have a url like `file:///path/to/remote/root`

59
@vates/fuse-vhd/README.md Normal file
View File

@@ -0,0 +1,59 @@
<!-- DO NOT EDIT MANUALLY, THIS FILE HAS BEEN GENERATED -->
# @vates/fuse-vhd
[![Package Version](https://badgen.net/npm/v/@vates/fuse-vhd)](https://npmjs.org/package/@vates/fuse-vhd) ![License](https://badgen.net/npm/license/@vates/fuse-vhd) [![PackagePhobia](https://badgen.net/bundlephobia/minzip/@vates/fuse-vhd)](https://bundlephobia.com/result?p=@vates/fuse-vhd) [![Node compatibility](https://badgen.net/npm/node/@vates/fuse-vhd)](https://npmjs.org/package/@vates/fuse-vhd)
## Install
Installation of the [npm package](https://npmjs.org/package/@vates/fuse-vhd):
```sh
npm install --save @vates/fuse-vhd
```
## Usage
Mount a vhd generated by xen-orchestra to filesystem
### Library
```js
import { mount } from 'fuse-vhd'
// return a disposable, see promise-toolbox/Disposable
// unmount automatically when disposable is disposed
// in case of differencing VHD, it mounts the full chain
await mount(handler, diskId, mountPoint)
```
### cli
From the install folder :
```
cli.mjs <remoteUrl> <vhdPathInRemote> <mountPoint>
```
After installing the package
```
xo-fuse-vhd <remoteUrl> <vhdPathInRemote> <mountPoint>
```
remoteUrl can be found by using cli in `@xen-orchestra/fs` , for example a local remote will have a url like `file:///path/to/remote/root`
## Contributions
Contributions are _very_ welcomed, either on the documentation or on
the code.
You may:
- report any [issue](https://github.com/vatesfr/xen-orchestra/issues)
you've encountered;
- fork and create a pull request.
## License
[ISC](https://spdx.org/licenses/ISC) © [Vates SAS](https://vates.fr)

26
@vates/fuse-vhd/cli.mjs Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env node
import Disposable from 'promise-toolbox/Disposable'
import { getSyncedHandler } from '@xen-orchestra/fs'
import { mount } from './index.mjs'
async function* main([remoteUrl, vhdPathInRemote, mountPoint]) {
if (mountPoint === undefined) {
throw new TypeError('missing arg: cli <remoteUrl> <vhdPathInRemote> <mountPoint>')
}
const handler = yield getSyncedHandler({ url: remoteUrl })
const mounted = await mount(handler, vhdPathInRemote, mountPoint)
let disposePromise
process.on('SIGINT', async () => {
// ensure single dispose
if (!disposePromise) {
disposePromise = mounted.dispose()
}
await disposePromise
process.exit()
})
}
Disposable.wrap(main)(process.argv.slice(2))

View File

@@ -58,7 +58,7 @@ export const mount = Disposable.factory(async function* mount(handler, diskPath,
},
})
return new Disposable(
() => fromCallback(() => fuse.unmount()),
fromCallback(() => fuse.mount())
() => fromCallback(cb => fuse.unmount(cb)),
fromCallback(cb => fuse.mount(cb))
)
})

View File

@@ -19,11 +19,15 @@
},
"main": "./index.mjs",
"dependencies": {
"@xen-orchestra/fs": "^4.1.3",
"fuse-native": "^2.2.6",
"lru-cache": "^7.14.0",
"promise-toolbox": "^0.21.0",
"vhd-lib": "^4.9.0"
},
"bin": {
"xo-fuse-vhd": "./cli.mjs"
},
"scripts": {
"postversion": "npm publish --access public"
}

View File

@@ -86,7 +86,12 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
throw new Error(`Job mode ${job.mode} not implemented for mirror backup`)
}
return runTask(taskStart, () => vmBackup.run())
return sourceRemoteAdapter
.listVmBackups(vmUuid, ({ mode }) => mode === job.mode)
.then(vmBackups => {
// avoiding to create tasks for empty directories
if (vmBackups.length > 0) return runTask(taskStart, () => vmBackup.run())
})
}
const { concurrency } = settings
await asyncMapSettled(vmsUuids, !concurrency ? handleVm : limitConcurrency(concurrency)(handleVm))

View File

@@ -2,8 +2,20 @@ import mapValues from 'lodash/mapValues.js'
import { dirname } from 'node:path'
function formatVmBackup(backup) {
const { isVhdDifferencing } = backup
const { isVhdDifferencing, vmSnapshot } = backup
let differencingVhds
let dynamicVhds
const withMemory = vmSnapshot.suspend_VDI !== 'OpaqueRef:NULL'
// isVhdDifferencing is either undefined or an object
if (isVhdDifferencing !== undefined) {
differencingVhds = Object.values(isVhdDifferencing).filter(t => t).length
dynamicVhds = Object.values(isVhdDifferencing).filter(t => !t).length
if (withMemory) {
// the suspend VDI (memory) is always a dynamic
dynamicVhds -= 1
}
}
return {
disks:
backup.vhds === undefined
@@ -28,9 +40,9 @@ function formatVmBackup(backup) {
name_label: backup.vm.name_label,
},
// isVhdDifferencing is either undefined or an object
differencingVhds: isVhdDifferencing && Object.values(isVhdDifferencing).filter(t => t).length,
dynamicVhds: isVhdDifferencing && Object.values(isVhdDifferencing).filter(t => !t).length,
differencingVhds,
dynamicVhds,
withMemory,
}
}

View File

@@ -2,8 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="manifest" href="/manifest.webmanifest">
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>XO Lite</title>
</head>

View File

@@ -14,6 +14,7 @@
"type-check": "vue-tsc --build --force tsconfig.type-check.json"
},
"devDependencies": {
"@csstools/postcss-global-data": "^2.1.1",
"@fontsource/poppins": "^5.0.8",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
@@ -22,16 +23,16 @@
"@intlify/unplugin-vue-i18n": "^2.0.0",
"@novnc/novnc": "^1.4.0",
"@tsconfig/node18": "^18.2.2",
"strip-json-comments": "^5.0.1",
"@types/d3-time-format": "^4.0.3",
"@types/file-saver": "^2.0.7",
"@types/lodash-es": "^4.17.12",
"@types/node": "^18.19.5",
"@vitejs/plugin-vue": "^5.0.2",
"@types/node": "^18.19.7",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/tsconfig": "^0.5.1",
"@vueuse/core": "^10.7.1",
"@vueuse/math": "^10.7.1",
"@vueuse/shared": "^10.7.1",
"@xen-orchestra/web-core": "*",
"complex-matcher": "^0.7.1",
"d3-time-format": "^4.1.0",
"decorator-synchronized": "^0.6.0",
@@ -51,15 +52,16 @@
"pinia": "^2.1.7",
"placement.js": "^1.0.0-beta.5",
"postcss": "^8.4.33",
"postcss-color-function": "^4.1.0",
"postcss-custom-media": "^10.0.2",
"postcss-nested": "^6.0.1",
"typescript": "^5.3.3",
"typescript": "~5.3.3",
"vite": "^5.0.11",
"vue": "^3.4.7",
"vue": "^3.4.13",
"vue-echarts": "^6.6.8",
"vue-i18n": "^9.9.0",
"vue-router": "^4.2.5",
"vue-tsc": "^1.8.22",
"vue-tsc": "^1.8.27",
"zx": "^7.2.3"
},
"private": true,

View File

@@ -1,6 +1,10 @@
export default {
plugins: {
'@csstools/postcss-global-data': {
files: ['../web-core/lib/assets/css/.globals.pcss'],
},
'postcss-nested': {},
'postcss-custom-media': {},
'postcss-color-function': {},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -16,7 +16,6 @@
</template>
<script lang="ts" setup>
import favicon from '@/assets/favicon.svg'
import AppHeader from '@/components/AppHeader.vue'
import AppLogin from '@/components/AppLogin.vue'
import AppNavigation from '@/components/AppNavigation.vue'
@@ -32,14 +31,6 @@ import { logicAnd } from '@vueuse/math'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
let link = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null
if (link == null) {
link = document.createElement('link')
link.rel = 'icon'
document.getElementsByTagName('head')[0].appendChild(link)
}
link.href = favicon
const xenApiStore = useXenApiStore()
const { pool } = usePoolCollection()
@@ -75,10 +66,6 @@ whenever(
useUnreachableHosts()
</script>
<style lang="postcss">
@import '@/assets/base.css';
</style>
<style lang="postcss" scoped>
.main {
overflow: auto;

View File

@@ -1,2 +0,0 @@
@custom-media --mobile (max-width: 1023px);
@custom-media --desktop (min-width: 1024px);

View File

@@ -1,100 +0,0 @@
@import 'reset.css';
@import 'theme.css';
@import '@fontsource/poppins/400.css';
@import '@fontsource/poppins/500.css';
@import '@fontsource/poppins/600.css';
@import '@fontsource/poppins/700.css';
@import '@fontsource/poppins/900.css';
@import '@fontsource/poppins/400-italic.css';
body {
min-height: 100vh;
font-size: 1.3rem;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--color-blue-scale-100);
}
a {
color: var(--color-extra-blue-base);
}
code,
code *,
pre {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
}
.card-view {
padding: 1.2rem;
display: flex;
gap: 2rem;
}
.link {
text-decoration: underline;
color: var(--color-extra-blue-base);
cursor: pointer;
}
.link:hover {
color: var(--color-extra-blue-d20);
}
.link:active,
.link.router-link-active {
color: var(--color-extra-blue-d40);
}
.link.router-link-active {
text-decoration: underline;
}
.context-color-success {
color: var(--color-green-infra-base);
}
.context-color-error {
color: var(--color-red-vates-base);
}
.context-color-warning {
color: var(--color-orange-world-base);
}
.context-color-info {
color: var(--color-extra-blue-base);
}
.context-background-color-success {
background-color: var(--background-color-green-infra);
}
.context-background-color-error {
background-color: var(--background-color-red-vates);
}
.context-background-color-warning {
background-color: var(--background-color-orange-world);
}
.context-background-color-info {
background-color: var(--background-color-extra-blue);
}
.context-border-color-success {
border-color: var(--color-green-infra-base);
}
.context-border-color-error {
border-color: var(--color-red-vates-base);
}
.context-border-color-warning {
border-color: var(--color-orange-world-base);
}
.context-border-color-info {
border-color: var(--color-extra-blue-base);
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,87 +0,0 @@
:root {
--color-logo: #282467;
--color-blue-scale-000: #000000;
--color-blue-scale-100: #1a1b38;
--color-blue-scale-200: #595a6f;
--color-blue-scale-300: #9899a5;
--color-blue-scale-400: #e5e5e7;
--color-blue-scale-500: #ffffff;
--color-extra-blue-l60: #d1cefb;
--color-extra-blue-l40: #bbb5f9;
--color-extra-blue-l20: #a39df8;
--color-extra-blue-base: #8f84ff;
--color-extra-blue-d20: #716ac6;
--color-extra-blue-d40: #554f94;
--color-extra-blue-d60: #383563;
--color-green-infra-l60: #b5dbca;
--color-green-infra-l40: #91c9b0;
--color-green-infra-l20: #70b795;
--color-green-infra-base: #55a57b;
--color-green-infra-d20: #438463;
--color-green-infra-d40: #32634a;
--color-green-infra-d60: #214231;
--color-orange-world-l60: #f2cda8;
--color-orange-world-l40: #ebb57d;
--color-orange-world-l20: #e59d56;
--color-orange-world-base: #ef7f18;
--color-orange-world-d20: #bf6612;
--color-orange-world-d40: #864f1f;
--color-orange-world-d60: #5a3514;
--color-red-vates-l60: #dda5a7;
--color-red-vates-l40: #ce787c;
--color-red-vates-l20: #bf4f51;
--color-red-vates-base: #be1621;
--color-red-vates-d20: #8e2221;
--color-red-vates-d40: #6a1919;
--color-red-vates-d60: #471010;
--color-grayscale-200: #585757;
--background-color-primary: #ffffff;
--background-color-secondary: #f6f6f7;
--background-color-extra-blue: #f4f3fe;
--background-color-green-infra: #ecf5f2;
--background-color-orange-world: #fbf2e9;
--background-color-red-vates: #f5e8e9;
--shadow-100: 0 0.1rem 0.1rem rgba(20, 20, 30, 0.06);
--shadow-200: 0 0.1rem 0.3rem rgba(20, 20, 30, 0.1), 0 0.2rem 0.1rem rgba(20, 20, 30, 0.06),
0 0.1rem 0.1rem rgba(20, 20, 30, 0.08);
--shadow-300: 0 0.3rem 0.5rem rgba(20, 20, 30, 0.1), 0 0.1rem 1.8rem rgba(20, 20, 30, 0.06),
0 0.6rem 1rem rgba(20, 20, 30, 0.08);
--shadow-400: 0 1.1rem 1.5rem rgba(20, 20, 30, 0.1), 0 0.9rem 4.6rem rgba(20, 20, 30, 0.06),
0 2.4rem 3.8rem rgba(20, 20, 30, 0.04);
}
:root.dark {
color-scheme: dark;
--color-logo: #e5e5e7;
--color-blue-scale-000: #ffffff;
--color-blue-scale-100: #e5e5e7;
--color-blue-scale-200: #9899a5;
--color-blue-scale-300: #595a6f;
--color-blue-scale-400: #1a1b38;
--color-blue-scale-500: #000000;
--background-color-primary: #14141d;
--background-color-secondary: #17182a;
--background-color-extra-blue: #35335d;
--background-color-green-infra: #243b3d;
--background-color-orange-world: #493328;
--background-color-red-vates: #3c1a28;
--shadow-100: 0 0.1rem 0.1rem rgba(20, 20, 30, 0.12);
--shadow-200: 0 0.1rem 0.3rem rgba(20, 20, 30, 0.2), 0 0.2rem 0.1rem rgba(20, 20, 30, 0.12),
0 0.1rem 0.1rem rgba(20, 20, 30, 0.16);
--shadow-300: 0 0.3rem 0.5rem rgba(20, 20, 30, 0.2), 0 0.1rem 1.8rem rgba(20, 20, 30, 0.12),
0 0.6rem 1rem rgba(20, 20, 30, 0.16);
--shadow-400: 0 1.1rem 1.5rem rgba(20, 20, 30, 0.2), 0 0.9rem 4.6rem rgba(20, 20, 30, 0.12),
0 2.4rem 3.8rem rgba(20, 20, 30, 0.08);
}

View File

@@ -51,14 +51,14 @@ const openSettings = () => router.push({ name: 'settings' })
display: flex;
align-items: center;
padding: 1rem;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
border: none;
border-radius: 0.8rem;
background-color: var(--background-color-secondary);
gap: 0.8rem;
&:disabled {
color: var(--color-blue-scale-400);
color: var(--color-grey-500);
}
&:not(:disabled) {
@@ -72,7 +72,7 @@ const openSettings = () => router.push({ name: 'settings' })
&:active,
&.active {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
}
}
@@ -86,6 +86,6 @@ const openSettings = () => router.push({ name: 'settings' })
}
.menu-item-logout {
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
</style>

View File

@@ -46,7 +46,7 @@ const { trigger: navigationTrigger } = storeToRefs(navigationStore)
justify-content: space-between;
height: 5.5rem;
padding: 1rem;
border-bottom: 0.1rem solid var(--color-blue-scale-400);
border-bottom: 0.1rem solid var(--color-grey-500);
background-color: var(--background-color-secondary);
img {

View File

@@ -135,7 +135,7 @@ form {
background-color: var(--background-color-secondary);
.error {
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
}
@@ -156,7 +156,7 @@ input {
max-width: 100%;
margin-bottom: 1rem;
padding: 1rem 1.5rem;
border: 1px solid var(--color-blue-scale-400);
border: 1px solid var(--color-grey-500);
border-radius: 0.8rem;
background-color: white;
}

View File

@@ -60,7 +60,7 @@ useEventListener(
}
code:not(.hljs-code) {
background-color: var(--background-color-extra-blue);
background-color: var(--background-color-purple-10);
padding: 0.3rem 0.6rem;
border-radius: 0.6rem;
}
@@ -81,12 +81,12 @@ useEventListener(
}
thead th {
border-bottom: 2px solid var(--color-blue-scale-400);
border-bottom: 2px solid var(--color-grey-500);
background-color: var(--background-color-secondary);
}
tbody td {
border-bottom: 1px solid var(--color-blue-scale-400);
border-bottom: 1px solid var(--color-grey-500);
}
}
@@ -103,11 +103,11 @@ useEventListener(
background-color: transparent;
&:hover {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
&:active {
color: var(--color-extra-blue-d20);
color: var(--color-purple-d20);
}
}
}

View File

@@ -45,7 +45,7 @@ whenever(isOpen, () => {
max-width: 37rem;
height: calc(100vh - 5.5rem);
padding: 0.5rem;
border-right: 1px solid var(--color-blue-scale-400);
border-right: 1px solid var(--color-grey-500);
background-color: var(--background-color-primary);
&.collapsible {

View File

@@ -41,9 +41,9 @@ watchEffect(() => {
display: inline-flex;
padding: 0.3125em 0.5em;
pointer-events: none;
color: var(--color-blue-scale-500);
color: var(--color-grey-600);
border-radius: 0.5em;
background-color: var(--color-blue-scale-100);
background-color: var(--color-grey-100);
z-index: 2;
}
@@ -145,6 +145,6 @@ watchEffect(() => {
content: '';
transform: rotate(45deg) skew(20deg, 20deg);
border-radius: 0.3125em;
background-color: var(--color-blue-scale-100);
background-color: var(--color-grey-100);
}
</style>

View File

@@ -54,14 +54,14 @@ const isIcon = (maybeIcon: any): maybeIcon is IconDefinition => typeof maybeIcon
align-items: stretch;
overflow: hidden;
padding: 0 0.7rem;
border: 1px solid var(--color-blue-scale-400);
border: 1px solid var(--color-grey-500);
border-radius: 0.8rem;
background-color: var(--color-blue-scale-500);
background-color: var(--color-grey-600);
box-shadow: var(--shadow-100);
gap: 0.1rem;
&:focus-within {
outline: 1px solid var(--color-extra-blue-l40);
outline: 1px solid var(--color-purple-l40);
}
}
@@ -71,7 +71,7 @@ const isIcon = (maybeIcon: any): maybeIcon is IconDefinition => typeof maybeIcon
}
.form-widget:hover .widget {
border-color: var(--color-extra-blue-l60);
border-color: var(--color-purple-l60);
}
.element {
@@ -93,8 +93,8 @@ const isIcon = (maybeIcon: any): maybeIcon is IconDefinition => typeof maybeIcon
font-size: inherit;
border: none;
outline: none;
color: var(--color-blue-scale-100);
background-color: var(--color-blue-scale-500);
color: var(--color-grey-100);
background-color: var(--color-grey-600);
flex: 1;
&:disabled {
@@ -134,7 +134,7 @@ const isIcon = (maybeIcon: any): maybeIcon is IconDefinition => typeof maybeIcon
&:disabled {
cursor: not-allowed;
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
}
}
</style>

View File

@@ -25,7 +25,7 @@ defineProps<{
font-size: 1.3rem;
line-height: 150%;
margin: 0.5rem 0;
color: var(--color-red-vates-base);
color: var(--color-red-base);
& svg {
margin-right: 0.5rem;

View File

@@ -25,6 +25,6 @@
font-weight: 500;
font-size: 1.25em;
line-height: 150%;
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
</style>

View File

@@ -27,6 +27,6 @@
font-weight: 500;
font-size: 2rem;
line-height: 150%;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
</style>

View File

@@ -85,7 +85,7 @@ const objectRoute = computed(() => {
<style lang="postcss" scoped>
.unknown {
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
font-style: italic;
}
</style>

View File

@@ -33,7 +33,7 @@ const isRecordNotFound = computed(() => props.isReady && !props.uuidChecker(id.v
}
.spinner {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
display: flex;
margin: auto;
width: 10rem;

View File

@@ -26,7 +26,7 @@ import UiStatusPanel from '@/components/ui/UiStatusPanel.vue'
.contact {
font-weight: 400;
font-size: 20px;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
& a {
text-transform: lowercase;

View File

@@ -44,7 +44,7 @@ const masterSessionStorage = useSessionStorage('master', null)
<style lang="postcss" scoped>
.warning-not-current-pool {
color: var(--color-orange-world-base);
color: var(--color-orange-base);
cursor: pointer;
.wrapper {

View File

@@ -26,18 +26,18 @@ const className = computed(() => `state-${props.state.toLocaleLowerCase()}`)
<style lang="postcss" scoped>
.power-state-icon {
color: var(--color-extra-blue-d60);
color: var(--color-purple-d60);
&.state-running {
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
&.state-paused {
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
}
&.state-suspended {
color: var(--color-extra-blue-d20);
color: var(--color-purple-d20);
}
}
</style>

View File

@@ -37,7 +37,7 @@ const progress = computed(() => {
.progress-circle-fill {
animation: progress 1s ease-out forwards;
fill: none;
stroke: var(--color-green-infra-base);
stroke: var(--color-green-base);
stroke-width: 1.2;
stroke-linecap: round;
stroke-dasharray: v-bind(progress), 100;
@@ -46,13 +46,13 @@ const progress = computed(() => {
.progress-circle-background {
fill: none;
stroke-width: 1.2;
stroke: var(--color-blue-scale-400);
stroke: var(--color-grey-500);
}
.progress-circle-text {
font-size: 0.7rem;
font-weight: bold;
fill: var(--color-green-infra-base);
fill: var(--color-green-base);
text-anchor: middle;
alignment-baseline: middle;
}

View File

@@ -29,18 +29,18 @@ defineProps<{
align-items: center;
height: 6rem;
padding: 0 1.5rem;
border-bottom: 1px solid var(--color-blue-scale-400);
border-bottom: 1px solid var(--color-grey-500);
background-color: var(--background-color-primary);
gap: 0.8rem;
}
.icon {
font-size: 2.5rem;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
.title {
font-size: 2.5rem;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
}
</style>

View File

@@ -60,28 +60,28 @@ const computedData = computed(() => {
}
.progress-item:nth-child(1) {
--progress-bar-color: var(--color-extra-blue-d60);
--progress-bar-color: var(--color-purple-d60);
}
.progress-item:nth-child(2) {
--progress-bar-color: var(--color-extra-blue-d40);
--progress-bar-color: var(--color-purple-d40);
}
.progress-item:nth-child(3) {
--progress-bar-color: var(--color-extra-blue-d20);
--progress-bar-color: var(--color-purple-d20);
}
.progress-item {
--progress-bar-height: 1.2rem;
--progress-bar-color: var(--color-extra-blue-l20);
--progress-bar-background-color: var(--color-blue-scale-400);
--progress-bar-color: var(--color-purple-l20);
--progress-bar-background-color: var(--color-grey-500);
&.warning {
--progress-bar-color: var(--color-orange-world-base);
--progress-bar-color: var(--color-orange-base);
}
&.error {
--progress-bar-color: var(--color-red-vates-base);
--progress-bar-color: var(--color-red-base);
}
}
</style>

View File

@@ -25,12 +25,12 @@
th,
td {
padding: 0.3rem 0.6rem;
border-bottom: 0.1rem solid var(--color-blue-scale-400);
border-bottom: 0.1rem solid var(--color-grey-500);
vertical-align: center;
}
&:nth-child(odd) {
background-color: var(--background-color-extra-blue);
background-color: var(--background-color-purple-10);
}
}

View File

@@ -127,14 +127,14 @@ const openRawValueModal = (code: string) =>
align-items: center;
padding: 0.4rem 0.6rem;
cursor: pointer;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
border-radius: 0.4rem;
gap: 0.6rem;
&.active {
font-weight: 600;
cursor: default;
color: var(--color-green-infra-l20);
color: var(--color-green-l20);
}
}
}
@@ -157,7 +157,7 @@ const openRawValueModal = (code: string) =>
.help {
font-style: italic;
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
}
.default-value {
@@ -168,12 +168,12 @@ const openRawValueModal = (code: string) =>
font-weight: 600;
font-style: normal;
opacity: 1;
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
}
.v-model-indicator,
.context-indicator {
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
</style>

View File

@@ -81,7 +81,7 @@ const isIndeterminate = computed(() => (type === 'checkbox' || type === 'toggle'
.input.indeterminate + .fake-checkbox > .icon {
opacity: 1;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
}
}
@@ -114,7 +114,7 @@ const isIndeterminate = computed(() => (type === 'checkbox' || type === 'toggle'
.fake-checkbox {
width: 2.5em;
--background-color: var(--color-blue-scale-400);
--background-color: var(--color-grey-500);
}
.icon {
@@ -128,7 +128,7 @@ const isIndeterminate = computed(() => (type === 'checkbox' || type === 'toggle'
.input.indeterminate + .fake-checkbox > .icon {
opacity: 1;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
transform: translateX(0);
}
}
@@ -143,7 +143,7 @@ const isIndeterminate = computed(() => (type === 'checkbox' || type === 'toggle'
.icon {
font-size: var(--checkbox-icon-size);
position: absolute;
color: var(--color-blue-scale-500);
color: var(--color-grey-600);
filter: drop-shadow(0 0.0625em 0.5em rgba(0, 0, 0, 0.1)) drop-shadow(0 0.1875em 0.1875em rgba(0, 0, 0, 0.06))
drop-shadow(0 0.1875em 0.25em rgba(0, 0, 0, 0.08));
@@ -162,44 +162,44 @@ const isIndeterminate = computed(() => (type === 'checkbox' || type === 'toggle'
background-color: var(--background-color);
box-shadow: var(--shadow-100);
--border-color: var(--color-blue-scale-400);
--border-color: var(--color-grey-500);
}
.input:disabled {
& + .fake-checkbox {
cursor: not-allowed;
--background-color: var(--background-color-secondary);
--border-color: var(--color-blue-scale-400);
--border-color: var(--color-grey-500);
}
&:checked + .fake-checkbox {
--border-color: transparent;
--background-color: var(--color-extra-blue-l60);
--background-color: var(--color-purple-l60);
}
}
.input:not(:disabled) {
&:hover + .fake-checkbox,
&:focus + .fake-checkbox {
--border-color: var(--color-extra-blue-l40);
--border-color: var(--color-purple-l40);
}
&:active + .fake-checkbox {
--border-color: var(--color-extra-blue-l20);
--border-color: var(--color-purple-l20);
}
&:checked + .fake-checkbox {
--border-color: transparent;
--background-color: var(--color-extra-blue-base);
--background-color: var(--color-purple-base);
}
&:checked:hover + .fake-checkbox,
&:checked:focus + .fake-checkbox {
--background-color: var(--color-extra-blue-d20);
--background-color: var(--color-purple-d20);
}
&:checked:active + .fake-checkbox {
--background-color: var(--color-extra-blue-d40);
--background-color: var(--color-purple-d40);
}
}
</style>

View File

@@ -144,14 +144,14 @@ defineExpose({
--after-width: v-bind('afterWidth || "1.625em"');
--caret-width: 1.5em;
--text-color: var(--color-blue-scale-100);
--text-color: var(--color-grey-100);
&.empty {
--text-color: var(--color-blue-scale-300);
--text-color: var(--color-grey-300);
}
&.disabled {
--text-color: var(--color-blue-scale-400);
--text-color: var(--color-grey-500);
}
}
@@ -189,7 +189,7 @@ defineExpose({
}
--background-color: var(--background-color-primary);
--border-color: var(--color-blue-scale-400);
--border-color: var(--color-grey-500);
&:disabled {
cursor: not-allowed;
@@ -199,63 +199,63 @@ defineExpose({
&:not(:disabled) {
&.info {
&:hover {
--border-color: var(--color-extra-blue-l60);
--border-color: var(--color-purple-l60);
}
&:active {
--border-color: var(--color-extra-blue-l40);
--border-color: var(--color-purple-l40);
}
&:focus {
--border-color: var(--color-extra-blue-base);
--border-color: var(--color-purple-base);
}
}
&.success {
--border-color: var(--color-green-infra-base);
--border-color: var(--color-green-base);
&:hover {
--border-color: var(--color-green-infra-l60);
--border-color: var(--color-green-l60);
}
&:active {
--border-color: var(--color-green-infra-l40);
--border-color: var(--color-green-l40);
}
&:focus {
--border-color: var(--color-green-infra-base);
--border-color: var(--color-green-base);
}
}
&.warning {
--border-color: var(--color-orange-world-base);
--border-color: var(--color-orange-base);
&:hover {
--border-color: var(--color-orange-world-l60);
--border-color: var(--color-orange-l60);
}
&:active {
--border-color: var(--color-orange-world-l40);
--border-color: var(--color-orange-l40);
}
&:focus {
--border-color: var(--color-orange-world-base);
--border-color: var(--color-orange-base);
}
}
&.error {
--border-color: var(--color-red-vates-base);
--border-color: var(--color-red-base);
&:hover {
--border-color: var(--color-red-vates-l60);
--border-color: var(--color-red-l60);
}
&:active {
--border-color: var(--color-red-vates-l40);
--border-color: var(--color-red-l40);
}
&:focus-within {
--border-color: var(--color-red-vates-base);
--border-color: var(--color-red-base);
}
}
}

View File

@@ -96,7 +96,7 @@ useContext(DisabledContext, () => props.disabled)
&.light {
font-size: 1.6rem;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
font-weight: 400;
}
@@ -104,7 +104,7 @@ useContext(DisabledContext, () => props.disabled)
font-size: 1.4rem;
text-transform: uppercase;
font-weight: 700;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
}
}
@@ -126,7 +126,7 @@ useContext(DisabledContext, () => props.disabled)
align-items: center;
gap: 0.5rem;
text-decoration: none;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
& > span {
text-decoration: underline;
@@ -134,14 +134,14 @@ useContext(DisabledContext, () => props.disabled)
}
.warning {
color: var(--color-orange-world-base);
color: var(--color-orange-base);
}
.error {
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
.help {
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
}
</style>

View File

@@ -53,7 +53,7 @@ whenever(
<style lang="postcss" scoped>
.collapsible {
padding: 1rem 1.5rem;
background-color: var(--background-color-extra-blue);
background-color: var(--background-color-purple-10);
border-radius: 0.8rem;
}
@@ -67,16 +67,16 @@ whenever(
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
border: none;
border-bottom: 1px solid var(--color-extra-blue-base);
border-bottom: 1px solid var(--color-purple-base);
width: 100%;
font-size: 2rem;
font-weight: 500;
padding-bottom: 1rem;
.collapsible & {
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
padding-bottom: 0;
cursor: pointer;
}
@@ -87,6 +87,6 @@ whenever(
}
.collapse-icon {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
</style>

View File

@@ -65,7 +65,7 @@ const vmCount = computed(() => recordsByHostRef.value.get(props.hostOpaqueRef)?.
}
.master-icon {
color: var(--color-orange-world-base);
color: var(--color-orange-base);
}
.vm-count {
@@ -76,9 +76,9 @@ const vmCount = computed(() => recordsByHostRef.value.get(props.hostOpaqueRef)?.
justify-content: center;
width: var(--size);
height: var(--size);
color: var(--color-blue-scale-500);
color: var(--color-grey-600);
border-radius: calc(var(--size) / 2);
background-color: var(--color-extra-blue-base);
background-color: var(--color-purple-base);
--size: 2.3rem;
}
</style>

View File

@@ -23,6 +23,6 @@ const { records: hosts, isReady, hasError } = useHostCollection()
font-weight: 700;
font-size: 16px;
line-height: 150%;
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
</style>

View File

@@ -40,27 +40,27 @@ const hasTooltip = computed(() => hasEllipsis(textElement.value))
.infra-item-label {
display: flex;
align-items: stretch;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
border-radius: 0.8rem;
background-color: var(--background-color-primary);
&:hover {
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
background-color: var(--background-color-secondary);
}
&:active,
&.active {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
background-color: var(--background-color-primary);
}
&.exact-active {
color: var(--color-blue-scale-100);
background-color: var(--background-color-extra-blue);
color: var(--color-grey-100);
background-color: var(--background-color-purple-10);
.icon {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
}
}

View File

@@ -27,7 +27,7 @@ defineProps<{
}
.icon {
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
}
.link-placeholder {
@@ -41,7 +41,7 @@ defineProps<{
.loader {
flex: 1;
animation: pulse alternate 1s infinite;
background-color: var(--background-color-extra-blue);
background-color: var(--background-color-purple-10);
}
@keyframes pulse {

View File

@@ -43,6 +43,6 @@ const { isReady, hasError, pool } = usePoolCollection()
font-weight: 700;
font-size: 16px;
line-height: 150%;
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
</style>

View File

@@ -40,18 +40,18 @@ const { stop } = useIntersectionObserver(rootElement, ([entry]) => {
<style lang="postcss" scoped>
.infra-action {
color: var(--color-extra-blue-d60);
color: var(--color-purple-d60);
&.running {
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
&.paused {
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
}
&.suspended {
color: var(--color-extra-blue-d20);
color: var(--color-purple-d20);
}
}
</style>

View File

@@ -31,6 +31,6 @@ const vms = computed(() => recordsByHostRef.value.get(props.hostOpaqueRef ?? ('O
font-weight: 700;
font-size: 16px;
line-height: 150%;
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
</style>

View File

@@ -87,9 +87,9 @@ const open = (event: MouseEvent) => {
flex-direction: column;
padding: 0.5rem;
cursor: default;
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
border-radius: 0.8rem;
background-color: var(--color-blue-scale-500);
background-color: var(--color-grey-600);
gap: 0.5rem;
&.horizontal {

View File

@@ -72,7 +72,7 @@ const handleClick = async () => {
<style lang="postcss" scoped>
.menu-item {
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
}
.submenu-icon {

View File

@@ -16,11 +16,11 @@ const horizontal = inject(
.ui-menu-separator {
&.horizontal {
margin: 0 0.5rem;
border-right: 1px solid var(--color-blue-scale-400);
border-right: 1px solid var(--color-grey-500);
}
&:not(.horizontal) {
border-bottom: 1px solid var(--color-blue-scale-400);
border-bottom: 1px solid var(--color-grey-500);
}
}
</style>

View File

@@ -29,10 +29,10 @@ defineProps<{
white-space: nowrap;
border-radius: 0.8rem;
gap: 1rem;
background-color: var(--color-blue-scale-500);
background-color: var(--color-grey-600);
&.disabled {
color: var(--color-blue-scale-400);
color: var(--color-grey-500);
}
&:not(.disabled) {
@@ -44,8 +44,8 @@ defineProps<{
&:active,
&.active {
color: var(--color-extra-blue-base);
background-color: var(--background-color-extra-blue);
color: var(--color-purple-base);
background-color: var(--background-color-purple-10);
}
}
}

View File

@@ -70,7 +70,7 @@ const { records: alarms, start, isStarted, isReady, hasError } = useAlarmCollect
margin-bottom: 2rem;
}
.no-alarm & {
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
}
.table-container {

View File

@@ -83,12 +83,12 @@ const hasError = computed(() => hostStoreHasError.value || vmStoreHasError.value
.progress-item {
margin-top: 2.6rem;
--progress-bar-height: 1.2rem;
--progress-bar-color: var(--color-extra-blue-base);
--progress-bar-background-color: var(--color-blue-scale-400);
--progress-bar-color: var(--color-purple-base);
--progress-bar-background-color: var(--color-grey-500);
&.warning {
--progress-bar-color: var(--color-orange-world-base);
--footer-value-color: var(--color-orange-world-base);
--progress-bar-color: var(--color-orange-base);
--footer-value-color: var(--color-orange-base);
}
& .footer-value {

View File

@@ -31,7 +31,7 @@ const { count, patches, areSomeLoaded, areAllLoaded } = useHostPatches(hosts)
<style lang="postcss" scoped>
.patches-title {
--section-title-right-color: var(--color-red-vates-base);
--section-title-right-color: var(--color-red-base);
}
.table-container {

View File

@@ -52,10 +52,10 @@ const inactive = computed(() => props.total - props.active)
width: 1.3rem;
height: 1.3rem;
border-radius: 0.65rem;
background-color: var(--color-green-infra-base);
background-color: var(--color-green-base);
&.inactive {
background-color: var(--color-blue-scale-400);
background-color: var(--color-grey-500);
}
}

View File

@@ -43,7 +43,7 @@ const hasTooltip = computed(() => hasEllipsis(descriptionElement.value))
text-overflow: ellipsis;
}
.level {
color: var(--color-red-vates-base);
color: var(--color-red-base);
font-size: 1.4rem;
font-weight: 700;
}

View File

@@ -56,7 +56,7 @@ const hasTasks = computed(() => props.pendingTasks.length > 0 || (props.finished
.no-tasks {
text-align: center;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
font-style: italic;
}
@@ -68,11 +68,11 @@ td[colspan='5'] {
font-weight: 700;
font-size: 16px;
line-height: 150%;
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
.loader {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
display: block;
font-size: 4rem;
margin: 2rem auto 0;

View File

@@ -40,12 +40,12 @@ const isDisplayed = computed(() => !isNaN(percentUsed.value) && !isNaN(percentFr
justify-content: space-between;
font-weight: 700;
font-size: 14px;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
margin-top: 2rem;
}
.summary-card {
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
display: flex;
text-transform: uppercase;
}

View File

@@ -49,11 +49,11 @@ const isDisabled = useContext(DisabledContext, () => props.disabled)
background-color: var(--background-color-primary);
&.disabled {
color: var(--color-blue-scale-400);
color: var(--color-grey-500);
}
&:not(.disabled) {
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
&:hover {
background-color: var(--background-color-secondary);
@@ -62,8 +62,8 @@ const isDisabled = useContext(DisabledContext, () => props.disabled)
&:active,
&.active,
&.busy {
color: var(--color-extra-blue-base);
background-color: var(--background-color-extra-blue);
color: var(--color-purple-base);
background-color: var(--background-color-purple-10);
}
}
}

View File

@@ -24,8 +24,8 @@ defineProps<{
font-weight: 500;
padding: 0 0.8rem;
height: 1.8em;
color: var(--color-blue-scale-500);
color: var(--color-grey-600);
border-radius: 9.6rem;
background-color: var(--color-blue-scale-300);
background-color: var(--color-grey-300);
}
</style>

View File

@@ -86,27 +86,27 @@ const className = computed(() => {
}
&.color-info {
--button-accent-color: var(--color-extra-blue-base);
--button-accent-color-hover: var(--color-extra-blue-d20);
--button-accent-color-active: var(--color-extra-blue-d40);
--button-accent-color: var(--color-purple-base);
--button-accent-color-hover: var(--color-purple-d20);
--button-accent-color-active: var(--color-purple-d40);
}
&.color-success {
--button-accent-color: var(--color-green-infra-base);
--button-accent-color-hover: var(--color-green-infra-d20);
--button-accent-color-active: var(--color-green-infra-d40);
--button-accent-color: var(--color-green-base);
--button-accent-color-hover: var(--color-green-d20);
--button-accent-color-active: var(--color-green-d40);
}
&.color-warning {
--button-accent-color: var(--color-orange-world-base);
--button-accent-color-hover: var(--color-orange-world-d20);
--button-accent-color-active: var(--color-orange-world-d40);
--button-accent-color: var(--color-orange-base);
--button-accent-color-hover: var(--color-orange-d20);
--button-accent-color-active: var(--color-orange-d40);
}
&.color-error {
--button-accent-color: var(--color-red-vates-base);
--button-accent-color-hover: var(--color-red-vates-d20);
--button-accent-color-active: var(--color-red-vates-d40);
--button-accent-color: var(--color-red-base);
--button-accent-color-hover: var(--color-red-d20);
--button-accent-color-active: var(--color-red-d40);
}
&:hover {
@@ -119,7 +119,7 @@ const className = computed(() => {
--button-accent-color: var(--button-accent-color-active);
}
--button-color: var(--color-blue-scale-500);
--button-color: var(--color-grey-600);
--button-border-color: transparent;
--button-background-color: var(--button-accent-color);
@@ -141,7 +141,7 @@ const className = computed(() => {
&.disabled {
cursor: not-allowed;
--button-color: var(--color-blue-scale-400);
--button-color: var(--color-grey-500);
--button-border-color: transparent;
--button-background-color: var(--background-color-secondary);
}

View File

@@ -13,7 +13,7 @@
<style lang="postcss" scoped>
.footer {
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
display: flex;
font-size: 14px;
justify-content: space-between;

View File

@@ -17,7 +17,7 @@ import UiSpinner from '@/components/ui/UiSpinner.vue'
}
.spinner {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 4rem;
}
</style>

View File

@@ -44,28 +44,28 @@ const tags = computed(() => {
justify-content: space-between;
--section-title-left-size: 2rem;
--section-title-left-color: var(--color-blue-scale-100);
--section-title-left-color: var(--color-grey-100);
--section-title-left-weight: 500;
--section-title-right-size: 1.6rem;
--section-title-right-color: var(--color-extra-blue-base);
--section-title-right-color: var(--color-purple-base);
--section-title-right-weight: 700;
&.h6 {
margin-bottom: 1rem;
--section-title-left-size: 1.5rem;
--section-title-left-color: var(--color-blue-scale-300);
--section-title-left-color: var(--color-grey-300);
--section-title-left-weight: 400;
}
&.h5 {
margin-top: 2rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--color-extra-blue-base);
border-bottom: 1px solid var(--color-purple-base);
--section-title-left-size: 1.6rem;
--section-title-left-color: var(--color-extra-blue-base);
--section-title-left-color: var(--color-purple-base);
--section-title-left-weight: 700;
--section-title-right-size: 1.4rem;
--section-title-right-color: var(--color-extra-blue-base);
--section-title-right-color: var(--color-purple-base);
--section-title-right-weight: 400;
}
}

View File

@@ -23,9 +23,9 @@ defineProps<{
justify-content: center;
width: var(--size);
height: var(--size);
color: var(--color-blue-scale-500);
color: var(--color-grey-600);
border-radius: calc(var(--size) / 2);
background-color: var(--color-blue-scale-300);
background-color: var(--color-grey-300);
--size: 1.75em;
.overflow {
@@ -33,19 +33,19 @@ defineProps<{
}
&.info {
background-color: var(--color-extra-blue-base);
background-color: var(--color-purple-base);
}
&.success {
background-color: var(--color-green-infra-base);
background-color: var(--color-green-base);
}
&.warning {
background-color: var(--color-orange-world-base);
background-color: var(--color-orange-base);
}
&.error {
background-color: var(--color-red-vates-base);
background-color: var(--color-red-base);
}
}
</style>

View File

@@ -27,11 +27,11 @@ const emit = defineEmits<{
align-items: stretch;
justify-content: center;
height: 3.4rem;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
border-radius: 1.7rem;
background-color: var(--background-color-extra-blue);
background-color: var(--background-color-purple-10);
gap: 1rem;
border: 1px solid var(--color-extra-blue-base);
border: 1px solid var(--color-purple-base);
}
.label,
@@ -55,10 +55,10 @@ const emit = defineEmits<{
border-radius: 1.4rem;
width: 2.8rem;
margin: 0.2rem;
background-color: var(--color-extra-blue-l40);
background-color: var(--color-purple-l40);
&:hover {
background-color: var(--color-red-vates-l20);
background-color: var(--color-red-l20);
}
}
</style>

View File

@@ -12,8 +12,6 @@
<script lang="ts" setup></script>
<style lang="postcss" scoped>
@import '@/assets/_responsive.pcss';
.key,
.value {
padding-top: 0.5rem;
@@ -24,7 +22,7 @@
.key {
padding-right: 2rem;
text-align: left;
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
@media (--desktop) {
min-width: 20rem;

View File

@@ -6,7 +6,7 @@
<style lang="postcss" scoped>
.ui-raw {
background-color: var(--color-blue-scale-400);
background-color: var(--color-grey-500);
text-align: left;
overflow: auto;
max-width: 100%;

View File

@@ -5,7 +5,7 @@
<style lang="postcss" scoped>
.ui-separator {
border: none;
border-top: 1px solid var(--color-blue-scale-400);
border-top: 1px solid var(--color-grey-500);
margin: 2rem 0;
}
</style>

View File

@@ -24,7 +24,7 @@ defineProps<{
flex-direction: column;
align-items: center;
justify-content: center;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
}
.title {

View File

@@ -29,12 +29,12 @@ const isTabBarDisabled = useContext(DisabledContext, () => props.disabled)
padding: 0 1.5rem;
text-decoration: none;
text-transform: uppercase;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
border-bottom: 2px solid transparent;
&.disabled {
pointer-events: none;
color: var(--color-blue-scale-400);
color: var(--color-grey-500);
}
&:not(.disabled) {
@@ -42,19 +42,19 @@ const isTabBarDisabled = useContext(DisabledContext, () => props.disabled)
&:hover {
cursor: pointer;
border-bottom-color: var(--color-extra-blue-base);
border-bottom-color: var(--color-purple-base);
background-color: var(--background-color-secondary);
}
&:active {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
color: var(--color-purple-base);
border-bottom-color: var(--color-purple-base);
background-color: var(--background-color-secondary);
}
&.active {
color: var(--color-extra-blue-base);
border-bottom-color: var(--color-extra-blue-base);
color: var(--color-purple-base);
border-bottom-color: var(--color-purple-base);
background-color: var(--background-color-primary);
}
}

View File

@@ -23,7 +23,7 @@ useContext(DisabledContext, () => props.disabled)
display: flex;
align-items: stretch;
height: 5rem;
border-bottom: 1px solid var(--color-blue-scale-400);
border-bottom: 1px solid var(--color-grey-500);
background-color: var(--background-color-primary);
max-width: 100%;
overflow: auto;

View File

@@ -19,12 +19,12 @@ defineProps<{
font-weight: 400;
font-size: 1.4rem;
line-height: 2.4rem;
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
:deep(th),
:deep(td) {
padding: 1rem;
border-top: 1px solid var(--color-blue-scale-400);
border-top: 1px solid var(--color-grey-500);
text-align: left;
}
@@ -35,7 +35,7 @@ defineProps<{
:deep(thead) {
th,
td {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 1.4rem;
font-weight: 400;
text-transform: uppercase;
@@ -45,7 +45,7 @@ defineProps<{
&.vertical-border {
:deep(th),
:deep(td) {
border-right: 1px solid var(--color-blue-scale-400);
border-right: 1px solid var(--color-grey-500);
&:last-child {
border-right: none;
@@ -55,6 +55,6 @@ defineProps<{
}
.error {
background-color: var(--background-color-red-vates);
background-color: var(--background-color-red-10);
}
</style>

View File

@@ -25,7 +25,7 @@ const tag = computed(() => {
line-height: 150%;
align-self: stretch;
flex-grow: 0;
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
&.display {
font-size: 6.4rem;

View File

@@ -30,16 +30,16 @@ const icon = computed(() => {
color: var(--icon-color);
&.error {
--icon-color: var(--color-red-vates-base);
--icon-color: var(--color-red-base);
}
&.warning {
--icon-color: var(--color-orange-world-base);
--icon-color: var(--color-orange-base);
}
&.info {
--icon-color: var(--color-extra-blue-base);
--icon-color: var(--color-purple-base);
}
&.success {
--icon-color: var(--color-green-infra-base);
--icon-color: var(--color-green-base);
}
}
</style>

View File

@@ -72,6 +72,6 @@ defineSlots<{
.subtitle {
font-size: 1.6rem;
font-weight: 400;
color: var(--color-blue-scale-200);
color: var(--color-grey-200);
}
</style>

View File

@@ -29,22 +29,22 @@ const progressWithUnit = computed(() => {
height: var(--progress-bar-height, 0.4rem);
margin: 1rem 0;
border-radius: 0.4rem;
background-color: var(--progress-bar-background-color, var(--background-color-extra-blue));
background-color: var(--progress-bar-background-color, var(--background-color-purple-10));
&.color-info {
--progress-bar-color: var(--color-extra-blue-base);
--progress-bar-color: var(--color-purple-base);
}
&.color-success {
--progress-bar-color: var(--color-green-infra-base);
--progress-bar-color: var(--color-green-base);
}
&.color-warning {
--progress-bar-color: var(--color-orange-world-base);
--progress-bar-color: var(--color-orange-base);
}
&.color-error {
--progress-bar-color: var(--color-red-vates-base);
--progress-bar-color: var(--color-red-base);
}
}

View File

@@ -19,7 +19,7 @@ withDefaults(
<style lang="postcss">
.unit {
color: var(--color-blue-scale-300);
color: var(--color-grey-300);
display: flex;
font-size: 12px;
font-weight: 400;

View File

@@ -25,15 +25,15 @@ defineProps<{
}
.icon {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 3.2rem;
}
.separator {
height: 4.5rem;
width: 0;
border-left: 0.1rem solid var(--color-extra-blue-base);
background-color: var(--color-extra-blue-base);
border-left: 0.1rem solid var(--color-purple-base);
background-color: var(--color-purple-base);
margin: 0 1.5rem;
}

View File

@@ -133,6 +133,6 @@ const getHostState = (host: XenApiHost) => (isHostRunning(host) ? VM_POWER_STATE
.star {
margin: 0 1rem;
color: var(--color-orange-world-base);
color: var(--color-orange-base);
}
</style>

View File

@@ -53,7 +53,7 @@ const { isMobile } = storeToRefs(useUiStore())
<style lang="postcss" scoped>
.vms-actions-bar {
padding-bottom: 1rem;
border-bottom: 1px solid var(--color-blue-scale-400);
border-bottom: 1px solid var(--color-grey-500);
background-color: var(--background-color-primary);
}
</style>

View File

@@ -10,10 +10,10 @@ export const useChartTheme = () => {
const getColors = () => ({
background: style.getPropertyValue('--background-color-primary'),
text: style.getPropertyValue('--color-blue-scale-300'),
splitLine: style.getPropertyValue('--color-blue-scale-400'),
primary: style.getPropertyValue('--color-extra-blue-base'),
secondary: style.getPropertyValue('--color-orange-world-base'),
text: style.getPropertyValue('--color-grey-300'),
splitLine: style.getPropertyValue('--color-grey-500'),
primary: style.getPropertyValue('--color-purple-base'),
secondary: style.getPropertyValue('--color-orange-base'),
})
const colors = ref(getColors())

View File

@@ -3,6 +3,7 @@ import { createApp } from 'vue'
import App from '@/App.vue'
import i18n from '@/i18n'
import router from '@/router'
import '@xen-orchestra/web-core/assets/css/base.pcss'
const app = createApp(App)

View File

@@ -36,7 +36,7 @@ img {
}
.text {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 36px;
font-weight: 400;
line-height: 150%;

View File

@@ -30,7 +30,7 @@ img {
width: 30%;
}
.numeric {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 96px;
font-weight: 900;
letter-spacing: 1em;
@@ -40,7 +40,7 @@ img {
}
.text {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 36px;
font-weight: 400;
line-height: 150%;

View File

@@ -145,8 +145,6 @@ const { colorMode } = storeToRefs(useUiStore())
</script>
<style lang="postcss" scoped>
@import '@/assets/_responsive.pcss';
.card-view {
flex-direction: column;
}
@@ -176,9 +174,9 @@ h5 {
flex-direction: column;
gap: 1.6em;
&.selected {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
img {
outline: solid 2px var(--color-extra-blue-base);
outline: solid 2px var(--color-purple-base);
}
}
&:not(.selected) {

View File

@@ -236,7 +236,7 @@ watch(
.warning {
font-size: 1.6rem;
font-weight: 600;
color: var(--color-orange-world-base);
color: var(--color-orange-base);
}
.code-highlight {

View File

@@ -123,7 +123,7 @@ const openInNewTab = () => {
}
.spinner {
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
display: flex;
margin: auto;
width: 10rem;
@@ -143,7 +143,7 @@ const openInNewTab = () => {
flex-direction: column;
text-align: center;
gap: 4rem;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
font-size: 3.6rem;
}
@@ -157,8 +157,8 @@ const openInNewTab = () => {
display: flex;
align-items: center;
gap: 1rem;
background-color: var(--color-extra-blue-base);
color: var(--color-blue-scale-500);
background-color: var(--color-purple-base);
color: var(--color-grey-600);
text-decoration: none;
padding: 1.5rem;
font-size: 1.6rem;

View File

@@ -520,7 +520,7 @@ async function cancel() {
justify-content: center;
align-items: center;
min-height: 76.5vh;
color: var(--color-extra-blue-base);
color: var(--color-purple-base);
text-align: center;
padding: 5rem;
margin: auto;
@@ -536,15 +536,15 @@ async function cancel() {
font-size: 2rem;
}
.status {
color: var(--color-blue-scale-100);
color: var(--color-grey-100);
}
.success {
color: var(--color-green-infra-base);
color: var(--color-green-base);
}
.danger {
color: var(--color-red-vates-base);
color: var(--color-red-base);
}
.success,
.danger {
@@ -560,6 +560,6 @@ async function cancel() {
gap: 0.5em;
}
.warning {
color: var(--color-orange-world-base);
color: var(--color-orange-base);
}
</style>

View File

@@ -2,14 +2,13 @@
import fse from 'fs-extra'
import getopts from 'getopts'
import pRetry from 'promise-toolbox/retry'
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
import { create as createServer } from 'http-server-plus'
import { createCachedLookup } from '@vates/cached-dns.lookup'
import { createLogger } from '@xen-orchestra/log'
import { createSecureServer } from 'http2'
import { genSelfSignedCert } from '@xen-orchestra/self-signed'
import { load as loadConfig } from 'app-conf'
import { readCert } from '@xen-orchestra/self-signed/readCert'
// -------------------------------------------------------------------
@@ -79,36 +78,17 @@ ${APP_NAME} v${APP_VERSION}
}
}
niceAddress = await pRetry(
async () => {
try {
opts.cert = fse.readFileSync(cert)
opts.key = fse.readFileSync(key)
} catch (error) {
if (!(autoCert && error.code === 'ENOENT')) {
throw error
}
const pems = await genSelfSignedCert()
fse.outputFileSync(cert, pems.cert, { flag: 'wx', mode: 0o400 })
fse.outputFileSync(key, pems.key, { flag: 'wx', mode: 0o400 })
info('new certificate generated', { cert, key })
opts.cert = pems.cert
opts.key = pems.key
}
niceAddress = await readCert(cert, key, {
autoCert,
info,
warn,
use({ cert, key }) {
opts.cert = cert
opts.key = key
return httpServer.listen(opts)
},
{
tries: 2,
when: e => autoCert && e.code === 'ERR_SSL_EE_KEY_TOO_SMALL',
onRetry: () => {
warn('deleting invalid certificate')
fse.unlinkSync(cert)
fse.unlinkSync(key)
},
}
)
})
} else {
niceAddress = await httpServer.listen(opts)
}

View File

@@ -1,3 +1,7 @@
### `genSelfSigned()`
> Generate a self-signed cert/key pair with OpenSSL.
```js
import { genSelfSigned } from '@xen-orchestra/self-signed'
@@ -18,3 +22,41 @@ console.log(
// '-----END RSA PRIVATE KEY-----\n'
// }
```
### `readCert()`
> Reads a cert/key pair from the filesystem, if missing or invalid, generates a new one and write them to the filesystem.
```js
import { readCert } from '@xen-orchestra/self-signed/readCert'
const { cert, key } = await readCert('path/to/cert.pem', 'path/to/key.pem', {
// if false, do not generate a new one in case of error
autoCert: false,
// this function is called in case a new pair is generated
info: console.log,
// mode used when creating files or directories after generating a new pair
mode: 0o400,
// this function is called when there is a non fatal error (fatal errors are thrown)
warn: console.warn,
})
// unfortunately some cert/key issues are detected only when attempting to use them
//
// that's why you can pass a `use` function to `readCert` that will received the pair
// and in case some specific errors are thrown, it will trigger a new generation
await readCert('path/to/cert.pem', 'path/to/key.pem', {
autoCert: true,
async use({ cert, key }) {
const server = https.createServer({ cert, key })
await new Promise((resolve, reject) => {
server.once('error', reject).listen(443, resolve)
})
},
})
```

View File

@@ -16,6 +16,10 @@ npm install --save @xen-orchestra/self-signed
## Usage
### `genSelfSigned()`
> Generate a self-signed cert/key pair with OpenSSL.
```js
import { genSelfSigned } from '@xen-orchestra/self-signed'
@@ -37,6 +41,44 @@ console.log(
// }
```
### `readCert()`
> Reads a cert/key pair from the filesystem, if missing or invalid, generates a new one and write them to the filesystem.
```js
import { readCert } from '@xen-orchestra/self-signed/readCert'
const { cert, key } = await readCert('path/to/cert.pem', 'path/to/key.pem', {
// if false, do not generate a new one in case of error
autoCert: false,
// this function is called in case a new pair is generated
info: console.log,
// mode used when creating files or directories after generating a new pair
mode: 0o400,
// this function is called when there is a non fatal error (fatal errors are thrown)
warn: console.warn,
})
// unfortunately some cert/key issues are detected only when attempting to use them
//
// that's why you can pass a `use` function to `readCert` that will received the pair
// and in case some specific errors are thrown, it will trigger a new generation
await readCert('path/to/cert.pem', 'path/to/key.pem', {
autoCert: true,
async use({ cert, key }) {
const server = https.createServer({ cert, key })
await new Promise((resolve, reject) => {
server.once('error', reject).listen(443, resolve)
})
},
})
```
## Contributions
Contributions are _very_ welcomed, either on the documentation or on

View File

@@ -11,7 +11,7 @@
},
"version": "0.1.3",
"engines": {
"node": ">=8.10"
"node": ">=15.6"
},
"scripts": {
"postversion": "npm publish --access public"
@@ -20,5 +20,9 @@
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"exports": {
".": "./index.js",
"./readCert": "./readCert.js"
}
}

View File

@@ -0,0 +1,81 @@
'use strict'
const { dirname } = require('node:path')
const { mkdir, readFile, writeFile, unlink } = require('node:fs/promises')
const { X509Certificate } = require('node:crypto')
const { genSelfSignedCert } = require('./index.js')
const identity = value => value
const noop = Function.prototype
async function outputFile(path, content, mode) {
for (let attempt = 0; attempt < 5; ++attempt) {
try {
return await writeFile(path, content, { mode })
} catch (error) {
const { code } = error
if (code === 'ENOENT') {
await mkdir(dirname(path), { mode, recursive: true })
} else if (code === 'EACCES') {
await unlink(path)
} else {
throw error
}
}
}
}
exports.readCert = async function readCert(
certPath,
keyPath,
{
autoCert = false,
use = identity,
info = noop,
mode = 0o400,
warn = noop,
...opts
}
) {
let readingDone = false
try {
const cert = await readFile(certPath)
if (autoCert) {
const x509 = new X509Certificate(cert)
const now = Date.now()
if (now < Date.parse(x509.validFrom) || now > Date.parse(x509.validTo)) {
const e = new Error('certificate is not valid')
// same code used when attempting to connect to a server with an expired certificate
e.code = 'CERT_HAS_EXPIRED'
throw e
}
}
const key = await readFile(keyPath)
readingDone = true
return await use({ cert, key })
} catch (error) {
// only regen if a reading error or if the use error was ERR_SSL_EE_KEY_TOO_SMALL
if (!(autoCert && (!readingDone || error.code === 'ERR_SSL_EE_KEY_TOO_SMALL'))) {
throw error
}
warn(error)
const { cert, key } = await genSelfSignedCert(opts)
info('new certificate generated', { cert, key })
await Promise.all([outputFile(certPath, cert, mode).catch(warn), outputFile(keyPath, key, mode).catch(warn)])
return use({ cert, key })
}
}

View File

@@ -0,0 +1 @@
../../scripts/npmignore

Some files were not shown because too many files have changed in this diff Show More