feat: support default image (#630)

This commit is contained in:
Eli Bosley
2023-05-01 13:01:32 -04:00
committed by GitHub
parent 0a9b047170
commit f9d656fc0d
4 changed files with 166 additions and 44 deletions

View File

@@ -0,0 +1,23 @@
import { getBannerPathIfPresent, getCasePathIfPresent } from "@app/core/utils/images/image-file-helpers";
import { store } from "@app/store/index";
import { loadDynamixConfigFile } from "@app/store/modules/dynamix";
import { expect, test } from "vitest";
test('get case path returns expected result', () => {
expect(getCasePathIfPresent()).resolves.toContain('/dev/dynamix/case-model.png')
})
test('get banner path returns null (state unloaded)', () => {
expect(getBannerPathIfPresent()).resolves.toMatchInlineSnapshot('null')
})
test('get banner path returns the banner (state loaded)', async() => {
await store.dispatch(loadDynamixConfigFile()).unwrap();
expect(getBannerPathIfPresent()).resolves.toContain('/dev/dynamix/banner.png');
})
test('get banner path returns null when no banner (state loaded)', async () => {
await store.dispatch(loadDynamixConfigFile()).unwrap();
expect(getBannerPathIfPresent('notabanner.png')).resolves.toMatchInlineSnapshot('null');
});

View File

@@ -0,0 +1,53 @@
import { getters } from '@app/store/index';
import { FileLoadStatus } from '@app/store/types';
import { readFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
const isImageFile = async (path: string): Promise<boolean> => {
try {
const stats = await stat(path);
if (stats.size < 25) {
return false;
}
return true;
} catch (error: unknown) {
return false;
}
};
export const getCasePathIfPresent = async (): Promise<string | null> => {
const dynamixBasePath = getters.paths()['dynamix-base'];
const configFilePath = join(dynamixBasePath, 'case-model.cfg');
const caseImagePath = join(dynamixBasePath, 'case-model.png');
try {
const caseConfig = await readFile(configFilePath, 'utf-8');
if (caseConfig.includes('.') && (await isImageFile(caseImagePath))) {
return caseImagePath;
}
return null;
} catch (error: unknown) {
return null;
}
};
export const getBannerPathIfPresent = async (
filename = 'banner.png'
): Promise<string | null> => {
if (getters.dynamix().status === FileLoadStatus.LOADED && getters.dynamix().display?.banner) {
const dynamixBasePath = getters.paths()['dynamix-base'];
const customBannerPath = join(dynamixBasePath, filename);
const defaultBannerPath = '/usr/local/emhttp/plugins/dynamix/images/banner.png';
if (await isImageFile(customBannerPath)) {
return customBannerPath;
}
if (await isImageFile(defaultBannerPath)) {
return defaultBannerPath;
}
}
return null;
};

View File

@@ -0,0 +1,30 @@
import { getBannerPathIfPresent, getCasePathIfPresent } from "@app/core/utils/images/image-file-helpers";
import { apiKeyToUser } from "@app/graphql/index";
import { type Request, type Response } from "express";
export const getImages = async (req: Request, res: Response) => {
// @TODO - Clean up this function
const apiKey = req.headers['x-api-key'];
if (
apiKey &&
typeof apiKey === 'string' &&
(await apiKeyToUser(apiKey)).role !== 'guest'
) {
if (req.params.type === 'banner') {
const path = await getBannerPathIfPresent();
if (path) {
res.sendFile(path);
return;
}
} else if (req.params.type === 'case') {
const path = await getCasePathIfPresent();
if (path) {
res.sendFile(path);
return;
}
}
return res.status(404).send('no customization of this type found');
}
return res.status(403).send('unauthorized');
};

View File

@@ -9,11 +9,11 @@ import { toBoolean } from '@app/core/utils/casting';
import { type DynamixConfig } from '@app/core/types/ini';
export type SliceState = {
status: FileLoadStatus;
status: FileLoadStatus;
} & DynamixConfig;
export const initialState: Partial<SliceState> = {
status: FileLoadStatus.UNLOADED,
status: FileLoadStatus.UNLOADED,
};
/**
@@ -21,53 +21,69 @@ export const initialState: Partial<SliceState> = {
*
* Note: If the file doesn't exist this will fallback to default values.
*/
export const loadDynamixConfigFile = createAsyncThunk<RecursiveNullable<RecursivePartial<DynamixConfig>>, string | undefined>('config/load-dynamix-config-file', async filePath => {
const store = await import('@app/store');
const paths = store.getters.paths();
const path = filePath ?? paths['dynamix-config'];
const fileExists = await access(path, F_OK).then(() => true).catch(() => false);
const file = fileExists ? parseConfig<RecursivePartial<DynamixConfig>>({
filePath: path,
type: 'ini',
}) : {};
const { display } = file;
return merge(file, {
...(display?.scale ? { scale: toBoolean(display?.scale) } : {}),
...(display?.tabs ? { tabs: toBoolean(display?.tabs) } : {}),
...(display?.resize ? { resize: toBoolean(display?.resize) } : {}),
...(display?.wwn ? { wwn: toBoolean(display?.wwn) } : {}),
...(display?.total ? { total: toBoolean(display?.total) } : {}),
...(display?.usage ? { usage: toBoolean(display?.usage) } : {}),
...(display?.text ? { text: toBoolean(display?.text) } : {}),
...(display?.warning ? { warning: Number.parseInt(display?.warning, 10) } : {}),
...(display?.critical ? { critical: Number.parseInt(display?.critical, 10) } : {}),
...(display?.hot ? { hot: Number.parseInt(display?.hot, 10) } : {}),
...(display?.max ? { max: Number.parseInt(display?.max, 10) } : {}),
locale: display?.locale ?? 'en_US',
}) as RecursivePartial<DynamixConfig>;
export const loadDynamixConfigFile = createAsyncThunk<
RecursiveNullable<RecursivePartial<DynamixConfig>>,
string | undefined
>('config/load-dynamix-config-file', async (filePath) => {
const store = await import('@app/store');
const paths = store.getters.paths();
const path = filePath ?? paths['dynamix-config'];
const fileExists = await access(path, F_OK)
.then(() => true)
.catch(() => false);
const file = fileExists
? parseConfig<RecursivePartial<DynamixConfig>>({
filePath: path,
type: 'ini',
})
: {};
const { display } = file;
return merge(file, {
...(display?.scale ? { scale: toBoolean(display?.scale) } : {}),
...(display?.tabs ? { tabs: toBoolean(display?.tabs) } : {}),
...(display?.resize ? { resize: toBoolean(display?.resize) } : {}),
...(display?.wwn ? { wwn: toBoolean(display?.wwn) } : {}),
...(display?.total ? { total: toBoolean(display?.total) } : {}),
...(display?.usage ? { usage: toBoolean(display?.usage) } : {}),
...(display?.text ? { text: toBoolean(display?.text) } : {}),
...(display?.warning
? { warning: Number.parseInt(display?.warning, 10) }
: {}),
...(display?.critical
? { critical: Number.parseInt(display?.critical, 10) }
: {}),
...(display?.hot ? { hot: Number.parseInt(display?.hot, 10) } : {}),
...(display?.max ? { max: Number.parseInt(display?.max, 10) } : {}),
locale: display?.locale ?? 'en_US',
}) as RecursivePartial<DynamixConfig>;
});
export const dynamix = createSlice({
name: 'dynamix',
initialState,
reducers: {
updateDynamixConfig(state, action: PayloadAction<RecursivePartial<SliceState>>) {
return merge(state, action.payload);
},
},
extraReducers(builder) {
builder.addCase(loadDynamixConfigFile.pending, (state, _action) => {
state.status = FileLoadStatus.LOADING;
});
name: 'dynamix',
initialState,
reducers: {
updateDynamixConfig(
state,
action: PayloadAction<RecursivePartial<SliceState>>
) {
return merge(state, action.payload);
},
},
extraReducers(builder) {
builder.addCase(loadDynamixConfigFile.pending, (state) => {
state.status = FileLoadStatus.LOADING;
});
builder.addCase(loadDynamixConfigFile.fulfilled, (state, action) => {
merge(state, action.payload, { status: FileLoadStatus.LOADED });
});
builder.addCase(loadDynamixConfigFile.fulfilled, (state, action) => {
merge(state, action.payload, { status: FileLoadStatus.LOADED });
});
builder.addCase(loadDynamixConfigFile.rejected, (state, action) => {
merge(state, action.payload, { status: FileLoadStatus.FAILED_LOADING });
});
},
builder.addCase(loadDynamixConfigFile.rejected, (state, action) => {
merge(state, action.payload, {
status: FileLoadStatus.FAILED_LOADING,
});
});
},
});
export const { updateDynamixConfig } = dynamix.actions;