mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
Add config migration process
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const AvailableElementTypes = ({
|
|||||||
},
|
},
|
||||||
behaviour: {
|
behaviour: {
|
||||||
isOpeningNewTab: true,
|
isOpeningNewTab: true,
|
||||||
onClickUrl: '',
|
externalUrl: '',
|
||||||
},
|
},
|
||||||
area: {
|
area: {
|
||||||
type: 'sidebar',
|
type: 'sidebar',
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) => (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
81
src/tools/config/migrateConfig.ts
Normal file
81
src/tools/config/migrateConfig.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export type ConfigAppType = Omit<AppType, 'integration'> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface AppBehaviourType {
|
interface AppBehaviourType {
|
||||||
onClickUrl: string;
|
externalUrl: string;
|
||||||
isOpeningNewTab: boolean;
|
isOpeningNewTab: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user