mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-11 07:55:52 +01:00
✨ Add migrator for modules to widgets
This commit is contained in:
@@ -1,8 +1,16 @@
|
|||||||
|
import Consola from 'consola';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { AppType } from '../../types/app';
|
import { AppIntegrationType, AppType, IntegrationType } from '../../types/app';
|
||||||
import { AreaType } from '../../types/area';
|
import { AreaType } from '../../types/area';
|
||||||
import { CategoryType } from '../../types/category';
|
import { CategoryType } from '../../types/category';
|
||||||
import { ConfigType } from '../../types/config';
|
import { ConfigType } from '../../types/config';
|
||||||
|
import widgets from '../../widgets';
|
||||||
|
import { IBitTorrent } from '../../widgets/bitTorrent/BitTorrentTile';
|
||||||
|
import { IDashDotTile } from '../../widgets/dashDot/DashDotTile';
|
||||||
|
import { IDateWidget } from '../../widgets/date/DateTile';
|
||||||
|
import { ITorrentNetworkTraffic } from '../../widgets/torrentNetworkTraffic/TorrentNetworkTrafficTile';
|
||||||
|
import { IWeatherWidget } from '../../widgets/weather/WeatherTile';
|
||||||
|
import { IWidget } from '../../widgets/widgets';
|
||||||
import { Config, serviceItem } from '../types';
|
import { Config, serviceItem } from '../types';
|
||||||
|
|
||||||
export function migrateConfig(config: Config): ConfigType {
|
export function migrateConfig(config: Config): ConfigType {
|
||||||
@@ -12,7 +20,7 @@ export function migrateConfig(config: Config): ConfigType {
|
|||||||
name: config.name ?? 'default',
|
name: config.name ?? 'default',
|
||||||
},
|
},
|
||||||
categories: [],
|
categories: [],
|
||||||
widgets: [],
|
widgets: migrateModules(config),
|
||||||
apps: [],
|
apps: [],
|
||||||
settings: {
|
settings: {
|
||||||
common: {
|
common: {
|
||||||
@@ -26,13 +34,17 @@ export function migrateConfig(config: Config): ConfigType {
|
|||||||
defaultConfig: 'default',
|
defaultConfig: 'default',
|
||||||
},
|
},
|
||||||
customization: {
|
customization: {
|
||||||
colors: {},
|
colors: {
|
||||||
|
primary: config.settings.primaryColor,
|
||||||
|
secondary: config.settings.secondaryColor,
|
||||||
|
shade: config.settings.primaryShade,
|
||||||
|
},
|
||||||
layout: {
|
layout: {
|
||||||
enabledDocker: false,
|
enabledDocker: config.modules.docker?.enabled ?? false,
|
||||||
enabledLeftSidebar: false,
|
enabledLeftSidebar: false,
|
||||||
enabledPing: false,
|
enabledPing: config.modules.ping?.enabled ?? false,
|
||||||
enabledRightSidebar: false,
|
enabledRightSidebar: false,
|
||||||
enabledSearchbar: true,
|
enabledSearchbar: config.modules.search?.enabled ?? true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,10 +134,7 @@ const migrateService = (
|
|||||||
appearance: {
|
appearance: {
|
||||||
iconUrl: migrateIcon(oldService.icon),
|
iconUrl: migrateIcon(oldService.icon),
|
||||||
},
|
},
|
||||||
integration: {
|
integration: migrateIntegration(oldService),
|
||||||
type: null,
|
|
||||||
properties: [],
|
|
||||||
},
|
|
||||||
area: areaType,
|
area: areaType,
|
||||||
shape: {
|
shape: {
|
||||||
lg: getShapeForColumnCount(serviceIndex, 12),
|
lg: getShapeForColumnCount(serviceIndex, 12),
|
||||||
@@ -134,13 +143,259 @@ const migrateService = (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const migrateModules = (config: Config): IWidget<string, any>[] => {
|
||||||
|
const moduleKeys = Object.keys(config.modules);
|
||||||
|
return moduleKeys
|
||||||
|
.map((moduleKey): IWidget<string, any> | null => {
|
||||||
|
const oldModule = config.modules[moduleKey];
|
||||||
|
|
||||||
|
if (!oldModule.enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (moduleKey.toLowerCase()) {
|
||||||
|
case 'torrent-status':
|
||||||
|
case 'Torrent':
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
properties: {
|
||||||
|
refreshInterval: 10,
|
||||||
|
displayCompletedTorrents: oldModule.options?.hideComplete?.value ?? false,
|
||||||
|
displayStaleTorrents: true,
|
||||||
|
},
|
||||||
|
area: {
|
||||||
|
type: 'wrapper',
|
||||||
|
properties: {
|
||||||
|
id: 'default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as IBitTorrent;
|
||||||
|
case 'weather':
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
properties: {
|
||||||
|
displayInFahrenheit: oldModule.options?.freedomunit?.value ?? false,
|
||||||
|
location: oldModule.options?.location?.value ?? 'Paris',
|
||||||
|
},
|
||||||
|
} as IWeatherWidget;
|
||||||
|
case 'dashdot':
|
||||||
|
case 'Dash.':
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
properties: {
|
||||||
|
url: oldModule.options?.url?.value ?? '',
|
||||||
|
cpuMultiView: oldModule.options?.cpuMultiView?.value ?? false,
|
||||||
|
storageMultiView: oldModule.options?.storageMultiView?.value ?? false,
|
||||||
|
useCompactView: oldModule.options?.useCompactView?.value ?? false,
|
||||||
|
graphs: oldModule.options?.graphs?.value ?? ['cpu', 'ram'],
|
||||||
|
},
|
||||||
|
} as IDashDotTile;
|
||||||
|
case 'date':
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
properties: {
|
||||||
|
display24HourFormat: oldModule.options?.full?.value ?? true,
|
||||||
|
},
|
||||||
|
} as IDateWidget;
|
||||||
|
case 'Download Speed':
|
||||||
|
case 'dlspeed':
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
properties: {},
|
||||||
|
} as ITorrentNetworkTraffic;
|
||||||
|
default:
|
||||||
|
Consola.error(`Failed to map unknown module type ${moduleKey} to new type definitions.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((x) => x !== null) as IWidget<string, any>[];
|
||||||
|
};
|
||||||
|
|
||||||
const migrateIcon = (iconUrl: string) => {
|
const migrateIcon = (iconUrl: string) => {
|
||||||
if (iconUrl.startsWith('https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/')) {
|
if (iconUrl.startsWith('https://cdn.jsdelivr.net/gh/walkxhub/dashboard-icons/png/')) {
|
||||||
console.log('migrating icon:');
|
|
||||||
const icon = iconUrl.split('/').at(-1);
|
const icon = iconUrl.split('/').at(-1);
|
||||||
console.log(`${iconUrl} -> ${icon}`);
|
Consola.warn(
|
||||||
|
`Detected legacy icon repository. Upgrading to replacement repository: ${iconUrl} -> ${icon}`
|
||||||
|
);
|
||||||
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
|
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return iconUrl;
|
return iconUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const migrateIntegration = (oldService: serviceItem): AppIntegrationType => {
|
||||||
|
const logInformation = (newType: IntegrationType) => {
|
||||||
|
Consola.info(`Migrated integration ${oldService.type} to the new type ${newType}`);
|
||||||
|
};
|
||||||
|
switch (oldService.type) {
|
||||||
|
case 'Deluge':
|
||||||
|
logInformation('deluge');
|
||||||
|
return {
|
||||||
|
type: 'deluge',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'password',
|
||||||
|
isDefined: oldService.password !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.password,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Jellyseerr':
|
||||||
|
logInformation('jellyseerr');
|
||||||
|
return {
|
||||||
|
type: 'jellyseerr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Overseerr':
|
||||||
|
logInformation('overseerr');
|
||||||
|
return {
|
||||||
|
type: 'overseerr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Lidarr':
|
||||||
|
logInformation('lidarr');
|
||||||
|
return {
|
||||||
|
type: 'lidarr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Radarr':
|
||||||
|
logInformation('radarr');
|
||||||
|
return {
|
||||||
|
type: 'radarr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Readarr':
|
||||||
|
logInformation('readarr');
|
||||||
|
return {
|
||||||
|
type: 'readarr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Sabnzbd':
|
||||||
|
logInformation('sabnzbd');
|
||||||
|
return {
|
||||||
|
type: 'sabnzbd',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Sonarr':
|
||||||
|
logInformation('sonarr');
|
||||||
|
return {
|
||||||
|
type: 'sonarr',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'apiKey',
|
||||||
|
isDefined: oldService.apiKey !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.apiKey,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'NZBGet':
|
||||||
|
logInformation('nzbGet');
|
||||||
|
return {
|
||||||
|
type: 'nzbGet',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
isDefined: oldService.username !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'password',
|
||||||
|
isDefined: oldService.password !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.password,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'qBittorrent':
|
||||||
|
logInformation('qBittorrent');
|
||||||
|
return {
|
||||||
|
type: 'qBittorrent',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
isDefined: oldService.username !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'password',
|
||||||
|
isDefined: oldService.password !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.password,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case 'Transmission':
|
||||||
|
logInformation('transmission');
|
||||||
|
return {
|
||||||
|
type: 'transmission',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
field: 'username',
|
||||||
|
isDefined: oldService.username !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'password',
|
||||||
|
isDefined: oldService.password !== undefined,
|
||||||
|
type: 'private',
|
||||||
|
value: oldService.password,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
Consola.warn(
|
||||||
|
`Integration type of service ${oldService.name} could not be mapped to new integration type definition`
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
type: null,
|
||||||
|
properties: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const definition = defineWidget({
|
|||||||
},
|
},
|
||||||
refreshInterval: {
|
refreshInterval: {
|
||||||
type: 'slider',
|
type: 'slider',
|
||||||
defaultValue: 1,
|
defaultValue: 10,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 60,
|
max: 60,
|
||||||
step: 1,
|
step: 1,
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ function DashDotTile({ widget }: DashDotTileProps) {
|
|||||||
dashDotUrl,
|
dashDotUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
const graphs = widget?.properties.graphs.map((g) => ({
|
const graphs = widget?.properties.graphs.map((graph) => ({
|
||||||
id: g,
|
id: graph,
|
||||||
name: t(`card.graphs.${g}.title`),
|
name: t(`card.graphs.${graph}.title`),
|
||||||
twoSpan: ['network', 'gpu'].includes(g),
|
twoSpan: ['network', 'gpu'].includes(graph),
|
||||||
isMultiView:
|
isMultiView:
|
||||||
(g === 'cpu' && widget.properties.cpuMultiView) ||
|
(graph === 'cpu' && widget.properties.cpuMultiView) ||
|
||||||
(g === 'storage' && widget.properties.storageMultiView),
|
(graph === 'storage' && widget.properties.storageMultiView),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const heading = (
|
const heading = (
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { TablerIcon } from '@tabler/icons';
|
import { TablerIcon } from '@tabler/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { AreaType } from '../types/area';
|
||||||
|
import { ShapeType } from '../types/shape';
|
||||||
|
|
||||||
// Type of widgets which are safed to config
|
// Type of widgets which are safed to config
|
||||||
export type IWidget<TKey extends string, TDefinition extends IWidgetDefinition> = {
|
export type IWidget<TKey extends string, TDefinition extends IWidgetDefinition> = {
|
||||||
Reference in New Issue
Block a user