mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-15 17:56:21 +01:00
🔀 Merge pull request #543 from manuel-rw/gridstack
This commit is contained in:
@@ -14,6 +14,7 @@ import { useState } from 'react';
|
|||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
import { AppType } from '../../../../types/app';
|
import { AppType } from '../../../../types/app';
|
||||||
|
import { useEditModeStore } from '../../Views/useEditModeStore';
|
||||||
import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab';
|
import { AppearanceTab } from './Tabs/AppereanceTab/AppereanceTab';
|
||||||
import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab';
|
import { BehaviourTab } from './Tabs/BehaviourTab/BehaviourTab';
|
||||||
import { GeneralTab } from './Tabs/GeneralTab/GeneralTab';
|
import { GeneralTab } from './Tabs/GeneralTab/GeneralTab';
|
||||||
@@ -33,6 +34,7 @@ export const EditAppModal = ({
|
|||||||
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
const { t } = useTranslation(['layout/modals/add-app', 'common']);
|
||||||
const { name: configName, config } = useConfigContext();
|
const { name: configName, config } = useConfigContext();
|
||||||
const updateConfig = useConfigStore((store) => store.updateConfig);
|
const updateConfig = useConfigStore((store) => store.updateConfig);
|
||||||
|
const { enabled: isEditMode } = useEditModeStore();
|
||||||
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
|
const [allowAppNamePropagation, setAllowAppNamePropagation] = useState<boolean>(
|
||||||
innerProps.allowAppNamePropagation
|
innerProps.allowAppNamePropagation
|
||||||
);
|
);
|
||||||
@@ -87,9 +89,15 @@ export const EditAppModal = ({
|
|||||||
configName,
|
configName,
|
||||||
(previousConfig) => ({
|
(previousConfig) => ({
|
||||||
...previousConfig,
|
...previousConfig,
|
||||||
apps: [...previousConfig.apps.filter((x) => x.id !== form.values.id), form.values],
|
apps: [
|
||||||
|
...previousConfig.apps.filter((x) => x.id !== values.id),
|
||||||
|
{
|
||||||
|
...values,
|
||||||
|
},
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
true
|
true,
|
||||||
|
!isEditMode
|
||||||
);
|
);
|
||||||
|
|
||||||
// also close the parent modal
|
// also close the parent modal
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ import { IconBox, IconStack } from '@tabler/icons';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useConfigContext } from '../../../../../../config/provider';
|
||||||
import { openContextModalGeneric } from '../../../../../../tools/mantineModalManagerExtensions';
|
import { openContextModalGeneric } from '../../../../../../tools/mantineModalManagerExtensions';
|
||||||
import { AppType } from '../../../../../../types/app';
|
import { AppType } from '../../../../../../types/app';
|
||||||
|
import { appTileDefinition } from '../../../../Tiles/Apps/AppTile';
|
||||||
import { useStyles } from '../Shared/styles';
|
import { useStyles } from '../Shared/styles';
|
||||||
|
|
||||||
interface AvailableElementTypesProps {
|
interface AvailableElementTypesProps {
|
||||||
@@ -17,6 +19,8 @@ export const AvailableElementTypes = ({
|
|||||||
onOpenStaticElements,
|
onOpenStaticElements,
|
||||||
}: AvailableElementTypesProps) => {
|
}: AvailableElementTypesProps) => {
|
||||||
const { t } = useTranslation('layout/element-selector/selector');
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
|
const { config } = useConfigContext();
|
||||||
|
const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -45,10 +49,11 @@ export const AvailableElementTypes = ({
|
|||||||
isOpeningNewTab: true,
|
isOpeningNewTab: true,
|
||||||
externalUrl: '',
|
externalUrl: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
area: {
|
area: {
|
||||||
type: 'sidebar',
|
type: 'wrapper',
|
||||||
properties: {
|
properties: {
|
||||||
location: 'right',
|
id: getLowestWrapper()?.id ?? '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
@@ -57,8 +62,8 @@ export const AvailableElementTypes = ({
|
|||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
height: 1,
|
width: appTileDefinition.minWidth,
|
||||||
width: 1,
|
height: appTileDefinition.minHeight,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
integration: {
|
integration: {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useTranslation } from 'next-i18next';
|
|||||||
import { useConfigContext } from '../../../../../../config/provider';
|
import { useConfigContext } from '../../../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../../../config/store';
|
import { useConfigStore } from '../../../../../../config/store';
|
||||||
import { IWidget, IWidgetDefinition } from '../../../../../../widgets/widgets';
|
import { IWidget, IWidgetDefinition } from '../../../../../../widgets/widgets';
|
||||||
|
import { useEditModeStore } from '../../../../Views/useEditModeStore';
|
||||||
import { GenericAvailableElementType } from '../Shared/GenericElementType';
|
import { GenericAvailableElementType } from '../Shared/GenericElementType';
|
||||||
|
|
||||||
interface WidgetElementTypeProps {
|
interface WidgetElementTypeProps {
|
||||||
@@ -18,6 +19,7 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
const { t } = useTranslation(`modules/${id}`);
|
const { t } = useTranslation(`modules/${id}`);
|
||||||
const { name: configName, config } = useConfigContext();
|
const { name: configName, config } = useConfigContext();
|
||||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||||
|
const isEditMode = useEditModeStore((x) => x.enabled);
|
||||||
|
|
||||||
if (!configName) return null;
|
if (!configName) return null;
|
||||||
|
|
||||||
@@ -56,9 +58,10 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
true
|
true,
|
||||||
|
!isEditMode
|
||||||
);
|
);
|
||||||
// TODO: safe to file system
|
|
||||||
closeModal('selectElement');
|
closeModal('selectElement');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -88,3 +88,11 @@ const useStyles = createStyles((theme, _params, getRef) => ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const appTileDefinition = {
|
||||||
|
component: AppTile,
|
||||||
|
minWidth: 2,
|
||||||
|
maxWidth: 12,
|
||||||
|
minHeight: 2,
|
||||||
|
maxHeight: 12,
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,77 +0,0 @@
|
|||||||
import calendarDefinition from '../../../widgets/calendar/CalendarTile';
|
|
||||||
import clockDefinition from '../../../widgets/date/DateTile';
|
|
||||||
import dashDotDefinition from '../../../widgets/dashDot/DashDotTile';
|
|
||||||
import useNetDefinition from '../../../widgets/useNet/UseNetTile';
|
|
||||||
import weatherDefinition from '../../../widgets/weather/WeatherTile';
|
|
||||||
import { EmptyTile } from './EmptyTile';
|
|
||||||
import { AppTile } from './Apps/AppTile';
|
|
||||||
|
|
||||||
// TODO: just remove and use app (later app) directly. For widgets the the definition should contain min/max width/height
|
|
||||||
type TileDefinitionProps = {
|
|
||||||
[key in keyof any | 'app']: {
|
|
||||||
minWidth?: number;
|
|
||||||
minHeight?: number;
|
|
||||||
maxWidth?: number;
|
|
||||||
maxHeight?: number;
|
|
||||||
component: React.ElementType;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Tiles: TileDefinitionProps = {
|
|
||||||
app: {
|
|
||||||
component: AppTile,
|
|
||||||
minWidth: 2,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 2,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
bitTorrent: {
|
|
||||||
component: EmptyTile,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 5,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
calendar: {
|
|
||||||
component: calendarDefinition.component,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 5,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
clock: {
|
|
||||||
component: clockDefinition.component,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 2,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
dashDot: {
|
|
||||||
component: dashDotDefinition.component,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 9,
|
|
||||||
minHeight: 5,
|
|
||||||
maxHeight: 14,
|
|
||||||
},
|
|
||||||
torrentNetworkTraffic: {
|
|
||||||
component: EmptyTile,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 5,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
useNet: {
|
|
||||||
component: useNetDefinition.component,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 5,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
weather: {
|
|
||||||
component: weatherDefinition.component,
|
|
||||||
minWidth: 4,
|
|
||||||
maxWidth: 12,
|
|
||||||
minHeight: 2,
|
|
||||||
maxHeight: 12,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -4,7 +4,7 @@ import { AppType } from '../../../types/app';
|
|||||||
import Widgets from '../../../widgets';
|
import Widgets from '../../../widgets';
|
||||||
import { IWidget, IWidgetDefinition } from '../../../widgets/widgets';
|
import { IWidget, IWidgetDefinition } from '../../../widgets/widgets';
|
||||||
import { WidgetWrapper } from '../../../widgets/WidgetWrapper';
|
import { WidgetWrapper } from '../../../widgets/WidgetWrapper';
|
||||||
import { Tiles } from '../Tiles/tilesDefinitions';
|
import { appTileDefinition } from '../Tiles/Apps/AppTile';
|
||||||
import { GridstackTileWrapper } from '../Tiles/TileWrapper';
|
import { GridstackTileWrapper } from '../Tiles/TileWrapper';
|
||||||
|
|
||||||
interface WrapperContentProps {
|
interface WrapperContentProps {
|
||||||
@@ -21,7 +21,7 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{apps?.map((app) => {
|
{apps?.map((app) => {
|
||||||
const { component: TileComponent, ...tile } = Tiles.app;
|
const { component: TileComponent, ...tile } = appTileDefinition;
|
||||||
return (
|
return (
|
||||||
<GridstackTileWrapper
|
<GridstackTileWrapper
|
||||||
id={app.id}
|
id={app.id}
|
||||||
|
|||||||
@@ -1,35 +1,53 @@
|
|||||||
import { Stack } from '@mantine/core';
|
import { Button, ScrollArea, Stack } from '@mantine/core';
|
||||||
import { useConfigContext } from '../../../config/provider';
|
import { useConfigContext } from '../../../config/provider';
|
||||||
import { ColorSelector } from './Theme/ColorSelector';
|
import { useConfigStore } from '../../../config/store';
|
||||||
|
import { LayoutSelector } from './Layout/LayoutSelector';
|
||||||
import { BackgroundChanger } from './Meta/BackgroundChanger';
|
import { BackgroundChanger } from './Meta/BackgroundChanger';
|
||||||
import { CustomCssChanger } from './Theme/CustomCssChanger';
|
|
||||||
import { FaviconChanger } from './Meta/FaviconChanger';
|
import { FaviconChanger } from './Meta/FaviconChanger';
|
||||||
import { LogoImageChanger } from './Meta/LogoImageChanger';
|
import { LogoImageChanger } from './Meta/LogoImageChanger';
|
||||||
import { MetaTitleChanger } from './Meta/MetaTitleChanger';
|
import { MetaTitleChanger } from './Meta/MetaTitleChanger';
|
||||||
import { PageTitleChanger } from './Meta/PageTitleChanger';
|
import { PageTitleChanger } from './Meta/PageTitleChanger';
|
||||||
|
import { ColorSelector } from './Theme/ColorSelector';
|
||||||
|
import { CustomCssChanger } from './Theme/CustomCssChanger';
|
||||||
import { OpacitySelector } from './Theme/OpacitySelector';
|
import { OpacitySelector } from './Theme/OpacitySelector';
|
||||||
import { ShadeSelector } from './Theme/ShadeSelector';
|
import { ShadeSelector } from './Theme/ShadeSelector';
|
||||||
import { LayoutSelector } from './Layout/LayoutSelector';
|
|
||||||
|
|
||||||
export default function CustomizationSettings() {
|
export default function CustomizationSettings() {
|
||||||
const { config } = useConfigContext();
|
const { config, name: configName } = useConfigContext();
|
||||||
|
|
||||||
|
const { updateConfig } = useConfigStore();
|
||||||
|
|
||||||
|
const saveConfiguration = () => {
|
||||||
|
if (!configName || !config) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig(configName, (_) => config, false, true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack mb="md" mr="sm" mt="xs">
|
<Stack mb="md" mr="sm" mt="xs">
|
||||||
<LayoutSelector defaultLayout={config?.settings.customization.layout} />
|
<ScrollArea style={{ height: '76vh' }} offsetScrollbars>
|
||||||
<PageTitleChanger defaultValue={config?.settings.customization.pageTitle} />
|
<LayoutSelector defaultLayout={config?.settings.customization.layout} />
|
||||||
<MetaTitleChanger defaultValue={config?.settings.customization.metaTitle} />
|
<PageTitleChanger defaultValue={config?.settings.customization.pageTitle} />
|
||||||
<LogoImageChanger defaultValue={config?.settings.customization.logoImageUrl} />
|
<MetaTitleChanger defaultValue={config?.settings.customization.metaTitle} />
|
||||||
<FaviconChanger defaultValue={config?.settings.customization.faviconUrl} />
|
<LogoImageChanger defaultValue={config?.settings.customization.logoImageUrl} />
|
||||||
<BackgroundChanger defaultValue={config?.settings.customization.backgroundImageUrl} />
|
<FaviconChanger defaultValue={config?.settings.customization.faviconUrl} />
|
||||||
<CustomCssChanger defaultValue={config?.settings.customization.customCss} />
|
<BackgroundChanger defaultValue={config?.settings.customization.backgroundImageUrl} />
|
||||||
<ColorSelector type="primary" defaultValue={config?.settings.customization.colors.primary} />
|
<CustomCssChanger defaultValue={config?.settings.customization.customCss} />
|
||||||
<ColorSelector
|
<ColorSelector
|
||||||
type="secondary"
|
type="primary"
|
||||||
defaultValue={config?.settings.customization.colors.secondary}
|
defaultValue={config?.settings.customization.colors.primary}
|
||||||
/>
|
/>
|
||||||
<ShadeSelector defaultValue={config?.settings.customization.colors.shade} />
|
<ColorSelector
|
||||||
<OpacitySelector defaultValue={config?.settings.customization.appOpacity} />
|
type="secondary"
|
||||||
|
defaultValue={config?.settings.customization.colors.secondary}
|
||||||
|
/>
|
||||||
|
<ShadeSelector defaultValue={config?.settings.customization.colors.shade} />
|
||||||
|
<OpacitySelector defaultValue={config?.settings.customization.appOpacity} />
|
||||||
|
</ScrollArea>
|
||||||
|
|
||||||
|
<Button onClick={saveConfiguration} variant="light">Save Customizations</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Title, Drawer, Tabs, ScrollArea } from '@mantine/core';
|
import { Drawer, ScrollArea, Tabs, Title } from '@mantine/core';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
|
||||||
import CustomizationSettings from './Customization/CustomizationSettings';
|
|
||||||
import CommonSettings from './Common/CommonSettings';
|
import CommonSettings from './Common/CommonSettings';
|
||||||
import Credits from './Common/Credits';
|
import Credits from './Common/Credits';
|
||||||
|
import CustomizationSettings from './Customization/CustomizationSettings';
|
||||||
|
|
||||||
function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
|
function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string }) {
|
||||||
const { t } = useTranslation('settings/common');
|
const { t } = useTranslation('settings/common');
|
||||||
@@ -20,9 +20,7 @@ function SettingsMenu({ newVersionAvailable }: { newVersionAvailable: string })
|
|||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
<Tabs.Panel value="customization">
|
<Tabs.Panel value="customization">
|
||||||
<ScrollArea style={{ height: '78vh' }} offsetScrollbars>
|
<CustomizationSettings />
|
||||||
<CustomizationSettings />
|
|
||||||
</ScrollArea>
|
|
||||||
</Tabs.Panel>
|
</Tabs.Panel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import axios from 'axios';
|
||||||
import create from 'zustand';
|
import create from 'zustand';
|
||||||
import { ConfigType } from '../types/config';
|
import { ConfigType } from '../types/config';
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ export const useConfigStore = create<UseConfigStoreType>((set, get) => ({
|
|||||||
updateConfig: async (
|
updateConfig: async (
|
||||||
name,
|
name,
|
||||||
updateCallback: (previous: ConfigType) => ConfigType,
|
updateCallback: (previous: ConfigType) => ConfigType,
|
||||||
shouldRegenerateGridstack = false
|
shouldRegenerateGridstack = false,
|
||||||
|
shouldSaveConfigToFileSystem = false
|
||||||
) => {
|
) => {
|
||||||
const { configs } = get();
|
const { configs } = get();
|
||||||
const currentConfig = configs.find((x) => x.value.configProperties.name === name);
|
const currentConfig = configs.find((x) => x.value.configProperties.name === name);
|
||||||
@@ -23,7 +25,6 @@ export const useConfigStore = create<UseConfigStoreType>((set, get) => ({
|
|||||||
// copies the value of currentConfig and creates a non reference object named previousConfig
|
// copies the value of currentConfig and creates a non reference object named previousConfig
|
||||||
const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value));
|
const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value));
|
||||||
|
|
||||||
// TODO: update config on server
|
|
||||||
const updatedConfig = updateCallback(currentConfig.value);
|
const updatedConfig = updateCallback(currentConfig.value);
|
||||||
set((old) => ({
|
set((old) => ({
|
||||||
...old,
|
...old,
|
||||||
@@ -40,6 +41,10 @@ export const useConfigStore = create<UseConfigStoreType>((set, get) => ({
|
|||||||
) {
|
) {
|
||||||
currentConfig.increaseVersion();
|
currentConfig.increaseVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldSaveConfigToFileSystem) {
|
||||||
|
axios.put(`/api/configs/${name}`, { ...updatedConfig });
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -51,6 +56,7 @@ interface UseConfigStoreType {
|
|||||||
updateCallback: (previous: ConfigType) => ConfigType,
|
updateCallback: (previous: ConfigType) => ConfigType,
|
||||||
shouldRegenerateGridstace?:
|
shouldRegenerateGridstace?:
|
||||||
| boolean
|
| boolean
|
||||||
| ((previousConfig: ConfigType, currentConfig: ConfigType) => boolean)
|
| ((previousConfig: ConfigType, currentConfig: ConfigType) => boolean),
|
||||||
|
shouldSaveConfigToFileSystem?: boolean
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
1
src/modules/ModuleTypes.d.ts
vendored
1
src/modules/ModuleTypes.d.ts
vendored
@@ -5,6 +5,7 @@
|
|||||||
import { TablerIcon } from '@tabler/icons';
|
import { TablerIcon } from '@tabler/icons';
|
||||||
|
|
||||||
// Note: Maybe use context to keep track of the modules
|
// Note: Maybe use context to keep track of the modules
|
||||||
|
// TODO: Remove this old component and the entire file
|
||||||
export interface IModule {
|
export interface IModule {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -1,22 +1,61 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
import { BackendConfigType, ConfigType } from '../../../types/config';
|
||||||
|
import { getConfig } from '../../../tools/config/getConfig';
|
||||||
|
|
||||||
function Put(req: NextApiRequest, res: NextApiResponse) {
|
function Put(req: NextApiRequest, res: NextApiResponse) {
|
||||||
// Get the slug of the request
|
// Get the slug of the request
|
||||||
const { slug } = req.query as { slug: string };
|
const { slug } = req.query as { slug: string };
|
||||||
// Get the body of the request
|
// Get the body of the request
|
||||||
const { body }: { body: string } = req;
|
const { body: config }: { body: ConfigType } = req;
|
||||||
if (!slug || !body) {
|
if (!slug || !config) {
|
||||||
res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'Wrong request',
|
error: 'Wrong request',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Save the body in the /data/config folder with the slug as filename
|
|
||||||
|
|
||||||
|
const previousConfig = getConfig(slug);
|
||||||
|
|
||||||
|
const newConfig: BackendConfigType = {
|
||||||
|
...config,
|
||||||
|
apps: [
|
||||||
|
...config.apps.map((app) => ({
|
||||||
|
...app,
|
||||||
|
integration: {
|
||||||
|
...app.integration,
|
||||||
|
properties: app.integration.properties.map((property) => {
|
||||||
|
if (property.type === 'public') {
|
||||||
|
return {
|
||||||
|
field: property.field,
|
||||||
|
type: property.type,
|
||||||
|
value: property.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousApp = previousConfig.apps.find(
|
||||||
|
(previousApp) => previousApp.id === app.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const previousProperty = previousApp?.integration?.properties.find(
|
||||||
|
(previousProperty) => previousProperty.field === property.field
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
field: property.field,
|
||||||
|
type: property.type,
|
||||||
|
value: property.value !== undefined ? property.value : previousProperty?.value,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the body in the /data/config folder with the slug as filename
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join('data/configs', `${slug}.json`),
|
path.join('data/configs', `${slug}.json`),
|
||||||
JSON.stringify(body, null, 2),
|
JSON.stringify(newConfig, null, 2),
|
||||||
'utf8'
|
'utf8'
|
||||||
);
|
);
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": false,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
|||||||
Reference in New Issue
Block a user