mirror of
https://github.com/OpenNebula/one.git
synced 2025-03-16 22:50:10 +03:00
F OpenNebula/one#6652: Add custom template logos (#3188)
Signed-off-by: Victor Hansson <vhansson@opennebula.io>
This commit is contained in:
parent
0b73b5e416
commit
015c735b71
@ -130,7 +130,13 @@ const General = ({
|
||||
},
|
||||
optionsValidate: { abortEarly: false },
|
||||
content: (props) =>
|
||||
Content({ ...props, isUpdate, oneConfig, adminGroup, isVrouter }),
|
||||
Content({
|
||||
...props,
|
||||
isUpdate,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
isVrouter,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
import { string, boolean } from 'yup'
|
||||
|
||||
import Image from 'client/components/Image'
|
||||
import { useGetTemplateLogosQuery } from 'client/features/OneApi/logo'
|
||||
import { Field, arrayToOptions } from 'client/utils'
|
||||
import {
|
||||
T,
|
||||
@ -23,7 +24,6 @@ import {
|
||||
INPUT_TYPES,
|
||||
HYPERVISORS,
|
||||
DEFAULT_TEMPLATE_LOGO,
|
||||
TEMPLATE_LOGOS,
|
||||
} from 'client/constants'
|
||||
|
||||
/**
|
||||
@ -77,14 +77,18 @@ export const LOGO = {
|
||||
label: T.Logo,
|
||||
type: INPUT_TYPES.AUTOCOMPLETE,
|
||||
optionsOnly: true,
|
||||
values: arrayToOptions(
|
||||
[['-', DEFAULT_TEMPLATE_LOGO], ...Object.entries(TEMPLATE_LOGOS)],
|
||||
{
|
||||
addEmpty: false,
|
||||
getText: ([name]) => name,
|
||||
getValue: ([, logo]) => logo,
|
||||
}
|
||||
),
|
||||
values: () => {
|
||||
const { data: logos } = useGetTemplateLogosQuery()
|
||||
|
||||
return arrayToOptions(
|
||||
[['-', DEFAULT_TEMPLATE_LOGO], ...Object.entries(logos || {})],
|
||||
{
|
||||
addEmpty: false,
|
||||
getText: ([name]) => name,
|
||||
getValue: ([, logo]) => logo,
|
||||
}
|
||||
)
|
||||
},
|
||||
renderValue: (value) => (
|
||||
<Image
|
||||
alt="logo"
|
||||
|
@ -44,9 +44,17 @@ import { T, HYPERVISORS, VmTemplateFeatures } from 'client/constants'
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @param {boolean} isVrouter - VRouter template
|
||||
* @returns {Section[]} Fields
|
||||
*/
|
||||
const SECTIONS = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
|
||||
const SECTIONS = (
|
||||
hypervisor,
|
||||
isUpdate,
|
||||
features,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
isVrouter
|
||||
) =>
|
||||
[
|
||||
{
|
||||
id: 'hypervisor',
|
||||
@ -134,11 +142,19 @@ const SECTIONS = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
|
||||
* @param {VmTemplateFeatures} [features] - Features
|
||||
* @param {object} oneConfig - Config of oned.conf
|
||||
* @param {boolean} adminGroup - User is admin or not
|
||||
* @param {boolean} isVrouter - VRouter template
|
||||
* @returns {BaseSchema} Step schema
|
||||
*/
|
||||
const SCHEMA = (hypervisor, isUpdate, features, oneConfig, adminGroup) =>
|
||||
const SCHEMA = (
|
||||
hypervisor,
|
||||
isUpdate,
|
||||
features,
|
||||
oneConfig,
|
||||
adminGroup,
|
||||
isVrouter
|
||||
) =>
|
||||
getObjectSchemaFromFields(
|
||||
SECTIONS(hypervisor, isUpdate, features)
|
||||
SECTIONS(hypervisor, isUpdate, features, oneConfig, adminGroup, isVrouter)
|
||||
.map(({ fields }) => fields)
|
||||
.flat()
|
||||
)
|
||||
|
@ -107,26 +107,6 @@ export const VCENTER_FIRMWARE_TYPES = FIRMWARE_TYPES.concat(['uefi'])
|
||||
|
||||
export const DEFAULT_TEMPLATE_LOGO = 'images/logos/default.png'
|
||||
|
||||
export const TEMPLATE_LOGOS = {
|
||||
'Alpine Linux': 'images/logos/alpine.png',
|
||||
ALT: 'images/logos/alt.png',
|
||||
Arch: 'images/logos/arch.png',
|
||||
CentOS: 'images/logos/centos.png',
|
||||
Debian: 'images/logos/debian.png',
|
||||
Devuan: 'images/logos/devuan.png',
|
||||
Fedora: 'images/logos/fedora.png',
|
||||
FreeBSD: 'images/logos/freebsd.png',
|
||||
HardenedBSD: 'images/logos/hardenedbsd.png',
|
||||
Knoppix: 'images/logos/knoppix.png',
|
||||
Linux: 'images/logos/linux.png',
|
||||
Oracle: 'images/logos/oracle.png',
|
||||
RedHat: 'images/logos/redhat.png',
|
||||
Suse: 'images/logos/suse.png',
|
||||
Ubuntu: 'images/logos/ubuntu.png',
|
||||
'Windows xp': 'images/logos/windowsxp.png',
|
||||
'Windows 10': 'images/logos/windows8.png',
|
||||
}
|
||||
|
||||
/** @enum {string} FS freeze options type */
|
||||
export const FS_FREEZE_OPTIONS = {
|
||||
[T.None]: 'NONE',
|
||||
|
@ -33,6 +33,29 @@ const logoApi = oneApi.injectEndpoints({
|
||||
providesTags: (tags) => [{ type: 'LOGO', id: tags?.logoName }],
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
|
||||
getTemplateLogos: builder.query({
|
||||
/**
|
||||
* @returns {object} JSON struct of logo names and paths
|
||||
* @throws Fails when response isn't code 200
|
||||
*/
|
||||
query: () => {
|
||||
const name = Actions.GET_TEMPLATE_LOGOS
|
||||
const command = { name, ...Commands[name] }
|
||||
|
||||
return { command }
|
||||
},
|
||||
providesTags: (tags) => {
|
||||
const logos = Object.keys(tags).reduce((acc, logo) => {
|
||||
acc.push({ type: 'LOGO', id: logo })
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
return logos
|
||||
},
|
||||
keepUnusedDataFor: 600,
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
@ -40,6 +63,8 @@ export const {
|
||||
// Queries
|
||||
useGetEncodedLogoQuery,
|
||||
useLazyGetEncodedLogoQuery,
|
||||
useGetTemplateLogosQuery,
|
||||
useLazyGetTemplateLogosQuery,
|
||||
} = logoApi
|
||||
|
||||
export default logoApi
|
||||
|
@ -15,6 +15,7 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
const {
|
||||
getLogo,
|
||||
getAllLogos,
|
||||
validateLogo,
|
||||
encodeLogo,
|
||||
} = require('server/routes/api/logo/utils')
|
||||
@ -83,6 +84,50 @@ const getEncodedLogo = async (res = {}, next = defaultEmptyFunction) => {
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware to get and send all logos with their paths.
|
||||
*
|
||||
* @param {object} res - The response object.
|
||||
* @param {Function} next - The next middleware function.
|
||||
* @returns {void}
|
||||
*/
|
||||
const getAllLogosHandler = async (res = {}, next = defaultEmptyFunction) => {
|
||||
try {
|
||||
const logos = getAllLogos() ?? {}
|
||||
|
||||
if (!logos) {
|
||||
res.locals.httpCode = httpResponse(notFound, 'No logos found', '')
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
const validLogos = {}
|
||||
for (const [name, filePath] of Object?.entries(logos)) {
|
||||
const validate = validateLogo(filePath, true)
|
||||
if (validate.valid) {
|
||||
validLogos[name] = validate.path
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(validLogos)?.length === 0) {
|
||||
res.locals.httpCode = httpResponse(notFound, 'No valid logos found', '')
|
||||
} else {
|
||||
res.locals.httpCode = httpResponse(ok, validLogos)
|
||||
}
|
||||
} catch (error) {
|
||||
const httpError = httpResponse(
|
||||
internalServerError,
|
||||
'Failed to load logos',
|
||||
''
|
||||
)
|
||||
writeInLogger(httpError)
|
||||
res.locals.httpCode = httpError
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEncodedLogo,
|
||||
getAllLogosHandler,
|
||||
}
|
||||
|
@ -15,12 +15,19 @@
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
const { Actions, Commands } = require('server/routes/api/logo/routes')
|
||||
const { getEncodedLogo } = require('server/routes/api/logo/functions')
|
||||
const { GET_LOGO } = Actions
|
||||
const {
|
||||
getEncodedLogo,
|
||||
getAllLogosHandler,
|
||||
} = require('server/routes/api/logo/functions')
|
||||
const { GET_LOGO, GET_TEMPLATE_LOGOS } = Actions
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
...Commands[GET_LOGO],
|
||||
action: getEncodedLogo,
|
||||
},
|
||||
{
|
||||
...Commands[GET_TEMPLATE_LOGOS],
|
||||
action: getAllLogosHandler,
|
||||
},
|
||||
]
|
||||
|
@ -19,10 +19,12 @@ const { httpMethod } = require('../../../utils/constants/defaults')
|
||||
const { GET } = httpMethod
|
||||
|
||||
const basepath = '/logo'
|
||||
const GET_LOGO = 'get.logo'
|
||||
const GET_LOGO = 'logo.brand'
|
||||
const GET_TEMPLATE_LOGOS = 'logo.templates'
|
||||
|
||||
const Actions = {
|
||||
GET_LOGO,
|
||||
GET_TEMPLATE_LOGOS,
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -33,5 +35,10 @@ module.exports = {
|
||||
httpMethod: GET,
|
||||
auth: false,
|
||||
},
|
||||
[GET_TEMPLATE_LOGOS]: {
|
||||
path: `${basepath}/templatelogos`,
|
||||
httpMethod: GET,
|
||||
auth: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License. *
|
||||
* ------------------------------------------------------------------------- */
|
||||
const { getSunstoneViewConfig } = require('server/utils/yml')
|
||||
const { existsSync } = require('fs')
|
||||
const { existsSync, readdirSync } = require('fs')
|
||||
const path = require('path')
|
||||
const { global } = require('window-or-global')
|
||||
const Jimp = require('jimp')
|
||||
@ -46,16 +46,19 @@ const getLogo = () => {
|
||||
* 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) => {
|
||||
const validateLogo = (logo, relativePaths = false) => {
|
||||
const imagesDirectory = global?.paths?.SUNSTONE_IMAGES
|
||||
|
||||
if (!logo || !imagesDirectory) {
|
||||
return { valid: false, path: null }
|
||||
}
|
||||
|
||||
const filePath = path.join(imagesDirectory, path.normalize(logo))
|
||||
const filePath = path.isAbsolute(logo)
|
||||
? logo
|
||||
: path.join(imagesDirectory, path.normalize(logo))
|
||||
|
||||
if (!filePath?.startsWith(imagesDirectory)) {
|
||||
return { valid: false, path: null }
|
||||
@ -65,6 +68,12 @@ const validateLogo = (logo) => {
|
||||
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 }
|
||||
}
|
||||
|
||||
@ -103,4 +112,38 @@ const encodeFavicon = async (filePath) => {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { getLogo, validateLogo, encodeLogo, encodeFavicon }
|
||||
/**
|
||||
* 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,
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user