Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/generated-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -939,6 +939,11 @@ type BrandingConfig {
"""
bannerImage: String

"""
Built-in case model value written to case-model.cfg when no custom override is supplied.
"""
caseModel: String

"""
Case model image source. Supports local path, remote URL, or data URI/base64.
"""
Expand Down Expand Up @@ -1425,6 +1430,7 @@ input BrandingConfigInput {
showBannerGradient: Boolean
theme: String
bannerImage: String
caseModel: String
caseModelImage: String
partnerLogoLightUrl: String
partnerLogoDarkUrl: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ export class BrandingConfig {
@Transform(({ value }) => sanitizeString(value))
bannerImage?: string | null;

@Field(() => String, {
nullable: true,
description:
'Built-in case model value written to case-model.cfg when no custom override is supplied.',
})
@IsOptional()
@IsString()
@Transform(({ value }) => sanitizeString(value))
caseModel?: string | null;

@Field(() => String, {
nullable: true,
description: 'Case model image source. Supports local path, remote URL, or data URI/base64.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,27 @@ describe('OnboardingService', () => {
);
});

it('applyCaseModelConfig should write the built-in case model when no custom asset exists', async () => {
(service as any).activationData = plainToInstance(ActivationCode, {
...mockActivationData,
branding: {
...mockActivationData.branding,
caseModel: 'mid-tower',
caseModelImage: null,
},
});
(service as any).materializedPartnerMedia = {
banner: true,
caseModel: false,
};
vi.mocked(fileExists).mockResolvedValue(false);

await (service as any).applyCaseModelConfig();

expect(fs.writeFile).toHaveBeenCalledWith(caseModelCfg, 'mid-tower');
expect(loggerLogSpy).toHaveBeenCalledWith(`Case model set to mid-tower in ${caseModelCfg}`);
});

it('applyServerIdentity should call emcmd directly', async () => {
const updateSpy = vi.spyOn(service as any, 'updateCfgFile');

Expand Down Expand Up @@ -1478,7 +1499,7 @@ describe('applyActivationCustomizations specific tests', () => {
)
);
expect(loggerLogSpy).toHaveBeenCalledWith(
'No partner case-model image configured in activation code, skipping case model setup.'
'No partner case-model configured in activation code, skipping case model setup.'
);

// Other steps should still run
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -782,9 +782,10 @@ export class OnboardingService implements OnModuleInit {
this.logger.warn('No activation data available for case model setup.');
return;
}
if (!this.materializedPartnerMedia.caseModel) {
const configuredCaseModel = this.activationData.branding?.caseModel?.trim() ?? '';
if (!this.materializedPartnerMedia.caseModel && !configuredCaseModel) {
this.logger.log(
'No partner case-model image configured in activation code, skipping case model setup.'
'No partner case-model configured in activation code, skipping case model setup.'
);
return;
}
Expand All @@ -805,6 +806,10 @@ export class OnboardingService implements OnModuleInit {
await fs.mkdir(path.dirname(this.caseModelCfg), { recursive: true });
await fs.writeFile(this.caseModelCfg, modelToSet);
this.logger.log(`Case model set to ${modelToSet} in ${this.caseModelCfg}`);
} else if (configuredCaseModel) {
await fs.mkdir(path.dirname(this.caseModelCfg), { recursive: true });
await fs.writeFile(this.caseModelCfg, configuredCaseModel);
this.logger.log(`Case model set to ${configuredCaseModel} in ${this.caseModelCfg}`);
} else {
this.logger.log('No custom case model file found in activation assets.');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export class BrandingConfigInput {
@IsString()
bannerImage?: string;

@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
caseModel?: string;

@Field(() => String, { nullable: true })
@IsOptional()
@IsString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const ACTIVATION_CODE_QUERY = parse(/* GraphQL */ `
showBannerGradient
theme
bannerImage
caseModel
caseModelImage
partnerLogoLightUrl
partnerLogoDarkUrl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type PartnerConfigPayload = {

type BrandingConfigPayload = {
bannerImage?: string;
caseModel?: string;
caseModelImage?: string;
partnerLogoLightUrl?: string;
partnerLogoDarkUrl?: string;
Expand Down Expand Up @@ -140,6 +141,7 @@ const genericPartnerConfig: PartnerConfigPayload = {

const genericBrandingConfig: BrandingConfigPayload = {
bannerImage: './assets/banner.png',
caseModel: 'mid-tower',
caseModelImage: './assets/case-model.png',
partnerLogoLightUrl: './assets/partner-logo-light.svg',
partnerLogoDarkUrl: './assets/partner-logo-dark.svg',
Expand Down Expand Up @@ -1026,6 +1028,16 @@ const currentRegistrationState = computed({
Banner image source (optional): relative path, URL, data URI, or base64
</td>
</tr>
<tr>
<td class="py-2 pr-4 font-mono text-cyan-500">activationCode.branding.caseModel</td>
<td class="py-2 pr-4">
<code class="bg-muted rounded px-1">*.activationcode</code>
</td>
<td class="text-muted-foreground py-2">
Built-in case model value for <code class="bg-muted rounded px-1">case-model.cfg</code>
when no custom image override is provided
</td>
</tr>
<tr>
<td class="py-2 pr-4 font-mono text-cyan-500">activationCode.branding.caseModelImage</td>
<td class="py-2 pr-4">
Expand Down
6 changes: 3 additions & 3 deletions web/src/composables/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type Documents = {
"\n subscription NotificationAddedSub {\n notificationAdded {\n ...NotificationFragment\n }\n }\n": typeof types.NotificationAddedSubDocument,
"\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": typeof types.NotificationOverviewSubDocument,
"\n subscription NotificationsWarningsAndAlertsSub {\n notificationsWarningsAndAlerts {\n ...NotificationFragment\n }\n }\n": typeof types.NotificationsWarningsAndAlertsSubDocument,
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n": typeof types.ActivationCodeDocument,
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModel\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n": typeof types.ActivationCodeDocument,
"\n query Onboarding {\n customization {\n onboarding {\n status\n isPartnerBuild\n completed\n completedAtVersion\n }\n }\n }\n": typeof types.OnboardingDocument,
"\n query GetAvailableLanguages {\n customization {\n availableLanguages {\n code\n name\n url\n }\n }\n }\n": typeof types.GetAvailableLanguagesDocument,
"\n mutation CompleteOnboarding {\n onboarding {\n completeOnboarding {\n status\n isPartnerBuild\n completed\n completedAtVersion\n }\n }\n }\n": typeof types.CompleteOnboardingDocument,
Expand Down Expand Up @@ -150,7 +150,7 @@ const documents: Documents = {
"\n subscription NotificationAddedSub {\n notificationAdded {\n ...NotificationFragment\n }\n }\n": types.NotificationAddedSubDocument,
"\n subscription NotificationOverviewSub {\n notificationsOverview {\n archive {\n ...NotificationCountFragment\n }\n unread {\n ...NotificationCountFragment\n }\n }\n }\n": types.NotificationOverviewSubDocument,
"\n subscription NotificationsWarningsAndAlertsSub {\n notificationsWarningsAndAlerts {\n ...NotificationFragment\n }\n }\n": types.NotificationsWarningsAndAlertsSubDocument,
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n": types.ActivationCodeDocument,
"\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModel\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n": types.ActivationCodeDocument,
"\n query Onboarding {\n customization {\n onboarding {\n status\n isPartnerBuild\n completed\n completedAtVersion\n }\n }\n }\n": types.OnboardingDocument,
"\n query GetAvailableLanguages {\n customization {\n availableLanguages {\n code\n name\n url\n }\n }\n }\n": types.GetAvailableLanguagesDocument,
"\n mutation CompleteOnboarding {\n onboarding {\n completeOnboarding {\n status\n isPartnerBuild\n completed\n completedAtVersion\n }\n }\n }\n": types.CompleteOnboardingDocument,
Expand Down Expand Up @@ -405,7 +405,7 @@ export function graphql(source: "\n subscription NotificationsWarningsAndAlerts
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n"): (typeof documents)["\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n"];
export function graphql(source: "\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModel\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n"): (typeof documents)["\n query ActivationCode {\n customization {\n activationCode {\n code\n partner {\n name\n url\n hardwareSpecsUrl\n manualUrl\n supportUrl\n extraLinks {\n title\n url\n }\n }\n branding {\n header\n headermetacolor\n background\n showBannerGradient\n theme\n bannerImage\n caseModel\n caseModelImage\n partnerLogoLightUrl\n partnerLogoDarkUrl\n hasPartnerLogo\n onboardingTitle\n onboardingSubtitle\n onboardingTitleFreshInstall\n onboardingSubtitleFreshInstall\n onboardingTitleUpgrade\n onboardingSubtitleUpgrade\n onboardingTitleDowngrade\n onboardingSubtitleDowngrade\n onboardingTitleIncomplete\n onboardingSubtitleIncomplete\n }\n system {\n serverName\n model\n }\n }\n onboarding {\n onboardingState {\n registrationState\n isRegistered\n isFreshInstall\n hasActivationCode\n activationRequired\n }\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
Loading
Loading