Add config migration process

This commit is contained in:
ajnart
2022-12-22 11:29:51 +09:00
parent 5f8a420c83
commit ecc61d5970
11 changed files with 147 additions and 55 deletions

View File

@@ -128,9 +128,9 @@
{ {
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990", "id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
"name": "Donate", "name": "Donate",
"url": "https://homarr.dev/docs/community/donate", "url": "https://ko-fi.com/ajnart",
"behaviour": { "behaviour": {
"onClickUrl": "https://homarr.dev/docs/community/donate", "onClickUrl": "https://ko-fi.com/ajnart",
"isOpeningInNewTab": true "isOpeningInNewTab": true
}, },
"network": { "network": {
@@ -140,7 +140,7 @@
] ]
}, },
"appearance": { "appearance": {
"iconUrl": "/imgs/logo/logo.png" "iconUrl": "https://uploads-ssl.webflow.com/5c14e387dab576fe667689cf/61e1116779fc0a9bd5bdbcc7_Frame%206.png"
}, },
"integration": { "integration": {
"type": null, "type": null,

View File

@@ -62,7 +62,7 @@ export const EditAppModal = ({
}, },
}, },
behaviour: { behaviour: {
onClickUrl: (url: string) => { externalUrl: (url: string) => {
if (url === undefined || url.length < 1) { if (url === undefined || url.length < 1) {
return null; return null;
} }
@@ -158,7 +158,7 @@ export const EditAppModal = ({
{t('tabs.general')} {t('tabs.general')}
</Tabs.Tab> </Tabs.Tab>
<Tabs.Tab <Tabs.Tab
rightSection={<ValidationErrorIndicator keys={['behaviour.onClickUrl']} />} rightSection={<ValidationErrorIndicator keys={['behaviour.externalUrl']} />}
icon={<IconClick size={14} />} icon={<IconClick size={14} />}
value="behaviour" value="behaviour"
> >

View File

@@ -35,7 +35,7 @@ export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
required required
{...form.getInputProps('url')} {...form.getInputProps('url')}
onChange={(e) => { onChange={(e) => {
form.setFieldValue('behaviour.onClickUrl', e.target.value); form.setFieldValue('behaviour.externalUrl', e.target.value);
form.setFieldValue('url', e.target.value); form.setFieldValue('url', e.target.value);
}} }}
/> />
@@ -46,7 +46,7 @@ export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
placeholder="https://homarr.mywebsite.com/" placeholder="https://homarr.mywebsite.com/"
variant="default" variant="default"
mb="md" mb="md"
{...form.getInputProps('behaviour.onClickUrl')} {...form.getInputProps('behaviour.externalUrl')}
/> />
</Tabs.Panel> </Tabs.Panel>
); );

View File

@@ -43,7 +43,7 @@ export const AvailableElementTypes = ({
}, },
behaviour: { behaviour: {
isOpeningNewTab: true, isOpeningNewTab: true,
onClickUrl: '', externalUrl: '',
}, },
area: { area: {
type: 'sidebar', type: 'sidebar',

View File

@@ -18,45 +18,47 @@ interface WrapperContentProps {
}; };
} }
export const WrapperContent = ({ apps, refs, widgets }: WrapperContentProps) => ( export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
<> return (
{apps?.map((app) => { <>
const { component: TileComponent, ...tile } = Tiles.app; {apps?.map((app) => {
return ( const { component: TileComponent, ...tile } = Tiles.app;
<GridstackTileWrapper return (
id={app.id} <GridstackTileWrapper
type="app" id={app.id}
key={app.id} type="app"
itemRef={refs.items.current[app.id]} key={app.id}
{...tile} itemRef={refs.items.current[app.id]}
{...app.shape.location} {...tile}
{...app.shape.size} {...app.shape.location}
> {...app.shape.size}
<TileComponent className="grid-stack-item-content" app={app} /> >
</GridstackTileWrapper> <TileComponent className="grid-stack-item-content" app={app} />
); </GridstackTileWrapper>
})} );
{widgets.map((widget) => { })}
const definition = Widgets[widget.id as keyof typeof Widgets] as {widgets.map((widget) => {
| IWidgetDefinition const definition = Widgets[widget.id as keyof typeof Widgets] as
| undefined; | IWidgetDefinition
if (!definition) return null; | undefined;
if (!definition) return null;
return ( return (
<GridstackTileWrapper <GridstackTileWrapper
type="widget" type="widget"
key={widget.id} key={widget.id}
itemRef={refs.items.current[widget.id]} itemRef={refs.items.current[widget.id]}
id={definition.id} id={definition.id}
{...definition.gridstack} {...definition.gridstack}
{...widget.shape.location} {...widget.shape.location}
{...widget.shape.size} {...widget.shape.size}
> >
<WidgetWrapper className="grid-stack-item-content" widget={widget} widgetId={widget.id}> <WidgetWrapper className="grid-stack-item-content" widget={widget} widgetId={widget.id}>
<definition.component className="grid-stack-item-content" widget={widget} /> <definition.component className="grid-stack-item-content" widget={widget} />
</WidgetWrapper> </WidgetWrapper>
</GridstackTileWrapper> </GridstackTileWrapper>
); );
})} })}
</> </>
); );
}

View File

@@ -119,7 +119,7 @@ export function Search() {
label: app.name, label: app.name,
value: app.name, value: app.name,
icon: app.appearance.iconUrl, icon: app.appearance.iconUrl,
url: app.behaviour.onClickUrl ?? app.url, url: app.behaviour.externalUrl ?? app.url,
})); }));
const AutoCompleteItem = forwardRef<HTMLDivElement, any>( const AutoCompleteItem = forwardRef<HTMLDivElement, any>(
({ label, value, icon, url, ...others }: any, ref) => ( ({ label, value, icon, url, ...others }: any, ref) => (

View File

@@ -179,7 +179,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}, },
behaviour: { behaviour: {
isOpeningNewTab: true, isOpeningNewTab: true,
onClickUrl: '', externalUrl: '',
}, },
area: { area: {
type: 'wrapper', // TODO: Set the wrapper automatically type: 'wrapper', // TODO: Set the wrapper automatically

View File

@@ -1,9 +1,18 @@
import { BackendConfigType, ConfigType } from '../../types/config'; import { BackendConfigType } from '../../types/config';
import { configExists } from './configExists'; import { configExists } from './configExists';
import { getFallbackConfig } from './getFallbackConfig'; import { getFallbackConfig } from './getFallbackConfig';
import { migrateConfig } from './migrateConfig';
import { readConfig } from './readConfig'; import { readConfig } from './readConfig';
export const getConfig = (name: string): BackendConfigType => { export const getConfig = (name: string): BackendConfigType => {
if (!configExists(name)) return getFallbackConfig(); if (!configExists(name)) return getFallbackConfig();
return readConfig(name); // Else if config exists but contains no "schema_version" property
// then it is an old config file and we should try to migrate it
// to the new format.
let config = readConfig(name);
if (!config.schemaVersion) {
// TODO: Migrate config to new format
config = migrateConfig(config);
}
return config;
}; };

View File

@@ -0,0 +1,81 @@
import { ConfigType } from '../../types/config';
import { Config } from '../types';
export function migrateConfig(config: Config): ConfigType {
const newConfig: ConfigType = {
schemaVersion: '1.0.0',
configProperties: {
name: config.name ?? 'default',
},
categories: [],
widgets: [],
apps: [],
settings: {
common: {
searchEngine: {
type: 'google',
properties: {
enabled: true,
openInNewTab: true,
},
},
defaultConfig: 'default',
},
customization: {
colors: {},
layout: {
enabledDocker: false,
enabledLeftSidebar: false,
enabledPing: false,
enabledRightSidebar: false,
enabledSearchbar: true,
},
},
},
wrappers: [
{
id: 'default',
position: 1,
},
],
};
newConfig.apps = config.services.map((s, idx) => ({
name: s.name,
id: s.id,
url: s.url,
behaviour: {
isOpeningNewTab: s.newTab ?? true,
externalUrl: s.openedUrl ?? '',
},
network: {
enabledStatusChecker: s.ping ?? true,
okStatus: s.status?.map((str) => parseInt(str, 10)) ?? [200],
},
appearance: {
iconUrl: s.icon,
},
integration: {
type: null,
properties: [],
},
area: {
type: 'wrapper',
properties: {
id: 'default',
},
},
shape: {
location: {
x: (idx * 3) % 18,
y: Math.floor((idx / 6)) * 3,
},
size: {
width: 3,
height: 3,
},
},
}));
return newConfig;
}

View File

@@ -20,7 +20,7 @@ export interface Settings {
export interface Config { export interface Config {
name: string; name: string;
apps: serviceItem[]; services: serviceItem[];
settings: Settings; settings: Settings;
modules: { modules: {
[key: string]: ConfigModule; [key: string]: ConfigModule;

View File

@@ -16,7 +16,7 @@ export type ConfigAppType = Omit<AppType, 'integration'> & {
}; };
interface AppBehaviourType { interface AppBehaviourType {
onClickUrl: string; externalUrl: string;
isOpeningNewTab: boolean; isOpeningNewTab: boolean;
} }