mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
F OpenNebula/one#6684: Add firmware options fetching (#3198)
Signed-off-by: Victor Hansson <vhansson@opennebula.io>
This commit is contained in:
parent
3d527c3968
commit
6d60b43a04
@ -16,6 +16,7 @@
|
||||
import { string, boolean } from 'yup'
|
||||
|
||||
import { useGetHostsQuery } from 'client/features/OneApi/host'
|
||||
import { useGetVmmConfigQuery } from 'client/features/OneApi/system'
|
||||
import { getKvmMachines } from 'client/models/Host'
|
||||
import { Field, arrayToOptions } from 'client/utils'
|
||||
import {
|
||||
@ -24,7 +25,6 @@ import {
|
||||
CPU_ARCHITECTURES,
|
||||
SD_DISK_BUSES,
|
||||
FIRMWARE_TYPES,
|
||||
KVM_FIRMWARE_TYPES,
|
||||
HYPERVISORS,
|
||||
} from 'client/constants'
|
||||
|
||||
@ -143,10 +143,14 @@ export const FIRMWARE = {
|
||||
.default(() => undefined),
|
||||
dependOf: ['HYPERVISOR', '$general.HYPERVISOR'],
|
||||
values: ([templateHyperv, hypervisor = templateHyperv] = []) => {
|
||||
const types =
|
||||
{
|
||||
[kvm]: KVM_FIRMWARE_TYPES,
|
||||
}[hypervisor] ?? FIRMWARE_TYPES
|
||||
const configurableHypervisors = [kvm]
|
||||
const { data: { OVMF_UEFIS = '' } = {} } =
|
||||
configurableHypervisors?.includes(hypervisor) &&
|
||||
useGetVmmConfigQuery({ hypervisor })
|
||||
|
||||
const types = FIRMWARE_TYPES.concat(
|
||||
OVMF_UEFIS?.replace(/"/g, '')?.split(' ') ?? []
|
||||
)
|
||||
|
||||
return arrayToOptions(types)
|
||||
},
|
||||
|
@ -96,11 +96,6 @@ export const COMMON_RESOLUTIONS = {
|
||||
|
||||
export const FIRMWARE_TYPES = ['BIOS', 'EFI']
|
||||
|
||||
export const KVM_FIRMWARE_TYPES = FIRMWARE_TYPES.concat([
|
||||
'/usr/share/OVMF/OVMF_CODE.fd',
|
||||
'/usr/share/OVMF/OVMF_CODE.secboot.fd',
|
||||
])
|
||||
|
||||
export const PCI_TYPES = { MANUAL: 'pci_manual', AUTOMATIC: 'pci_automatic' }
|
||||
|
||||
export const VCENTER_FIRMWARE_TYPES = FIRMWARE_TYPES.concat(['uefi'])
|
||||
|
@ -14,6 +14,10 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
import { Actions, Commands } from 'server/utils/constants/commands/system'
|
||||
import {
|
||||
Actions as VmmActions,
|
||||
Commands as VmmCommands,
|
||||
} from 'server/routes/api/system/routes'
|
||||
import {
|
||||
Actions as SunstoneActions,
|
||||
Commands as SunstoneCommands,
|
||||
@ -117,6 +121,24 @@ const systemApi = oneApi.injectEndpoints({
|
||||
providesTags: [{ type: SYSTEM, id: 'sunstone-avalaibles-views' }],
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
|
||||
getVmmConfig: builder.query({
|
||||
/**
|
||||
* Returns the hypervisor VMM_EXEC config.
|
||||
*
|
||||
* @param {object} params - Request params
|
||||
* @returns {object} The set config options
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: (params) => {
|
||||
const name = VmmActions.VMM_CONFIG
|
||||
const command = { name, ...VmmCommands[name] }
|
||||
|
||||
return { params, command }
|
||||
},
|
||||
providesTags: [{ type: SYSTEM, id: 'vmm_config' }],
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@ -126,6 +148,8 @@ export const {
|
||||
useLazyGetOneVersionQuery,
|
||||
useGetOneConfigQuery,
|
||||
useLazyGetOneConfigQuery,
|
||||
useGetVmmConfigQuery,
|
||||
useLazyGetVmmConfigQuery,
|
||||
useGetSunstoneConfigQuery,
|
||||
useLazyGetSunstoneConfigQuery,
|
||||
useGetSunstoneViewsQuery,
|
||||
|
@ -18,7 +18,7 @@ const {
|
||||
getAllLogos,
|
||||
validateLogo,
|
||||
encodeLogo,
|
||||
} = require('server/routes/api/logo/utils')
|
||||
} = require('server/utils/logo')
|
||||
|
||||
const { defaults, httpCodes } = require('server/utils/constants')
|
||||
const { httpResponse } = require('server/utils/server')
|
||||
|
@ -1,149 +0,0 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2024, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
const { getSunstoneViewConfig } = require('server/utils/yml')
|
||||
const { existsSync, readdirSync } = require('fs')
|
||||
const path = require('path')
|
||||
const { global } = require('window-or-global')
|
||||
const Jimp = require('jimp')
|
||||
|
||||
/**
|
||||
* Retrieves the logo filename.
|
||||
*
|
||||
* @returns {string|null} The validated logo filename or null if the filename is invalid or not specified.
|
||||
*/
|
||||
const getLogo = () => {
|
||||
const config = getSunstoneViewConfig()
|
||||
const logo = config?.logo
|
||||
|
||||
const validFilenameRegex = /^[a-zA-Z0-9-_]+\.(jpg|jpeg|png|)$/
|
||||
|
||||
if (
|
||||
logo &&
|
||||
typeof logo === 'string' &&
|
||||
logo.trim() !== '' &&
|
||||
validFilenameRegex.test(logo)
|
||||
) {
|
||||
return { valid: true, filename: logo }
|
||||
}
|
||||
|
||||
return { valid: false, filename: null, ...(!logo ? { NOTSET: true } : {}) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the specified logo file path.
|
||||
*
|
||||
* @param {string} logo - The logo file name to validate.
|
||||
* @param {boolean} relativePaths - Return relative paths instead of absolute
|
||||
* @returns {string|boolean} Full logo path or false if invalid.
|
||||
*/
|
||||
const validateLogo = (logo, relativePaths = false) => {
|
||||
const imagesDirectory = global?.paths?.SUNSTONE_IMAGES
|
||||
|
||||
if (!logo || !imagesDirectory) {
|
||||
return { valid: false, path: null }
|
||||
}
|
||||
|
||||
const filePath = path.isAbsolute(logo)
|
||||
? logo
|
||||
: path.join(imagesDirectory, path.normalize(logo))
|
||||
|
||||
if (!filePath?.startsWith(imagesDirectory)) {
|
||||
return { valid: false, path: null }
|
||||
}
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
return { valid: false, path: 'Not found' }
|
||||
}
|
||||
|
||||
if (relativePaths) {
|
||||
const relativePath = path.relative(imagesDirectory, filePath)
|
||||
|
||||
return { valid: true, path: `images/logos/${relativePath}` }
|
||||
}
|
||||
|
||||
return { valid: true, path: filePath }
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an image file at a specified path into a base64 string.
|
||||
*
|
||||
* @param {string} filePath - The full path to the image file.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded image string.
|
||||
*/
|
||||
const encodeLogo = async (filePath) => {
|
||||
try {
|
||||
const image = await Jimp.read(filePath)
|
||||
const data = await image.getBufferAsync(Jimp.MIME_PNG)
|
||||
|
||||
return `data:image/png;base64,${data.toString('base64')}`
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes and encodes an image file to be used as a favicon.
|
||||
*
|
||||
* @param {string} filePath - The full path to the image file.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded image string suitable for favicon use.
|
||||
*/
|
||||
const encodeFavicon = async (filePath) => {
|
||||
try {
|
||||
const image = await Jimp.read(filePath)
|
||||
const resizedImage = await image.resize(32, 32)
|
||||
const data = await resizedImage.getBufferAsync(Jimp.MIME_PNG)
|
||||
|
||||
return `data:image/png;base64,${data.toString('base64')}`
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all logo files from the assets directory.
|
||||
*
|
||||
* @returns {object} A JSON object with filename as key and full path as value.
|
||||
*/
|
||||
const getAllLogos = () => {
|
||||
const imagesDirectory = global?.paths?.SUNSTONE_IMAGES
|
||||
if (!imagesDirectory || !existsSync(imagesDirectory)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const files = readdirSync(imagesDirectory)
|
||||
const validFilenameRegex = /^[a-zA-Z0-9-_]+\.(jpg|jpeg|png|)$/
|
||||
|
||||
const logos = files.reduce((acc, file) => {
|
||||
if (validFilenameRegex.test(file)) {
|
||||
acc[file.replace(/\.(jpg|jpeg|png)$/, '')] = path.join(
|
||||
imagesDirectory,
|
||||
file
|
||||
)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return logos
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLogo,
|
||||
getAllLogos,
|
||||
validateLogo,
|
||||
encodeLogo,
|
||||
encodeFavicon,
|
||||
}
|
@ -22,10 +22,12 @@ const {
|
||||
Actions: ActionSystem,
|
||||
} = require('server/utils/constants/commands/system')
|
||||
const { createTokenServerAdmin } = require('server/routes/api/auth/utils')
|
||||
const { getVmmConfig } = require('server/utils/vmm')
|
||||
|
||||
const { defaultEmptyFunction, httpMethod } = defaults
|
||||
const { ok, internalServerError, badRequest } = httpCodes
|
||||
const { ok, internalServerError, badRequest, notFound } = httpCodes
|
||||
const { GET } = httpMethod
|
||||
const { writeInLogger } = require('server/utils/logger')
|
||||
|
||||
const ALLOWED_KEYS_ONED_CONF = [
|
||||
'DEFAULT_COST',
|
||||
@ -112,6 +114,56 @@ const getConfig = (
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {object} res - http response
|
||||
* @param {Function} next - express stepper
|
||||
* @param {object} params - params of http request
|
||||
* @param {object} [params.hypervisor="kvm"] - fetch vmm_exec_[hypervisor].conf
|
||||
* @returns {void}
|
||||
*/
|
||||
const getVmmConfigHandler = async (
|
||||
res = {},
|
||||
next = defaultEmptyFunction,
|
||||
params = {}
|
||||
) => {
|
||||
try {
|
||||
const { hypervisor } = params
|
||||
const vmmConfig = (await getVmmConfig(hypervisor)) ?? {}
|
||||
|
||||
if (!vmmConfig) {
|
||||
res.locals.httpCode = httpResponse(
|
||||
notFound,
|
||||
'No vmm_exec config found',
|
||||
''
|
||||
)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
if (Object.keys(vmmConfig)?.length === 0) {
|
||||
res.locals.httpCode = httpResponse(
|
||||
notFound,
|
||||
'No valid vmm_exec config found',
|
||||
''
|
||||
)
|
||||
} else {
|
||||
res.locals.httpCode = httpResponse(ok, vmmConfig)
|
||||
}
|
||||
} catch (error) {
|
||||
const httpError = httpResponse(
|
||||
internalServerError,
|
||||
'Failed to load vmm_exec config',
|
||||
''
|
||||
)
|
||||
writeInLogger(httpError)
|
||||
res.locals.httpCode = httpError
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getConfig,
|
||||
getVmmConfigHandler,
|
||||
}
|
||||
|
@ -15,13 +15,20 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { Actions, Commands } = require('server/routes/api/system/routes')
|
||||
const { getConfig } = require('server/routes/api/system/functions')
|
||||
const {
|
||||
getConfig,
|
||||
getVmmConfigHandler,
|
||||
} = require('server/routes/api/system/functions')
|
||||
|
||||
const { SYSTEM_CONFIG } = Actions
|
||||
const { SYSTEM_CONFIG, VMM_CONFIG } = Actions
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
...Commands[SYSTEM_CONFIG],
|
||||
action: getConfig,
|
||||
},
|
||||
{
|
||||
...Commands[VMM_CONFIG],
|
||||
action: getVmmConfigHandler,
|
||||
},
|
||||
]
|
||||
|
@ -14,14 +14,19 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { httpMethod } = require('server/utils/constants/defaults')
|
||||
const {
|
||||
from: { query },
|
||||
httpMethod,
|
||||
} = require('../../../utils/constants/defaults')
|
||||
|
||||
const basepath = '/system'
|
||||
const { GET } = httpMethod
|
||||
|
||||
const SYSTEM_CONFIG = 'system.config'
|
||||
const VMM_CONFIG = 'vmm.config'
|
||||
const Actions = {
|
||||
SYSTEM_CONFIG,
|
||||
VMM_CONFIG,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -32,5 +37,16 @@ module.exports = {
|
||||
httpMethod: GET,
|
||||
auth: true,
|
||||
},
|
||||
[VMM_CONFIG]: {
|
||||
path: `${basepath}/vmmconfig`,
|
||||
httpMethod: GET,
|
||||
params: {
|
||||
hypervisor: {
|
||||
from: query,
|
||||
default: 'kvm',
|
||||
},
|
||||
},
|
||||
auth: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -13,11 +13,132 @@
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
const {
|
||||
getLogo,
|
||||
validateLogo,
|
||||
encodeFavicon,
|
||||
} = require('server/routes/api/logo/utils')
|
||||
const { getSunstoneViewConfig } = require('server/utils/yml')
|
||||
const { existsSync, readdirSync } = require('fs')
|
||||
const path = require('path')
|
||||
const { global } = require('window-or-global')
|
||||
const Jimp = require('jimp')
|
||||
|
||||
/**
|
||||
* Retrieves the logo filename.
|
||||
*
|
||||
* @returns {string|null} The validated logo filename or null if the filename is invalid or not specified.
|
||||
*/
|
||||
const getLogo = () => {
|
||||
const config = getSunstoneViewConfig()
|
||||
const logo = config?.logo
|
||||
|
||||
const validFilenameRegex = /^[a-zA-Z0-9-_]+\.(jpg|jpeg|png|)$/
|
||||
|
||||
if (
|
||||
logo &&
|
||||
typeof logo === 'string' &&
|
||||
logo.trim() !== '' &&
|
||||
validFilenameRegex.test(logo)
|
||||
) {
|
||||
return { valid: true, filename: logo }
|
||||
}
|
||||
|
||||
return { valid: false, filename: null, ...(!logo ? { NOTSET: true } : {}) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the specified logo file path.
|
||||
*
|
||||
* @param {string} logo - The logo file name to validate.
|
||||
* @param {boolean} relativePaths - Return relative paths instead of absolute
|
||||
* @returns {string|boolean} Full logo path or false if invalid.
|
||||
*/
|
||||
const validateLogo = (logo, relativePaths = false) => {
|
||||
const imagesDirectory = global?.paths?.SUNSTONE_IMAGES
|
||||
|
||||
if (!logo || !imagesDirectory) {
|
||||
return { valid: false, path: null }
|
||||
}
|
||||
|
||||
const filePath = path.isAbsolute(logo)
|
||||
? logo
|
||||
: path.join(imagesDirectory, path.normalize(logo))
|
||||
|
||||
if (!filePath?.startsWith(imagesDirectory)) {
|
||||
return { valid: false, path: null }
|
||||
}
|
||||
|
||||
if (!existsSync(filePath)) {
|
||||
return { valid: false, path: 'Not found' }
|
||||
}
|
||||
|
||||
if (relativePaths) {
|
||||
const relativePath = path.relative(imagesDirectory, filePath)
|
||||
|
||||
return { valid: true, path: `images/logos/${relativePath}` }
|
||||
}
|
||||
|
||||
return { valid: true, path: filePath }
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes an image file at a specified path into a base64 string.
|
||||
*
|
||||
* @param {string} filePath - The full path to the image file.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded image string.
|
||||
*/
|
||||
const encodeLogo = async (filePath) => {
|
||||
try {
|
||||
const image = await Jimp.read(filePath)
|
||||
const data = await image.getBufferAsync(Jimp.MIME_PNG)
|
||||
|
||||
return `data:image/png;base64,${data.toString('base64')}`
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes and encodes an image file to be used as a favicon.
|
||||
*
|
||||
* @param {string} filePath - The full path to the image file.
|
||||
* @returns {Promise<string>} A promise that resolves to the base64 encoded image string suitable for favicon use.
|
||||
*/
|
||||
const encodeFavicon = async (filePath) => {
|
||||
try {
|
||||
const image = await Jimp.read(filePath)
|
||||
const resizedImage = await image.resize(32, 32)
|
||||
const data = await resizedImage.getBufferAsync(Jimp.MIME_PNG)
|
||||
|
||||
return `data:image/png;base64,${data.toString('base64')}`
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all logo files from the assets directory.
|
||||
*
|
||||
* @returns {object} A JSON object with filename as key and full path as value.
|
||||
*/
|
||||
const getAllLogos = () => {
|
||||
const imagesDirectory = global?.paths?.SUNSTONE_IMAGES
|
||||
if (!imagesDirectory || !existsSync(imagesDirectory)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const files = readdirSync(imagesDirectory)
|
||||
const validFilenameRegex = /^[a-zA-Z0-9-_]+\.(jpg|jpeg|png|)$/
|
||||
|
||||
const logos = files.reduce((acc, file) => {
|
||||
if (validFilenameRegex.test(file)) {
|
||||
acc[file.replace(/\.(jpg|jpeg|png)$/, '')] = path.join(
|
||||
imagesDirectory,
|
||||
file
|
||||
)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return logos
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves, validates, and encodes a custom favicon image.
|
||||
@ -54,5 +175,10 @@ const getEncodedFavicon = async () => {
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getLogo,
|
||||
getAllLogos,
|
||||
validateLogo,
|
||||
encodeLogo,
|
||||
encodeFavicon,
|
||||
getEncodedFavicon,
|
||||
}
|
||||
|
@ -616,6 +616,9 @@ const genPathResources = () => {
|
||||
if (!global.paths.SUNSTONE_VIEWS) {
|
||||
global.paths.SUNSTONE_VIEWS = `${ETC_LOCATION}/${defaultSunstonePath}/${defaultSunstoneViews}`
|
||||
}
|
||||
if (!global.paths.VMM_EXEC_CONFIG) {
|
||||
global.paths.VMM_EXEC_CONFIG = `${ETC_LOCATION}/vmm_exec`
|
||||
}
|
||||
if (!global.paths.FIREEDGE_KEY_PATH) {
|
||||
global.paths.FIREEDGE_KEY_PATH = `${VAR_LOCATION}/.one/${defaultKeyFilename}`
|
||||
}
|
||||
|
109
src/fireedge/src/server/utils/vmm.js
Normal file
109
src/fireedge/src/server/utils/vmm.js
Normal file
@ -0,0 +1,109 @@
|
||||
/* ------------------------------------------------------------------------- *
|
||||
* Copyright 2002-2024, OpenNebula Project, OpenNebula Systems *
|
||||
* *
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
|
||||
* not use this file except in compliance with the License. You may obtain *
|
||||
* a copy of the License at *
|
||||
* *
|
||||
* http://www.apache.org/licenses/LICENSE-2.0 *
|
||||
* *
|
||||
* Unless required by applicable law or agreed to in writing, software *
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, *
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
|
||||
* See the License for the specific language governing permissions and *
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const { global } = require('window-or-global')
|
||||
|
||||
/**
|
||||
* Parse custom configuration file format.
|
||||
*
|
||||
* @param {string} fileContent - Content of the configuration file.
|
||||
* @returns {object} Parsed configuration object.
|
||||
*/
|
||||
const parseConfigFileContent = (fileContent) => {
|
||||
const lines = fileContent
|
||||
.split('\n')
|
||||
.filter((line) => line && !line.startsWith('#'))
|
||||
|
||||
const config = {}
|
||||
let inBlock = false
|
||||
let currentKey = ''
|
||||
let blockContent = []
|
||||
let blockEndMarker = ''
|
||||
|
||||
lines.forEach((line) => {
|
||||
if (!line) return
|
||||
|
||||
const trimLine = line?.trim()
|
||||
|
||||
if (!inBlock) {
|
||||
const [key, ...rest] = trimLine.split('=')
|
||||
const value = rest.join('=').trim()
|
||||
|
||||
if (key && /^[A-Za-z0-9_]+$/.test(key.trim())) {
|
||||
currentKey = key.trim()
|
||||
|
||||
if (value.startsWith('[') || value.startsWith('"')) {
|
||||
blockEndMarker = value.startsWith('[') ? ']' : '"'
|
||||
if (value.endsWith(blockEndMarker) && value.length > 1) {
|
||||
config[currentKey] = value
|
||||
} else {
|
||||
inBlock = true
|
||||
blockContent = [value]
|
||||
}
|
||||
} else {
|
||||
config[currentKey] = value
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blockContent.push(line)
|
||||
if (line.endsWith(blockEndMarker)) {
|
||||
config[currentKey] = blockContent.join('\n')
|
||||
inBlock = false
|
||||
currentKey = ''
|
||||
blockContent = []
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (inBlock && currentKey) {
|
||||
config[currentKey] = blockContent?.join('\n')
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a specific hypervisor.
|
||||
*
|
||||
* @param {string} hypervisor - The hypervisor type.
|
||||
* @returns {Promise<object>} Parsed configuration object.
|
||||
*/
|
||||
const getVmmConfig = async (hypervisor) => {
|
||||
const vmmExecConfigDirectory = global?.paths?.VMM_EXEC_CONFIG
|
||||
|
||||
const configFilePath = path.join(
|
||||
vmmExecConfigDirectory,
|
||||
`vmm_exec_${hypervisor}.conf`
|
||||
)
|
||||
|
||||
if (!(await fs.pathExists(configFilePath))) {
|
||||
throw new Error(`Configuration file not found: ${configFilePath}`)
|
||||
}
|
||||
|
||||
try {
|
||||
const fileContent = await fs.readFile(configFilePath, 'utf-8')
|
||||
const config = parseConfigFileContent(fileContent)
|
||||
|
||||
return config
|
||||
} catch (error) {
|
||||
throw new Error(`Error parsing config file: ${configFilePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getVmmConfig,
|
||||
}
|
@ -157,6 +157,7 @@ const getProvisionConfig = (options) =>
|
||||
getConfiguration(defaultApps.provision.name, options)
|
||||
|
||||
module.exports = {
|
||||
readYAMLFile,
|
||||
getFireedgeConfig,
|
||||
getSunstoneConfig,
|
||||
getSunstoneViewConfig,
|
||||
|
@ -24,6 +24,7 @@ define(function(require) {
|
||||
var Locale = require("utils/locale");
|
||||
var Tips = require("utils/tips");
|
||||
var WizardFields = require("utils/wizard-fields");
|
||||
var TemplateUtils = require('utils/template-utils');
|
||||
var FilesTable = require("tabs/files-tab/datatable");
|
||||
var UniqueId = require("utils/unique-id");
|
||||
var OpenNebulaHost = require("opennebula/host");
|
||||
@ -136,8 +137,6 @@ define(function(require) {
|
||||
var FIRMWARE_VALUES = {
|
||||
"BIOS": false,
|
||||
"EFI": false,
|
||||
"/usr/share/OVMF/OVMF_CODE.fd": false,
|
||||
"/usr/share/OVMF/OVMF_CODE.secboot.fd": true,
|
||||
"custom": true
|
||||
};
|
||||
|
||||
@ -322,7 +321,18 @@ define(function(require) {
|
||||
});
|
||||
that.initrdFilesTable.refreshResourceTableSelect();
|
||||
|
||||
$("#firmwareType", context).change(function() {
|
||||
var firmwareTypeSelect = $("#firmwareType", context);
|
||||
|
||||
TemplateUtils.fetchOvmfValues().done(function(response) {
|
||||
var ovmfUefis = response.ovmf_uefis;
|
||||
ovmfUefis.forEach(function(uefi) {
|
||||
firmwareTypeSelect.append('<option value="' + uefi + '" class="only_kvm">UEFI: ' + uefi + '</option>');
|
||||
});
|
||||
}).fail(function() {
|
||||
console.error('Failed to load UEFI options');
|
||||
});
|
||||
|
||||
firmwareTypeSelect.change(function() {
|
||||
if (FIRMWARE_VALUES[$(this).val()]){
|
||||
$("#firmwareSecure", context).show();
|
||||
}
|
||||
|
@ -114,8 +114,6 @@
|
||||
<select id="firmwareType" wizard_field="FIRMWARE">
|
||||
<option value="">{{tr "None"}}</option>
|
||||
<option value="BIOS">{{tr "BIOS"}}</option>
|
||||
<option value="/usr/share/OVMF/OVMF_CODE.fd" class="only_kvm">UEFI: /usr/share/OVMF/OVMF_CODE.fd</option>
|
||||
<option value="/usr/share/OVMF/OVMF_CODE.secboot.fd" class="only_kvm">UEFI: /usr/share/OVMF/OVMF_CODE.secboot.fd</option>
|
||||
<option value="custom" class="only_kvm">{{tr "Custom"}}</option>
|
||||
<option value="EFI" class="only_vcenter">{{tr "EFI"}}</option>
|
||||
</select>
|
||||
|
@ -181,13 +181,21 @@ define(function(require) {
|
||||
return rtn;
|
||||
}
|
||||
|
||||
function _fetchOvmfValues() {
|
||||
return $.ajax({
|
||||
url: '/ovmf_uefis',
|
||||
method: 'GET'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
"stringToTemplate": _convert_string_to_template,
|
||||
"templateToString": _convert_template_to_string,
|
||||
"htmlDecode": _htmlDecode,
|
||||
"htmlEncode": _htmlEncode,
|
||||
"escapeDoubleQuotes": _escapeDoubleQuotes,
|
||||
"removeHTMLTags": _removeHTMLTags
|
||||
"removeHTMLTags": _removeHTMLTags,
|
||||
"fetchOvmfValues": _fetchOvmfValues,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -38,6 +38,7 @@ else
|
||||
end
|
||||
|
||||
VMS_LOCATION = VAR_LOCATION + "/vms"
|
||||
VMM_EXEC_CONF = ETC_LOCATION + "/vmm_exec/vmm_exec_kvm.conf"
|
||||
|
||||
SUNSTONE_AUTH = VAR_LOCATION + '/.one/sunstone_auth'
|
||||
SUNSTONE_LOG = LOG_LOCATION + '/sunstone.log'
|
||||
@ -356,6 +357,23 @@ helpers do
|
||||
session[:csrftoken] && session[:csrftoken] == csrftoken
|
||||
end
|
||||
|
||||
def get_ovmf_uefis
|
||||
ovmf_uefis = []
|
||||
|
||||
if File.exist?(VMM_EXEC_CONF)
|
||||
File.foreach(VMM_EXEC_CONF) do |line|
|
||||
if line =~ /^OVMF_UEFIS\s*=\s*"(.+)"$/
|
||||
ovmf_uefis = $1.split(" ")
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
logger.error("Configuration file not found: #{VMM_EXEC_CONF}")
|
||||
end
|
||||
|
||||
ovmf_uefis
|
||||
end
|
||||
|
||||
def authorized?
|
||||
session[:ip] && session[:ip] == request.ip
|
||||
end
|
||||
@ -862,6 +880,14 @@ get '/version' do
|
||||
[200, version.to_json]
|
||||
end
|
||||
|
||||
get '/ovmf_uefis' do
|
||||
content_type 'application/json', :charset => 'utf-8'
|
||||
ovmf_uefis = {}
|
||||
ovmf_uefis["ovmf_uefis"] = get_ovmf_uefis
|
||||
[200, ovmf_uefis.to_json]
|
||||
end
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Login
|
||||
##############################################################################
|
||||
|
Loading…
x
Reference in New Issue
Block a user