Skip to content

Commit

Permalink
F #6652: Add custom template logos (#3188)
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Hansson <vhansson@opennebula.io>
  • Loading branch information
vichansson authored Aug 2, 2024
1 parent 0b73b5e commit 015c735
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,13 @@ const General = ({
},
optionsValidate: { abortEarly: false },
content: (props) =>
Content({ ...props, isUpdate, oneConfig, adminGroup, isVrouter }),
Content({
...props,
isUpdate,
oneConfig,
adminGroup,
isVrouter,
}),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
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,
STATIC_FILES_URL,
INPUT_TYPES,
HYPERVISORS,
DEFAULT_TEMPLATE_LOGO,
TEMPLATE_LOGOS,
} from 'client/constants'

/**
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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()
)
Expand Down
20 changes: 0 additions & 20 deletions src/fireedge/src/client/constants/vmTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
25 changes: 25 additions & 0 deletions src/fireedge/src/client/features/OneApi/logo.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,38 @@ 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,
}),
}),
})

export const {
// Queries
useGetEncodedLogoQuery,
useLazyGetEncodedLogoQuery,
useGetTemplateLogosQuery,
useLazyGetTemplateLogosQuery,
} = logoApi

export default logoApi
45 changes: 45 additions & 0 deletions src/fireedge/src/server/routes/api/logo/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* ------------------------------------------------------------------------- */
const {
getLogo,
getAllLogos,
validateLogo,
encodeLogo,
} = require('server/routes/api/logo/utils')
Expand Down Expand Up @@ -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,
}
11 changes: 9 additions & 2 deletions src/fireedge/src/server/routes/api/logo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
]
9 changes: 8 additions & 1 deletion src/fireedge/src/server/routes/api/logo/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -33,5 +35,10 @@ module.exports = {
httpMethod: GET,
auth: false,
},
[GET_TEMPLATE_LOGOS]: {
path: `${basepath}/templatelogos`,
httpMethod: GET,
auth: false,
},
},
}
51 changes: 47 additions & 4 deletions src/fireedge/src/server/routes/api/logo/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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 }
Expand All @@ -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 }
}

Expand Down Expand Up @@ -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,
}

0 comments on commit 015c735

Please sign in to comment.