Merge pull request #788 from ajnart/allow-multiple-widgets-of-same-type
Allow multiple widgets of same type
This commit is contained in:
@@ -1,387 +1,389 @@
|
|||||||
{
|
{
|
||||||
"schemaVersion": 1,
|
"schemaVersion": 1,
|
||||||
"configProperties": {
|
"configProperties": {
|
||||||
"name": "default"
|
"name": "default"
|
||||||
},
|
},
|
||||||
"categories": [
|
"categories": [
|
||||||
{
|
{
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f",
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f",
|
||||||
"position": 1,
|
"position": 1,
|
||||||
"name": "Welcome to Homarr 🎉",
|
"name": "Welcome to Homarr 🎉",
|
||||||
"type": "category"
|
"type": "category"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"wrappers": [
|
||||||
|
{
|
||||||
|
"id": "default",
|
||||||
|
"position": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a326",
|
||||||
|
"position": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337",
|
||||||
|
"name": "Discord",
|
||||||
|
"url": "https://discord.com/invite/aCsmEV5RgA",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://discord.com/invite/aCsmEV5RgA",
|
||||||
|
"isOpeningNewTab": true,
|
||||||
|
"externalUrl": "https://discord.com/invite/aCsmEV5RgA"
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 3,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 3,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
|
||||||
|
"name": "Donate",
|
||||||
|
"url": "https://ko-fi.com/ajnart",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://ko-fi.com/ajnart",
|
||||||
|
"externalUrl": "https://ko-fi.com/ajnart",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 3,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330",
|
||||||
|
"name": "Contribute",
|
||||||
|
"url": "https://github.com/ajnart/homarr",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://github.com/ajnart/homarr",
|
||||||
|
"externalUrl": "https://github.com/ajnart/homarr",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": []
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 2
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 4,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5df743d9-5cb1-457c-85d2-64ff86855652",
|
||||||
|
"name": "Documentation",
|
||||||
|
"url": "https://homarr.dev",
|
||||||
|
"behaviour": {
|
||||||
|
"onClickUrl": "https://homarr.dev",
|
||||||
|
"externalUrl": "https://homarr.dev",
|
||||||
|
"isOpeningNewTab": true
|
||||||
|
},
|
||||||
|
"network": {
|
||||||
|
"enabledStatusChecker": false,
|
||||||
|
"okStatus": [
|
||||||
|
200
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"iconUrl": "/imgs/logo/logo.png"
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"type": null,
|
||||||
|
"properties": []
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 1,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgets": [
|
||||||
|
{
|
||||||
|
"id": "971aa859-8570-49a1-8d34-dd5c7b3638d1",
|
||||||
|
"type": "date",
|
||||||
|
"properties": {
|
||||||
|
"display24HourFormat": true
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 1
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 4,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 2,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "e3004052-6b83-480e-b458-56e8ccdca5f0",
|
||||||
|
"type": "weather",
|
||||||
|
"properties": {
|
||||||
|
"displayInFahrenheit": false,
|
||||||
|
"location": "Paris"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"type": "category",
|
||||||
|
"properties": {
|
||||||
|
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shape": {
|
||||||
|
"md": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sm": {
|
||||||
|
"location": {
|
||||||
|
"x": 1,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lg": {
|
||||||
|
"location": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"width": 2,
|
||||||
|
"height": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"common": {
|
||||||
|
"searchEngine": {
|
||||||
|
"type": "google",
|
||||||
|
"properties": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customization": {
|
||||||
|
"layout": {
|
||||||
|
"enabledLeftSidebar": false,
|
||||||
|
"enabledRightSidebar": false,
|
||||||
|
"enabledDocker": false,
|
||||||
|
"enabledPing": false,
|
||||||
|
"enabledSearchbar": true
|
||||||
|
},
|
||||||
|
"pageTitle": "Homarr v0.11 ⭐️",
|
||||||
|
"logoImageUrl": "/imgs/logo/logo.png",
|
||||||
|
"faviconUrl": "/imgs/favicon/favicon-squared",
|
||||||
|
"backgroundImageUrl": "",
|
||||||
|
"customCss": "",
|
||||||
|
"colors": {
|
||||||
|
"primary": "red",
|
||||||
|
"secondary": "yellow",
|
||||||
|
"shade": 7
|
||||||
|
},
|
||||||
|
"appOpacity": 100
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"wrappers": [
|
|
||||||
{
|
|
||||||
"id": "default",
|
|
||||||
"position": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a326",
|
|
||||||
"position": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"apps": [
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a337",
|
|
||||||
"name": "Discord",
|
|
||||||
"url": "https://discord.com/invite/aCsmEV5RgA",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://discord.com/invite/aCsmEV5RgA",
|
|
||||||
"isOpeningNewTab": true,
|
|
||||||
"externalUrl": "https://discord.com/invite/aCsmEV5RgA"
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/discord.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 3,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 3,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a990",
|
|
||||||
"name": "Donate",
|
|
||||||
"url": "https://ko-fi.com/ajnart",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://ko-fi.com/ajnart",
|
|
||||||
"externalUrl": "https://ko-fi.com/ajnart",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/ko-fi.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 2
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 3,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a330",
|
|
||||||
"name": "Contribute",
|
|
||||||
"url": "https://github.com/ajnart/homarr",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://github.com/ajnart/homarr",
|
|
||||||
"externalUrl": "https://github.com/ajnart/homarr",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": []
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/github.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 2
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 4,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "5df743d9-5cb1-457c-85d2-64ff86855652",
|
|
||||||
"name": "Documentation",
|
|
||||||
"url": "https://homarr.dev",
|
|
||||||
"behaviour": {
|
|
||||||
"onClickUrl": "https://homarr.dev",
|
|
||||||
"externalUrl": "https://homarr.dev",
|
|
||||||
"isOpeningNewTab": true
|
|
||||||
},
|
|
||||||
"network": {
|
|
||||||
"enabledStatusChecker": false,
|
|
||||||
"okStatus": [
|
|
||||||
200
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"appearance": {
|
|
||||||
"iconUrl": "/imgs/logo/logo.png"
|
|
||||||
},
|
|
||||||
"integration": {
|
|
||||||
"type": null,
|
|
||||||
"properties": []
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 1,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"widgets": [
|
|
||||||
{
|
|
||||||
"id": "date",
|
|
||||||
"properties": {
|
|
||||||
"display24HourFormat": true
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 1
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 4,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 2,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "weather",
|
|
||||||
"properties": {
|
|
||||||
"displayInFahrenheit": false,
|
|
||||||
"location": "Paris"
|
|
||||||
},
|
|
||||||
"area": {
|
|
||||||
"type": "category",
|
|
||||||
"properties": {
|
|
||||||
"id": "47af36c0-47c1-4e5b-bfc7-ad645ee6a33f"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shape": {
|
|
||||||
"md": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sm": {
|
|
||||||
"location": {
|
|
||||||
"x": 1,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lg": {
|
|
||||||
"location": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"size": {
|
|
||||||
"width": 2,
|
|
||||||
"height": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"settings": {
|
|
||||||
"common": {
|
|
||||||
"searchEngine": {
|
|
||||||
"type": "google",
|
|
||||||
"properties": {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"customization": {
|
|
||||||
"layout": {
|
|
||||||
"enabledLeftSidebar": false,
|
|
||||||
"enabledRightSidebar": false,
|
|
||||||
"enabledDocker": false,
|
|
||||||
"enabledPing": false,
|
|
||||||
"enabledSearchbar": true
|
|
||||||
},
|
|
||||||
"pageTitle": "Homarr v0.11 ⭐️",
|
|
||||||
"logoImageUrl": "/imgs/logo/logo.png",
|
|
||||||
"faviconUrl": "/imgs/favicon/favicon-squared",
|
|
||||||
"backgroundImageUrl": "",
|
|
||||||
"customCss": "",
|
|
||||||
"colors": {
|
|
||||||
"primary": "red",
|
|
||||||
"secondary": "yellow",
|
|
||||||
"shade": 7
|
|
||||||
},
|
|
||||||
"appOpacity": 100
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"xml-js": "^1.6.11",
|
"xml-js": "^1.6.11",
|
||||||
"yarn": "^1.22.19",
|
"yarn": "^1.22.19",
|
||||||
|
"zod": "^3.21.4",
|
||||||
"zustand": "^4.1.4"
|
"zustand": "^4.1.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ export const ChangeWidgetPositionModal = ({
|
|||||||
closeModal(id);
|
closeModal(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const widthData = useWidthData(innerProps.widgetId);
|
const widthData = useWidthData(innerProps.widgetType);
|
||||||
const heightData = useHeightData(innerProps.widgetId);
|
const heightData = useHeightData(innerProps.widgetType);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChangePositionModal
|
<ChangePositionModal
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Grid, Text } from '@mantine/core';
|
import { Grid, Text } from '@mantine/core';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { useConfigContext } from '../../../../../../config/provider';
|
|
||||||
import widgets from '../../../../../../widgets';
|
import widgets from '../../../../../../widgets';
|
||||||
import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
|
import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
|
||||||
import { WidgetElementType } from './WidgetElementType';
|
import { WidgetElementType } from './WidgetElementType';
|
||||||
@@ -13,7 +12,6 @@ export const AvailableIntegrationElements = ({
|
|||||||
onClickBack,
|
onClickBack,
|
||||||
}: AvailableIntegrationElementsProps) => {
|
}: AvailableIntegrationElementsProps) => {
|
||||||
const { t } = useTranslation('layout/element-selector/selector');
|
const { t } = useTranslation('layout/element-selector/selector');
|
||||||
const activeWidgets = useConfigContext().config?.widgets ?? [];
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SelectorBackArrow onClickBack={onClickBack} />
|
<SelectorBackArrow onClickBack={onClickBack} />
|
||||||
@@ -23,11 +21,9 @@ export const AvailableIntegrationElements = ({
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
{Object.entries(widgets)
|
{Object.entries(widgets).map(([k, v]) => (
|
||||||
.filter(([widgetId]) => !activeWidgets.some((aw) => aw.id === widgetId))
|
<WidgetElementType key={k} id={k} image={v.icon} widget={v} />
|
||||||
.map(([k, v]) => (
|
))}
|
||||||
<WidgetElementType key={k} id={k} image={v.icon} widget={v} />
|
|
||||||
))}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useModals } from '@mantine/modals';
|
|||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { IconChecks, TablerIcon } from '@tabler/icons';
|
import { IconChecks, TablerIcon } from '@tabler/icons';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
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';
|
||||||
@@ -32,9 +33,10 @@ export const WidgetElementType = ({ id, image, disabled, widget }: WidgetElement
|
|||||||
(prev) => ({
|
(prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
widgets: [
|
widgets: [
|
||||||
...prev.widgets.filter((w) => w.id !== widget.id),
|
...prev.widgets,
|
||||||
{
|
{
|
||||||
id: widget.id,
|
id: uuidv4(),
|
||||||
|
type: widget.id,
|
||||||
properties: Object.entries(widget.options).reduce((prev, [k, v]) => {
|
properties: Object.entries(widget.options).reduce((prev, [k, v]) => {
|
||||||
const newPrev = prev;
|
const newPrev = prev;
|
||||||
newPrev[k] = v.defaultValue;
|
newPrev[k] = v.defaultValue;
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { DraggableList } from './DraggableList';
|
|||||||
|
|
||||||
export type WidgetEditModalInnerProps = {
|
export type WidgetEditModalInnerProps = {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
|
widgetType: string;
|
||||||
options: IWidget<string, any>['properties'];
|
options: IWidget<string, any>['properties'];
|
||||||
widgetOptions: IWidget<string, any>['properties'];
|
widgetOptions: IWidget<string, any>['properties'];
|
||||||
};
|
};
|
||||||
@@ -37,7 +38,7 @@ export const WidgetsEditModal = ({
|
|||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<WidgetEditModalInnerProps>) => {
|
}: ContextModalProps<WidgetEditModalInnerProps>) => {
|
||||||
const { t } = useTranslation([`modules/${innerProps.widgetId}`, 'common']);
|
const { t } = useTranslation([`modules/${innerProps.widgetType}`, 'common']);
|
||||||
const [moduleProperties, setModuleProperties] = useState(innerProps.options);
|
const [moduleProperties, setModuleProperties] = useState(innerProps.options);
|
||||||
const items = Object.entries(innerProps.widgetOptions ?? {}) as [
|
const items = Object.entries(innerProps.widgetOptions ?? {}) as [
|
||||||
string,
|
string,
|
||||||
@@ -45,7 +46,7 @@ export const WidgetsEditModal = ({
|
|||||||
][];
|
][];
|
||||||
|
|
||||||
// Find the Key in the "Widgets" Object that matches the widgetId
|
// Find the Key in the "Widgets" Object that matches the widgetId
|
||||||
const currentWidgetDefinition = Widgets[innerProps.widgetId as keyof typeof Widgets];
|
const currentWidgetDefinition = Widgets[innerProps.widgetType as keyof typeof Widgets];
|
||||||
const { name: configName } = useConfigContext();
|
const { name: configName } = useConfigContext();
|
||||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ export const WidgetsEditModal = ({
|
|||||||
<WidgetOptionTypeSwitch
|
<WidgetOptionTypeSwitch
|
||||||
key={`${key}.${index}`}
|
key={`${key}.${index}`}
|
||||||
option={option}
|
option={option}
|
||||||
widgetId={innerProps.widgetId}
|
widgetId={innerProps.widgetType}
|
||||||
propName={key}
|
propName={key}
|
||||||
value={value}
|
value={value}
|
||||||
handleChange={handleChange}
|
handleChange={handleChange}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { WidgetsRemoveModalInnerProps } from './WidgetsRemoveModal';
|
|||||||
|
|
||||||
export type WidgetChangePositionModalInnerProps = {
|
export type WidgetChangePositionModalInnerProps = {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
|
widgetType: string;
|
||||||
widget: IWidget<string, any>;
|
widget: IWidget<string, any>;
|
||||||
wrapperColumnCount: number;
|
wrapperColumnCount: number;
|
||||||
};
|
};
|
||||||
@@ -27,8 +28,8 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
|
|||||||
// Match widget.id with WidgetsDefinitions
|
// Match widget.id with WidgetsDefinitions
|
||||||
// First get the keys
|
// First get the keys
|
||||||
const keys = Object.keys(WidgetsDefinitions);
|
const keys = Object.keys(WidgetsDefinitions);
|
||||||
// Then find the key that matches the widget.id
|
// Then find the key that matches the widget.type
|
||||||
const widgetDefinition = keys.find((key) => key === widget.id);
|
const widgetDefinition = keys.find((key) => key === widget.type);
|
||||||
// Then get the widget definition
|
// Then get the widget definition
|
||||||
const widgetDefinitionObject =
|
const widgetDefinitionObject =
|
||||||
WidgetsDefinitions[widgetDefinition as keyof typeof WidgetsDefinitions];
|
WidgetsDefinitions[widgetDefinition as keyof typeof WidgetsDefinitions];
|
||||||
@@ -38,13 +39,8 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
|
|||||||
modal: 'integrationRemove',
|
modal: 'integrationRemove',
|
||||||
title: <Title order={4}>{t('common:remove')}</Title>,
|
title: <Title order={4}>{t('common:remove')}</Title>,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
widgetId: integration,
|
widgetId: widget.id,
|
||||||
},
|
widgetType: integration,
|
||||||
styles: {
|
|
||||||
inner: {
|
|
||||||
position: 'sticky',
|
|
||||||
top: 30,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -55,16 +51,11 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
|
|||||||
size: 'xl',
|
size: 'xl',
|
||||||
title: null,
|
title: null,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
widgetId: integration,
|
widgetId: widget.id,
|
||||||
|
widgetType: integration,
|
||||||
widget,
|
widget,
|
||||||
wrapperColumnCount,
|
wrapperColumnCount,
|
||||||
},
|
},
|
||||||
styles: {
|
|
||||||
inner: {
|
|
||||||
position: 'sticky',
|
|
||||||
top: 30,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,19 +64,13 @@ export const WidgetsMenu = ({ integration, widget }: WidgetsMenuProps) => {
|
|||||||
modal: 'integrationOptions',
|
modal: 'integrationOptions',
|
||||||
title: <Title order={4}>{t('descriptor.settings.title')}</Title>,
|
title: <Title order={4}>{t('descriptor.settings.title')}</Title>,
|
||||||
innerProps: {
|
innerProps: {
|
||||||
widgetId: integration,
|
widgetId: widget.id,
|
||||||
|
widgetType: integration,
|
||||||
options: widget.properties,
|
options: widget.properties,
|
||||||
// Cast as the right type for the correct widget
|
// Cast as the right type for the correct widget
|
||||||
widgetOptions: widgetDefinitionObject.options as any,
|
widgetOptions: widgetDefinitionObject.options as any,
|
||||||
},
|
},
|
||||||
zIndex: 5,
|
zIndex: 5,
|
||||||
styles: {
|
|
||||||
inner: {
|
|
||||||
position: 'sticky',
|
|
||||||
top: 30,
|
|
||||||
maxHeight: '100%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useConfigStore } from '../../../../config/store';
|
|||||||
|
|
||||||
export type WidgetsRemoveModalInnerProps = {
|
export type WidgetsRemoveModalInnerProps = {
|
||||||
widgetId: string;
|
widgetId: string;
|
||||||
|
widgetType: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WidgetsRemoveModal = ({
|
export const WidgetsRemoveModal = ({
|
||||||
@@ -14,7 +15,7 @@ export const WidgetsRemoveModal = ({
|
|||||||
id,
|
id,
|
||||||
innerProps,
|
innerProps,
|
||||||
}: ContextModalProps<WidgetsRemoveModalInnerProps>) => {
|
}: ContextModalProps<WidgetsRemoveModalInnerProps>) => {
|
||||||
const { t } = useTranslation([`modules/${innerProps.widgetId}`, 'common']);
|
const { t } = useTranslation([`modules/${innerProps.widgetType}`, 'common']);
|
||||||
const { name: configName } = useConfigContext();
|
const { name: configName } = useConfigContext();
|
||||||
if (!configName) return null;
|
if (!configName) return null;
|
||||||
const updateConfig = useConfigStore((x) => x.updateConfig);
|
const updateConfig = useConfigStore((x) => x.updateConfig);
|
||||||
@@ -35,7 +36,7 @@ export const WidgetsRemoveModal = ({
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey="common:removeConfirm"
|
i18nKey="common:removeConfirm"
|
||||||
components={[<Text weight={500} />]}
|
components={[<Text weight={500} />]}
|
||||||
values={{ item: innerProps.widgetId }}
|
values={{ item: innerProps.widgetType }}
|
||||||
/>
|
/>
|
||||||
<Group position="right">
|
<Group position="right">
|
||||||
<Button onClick={() => context.closeModal(id)} variant="light">
|
<Button onClick={() => context.closeModal(id)} variant="light">
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{widgets.map((widget) => {
|
{widgets.map((widget) => {
|
||||||
const definition = Widgets[widget.id as keyof typeof Widgets] as
|
const definition = Widgets[widget.type as keyof typeof Widgets] as
|
||||||
| IWidgetDefinition
|
| IWidgetDefinition
|
||||||
| undefined;
|
| undefined;
|
||||||
if (!definition) return null;
|
if (!definition) return null;
|
||||||
@@ -52,7 +52,7 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
|
|||||||
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={widget.id}
|
||||||
{...definition.gridstack}
|
{...definition.gridstack}
|
||||||
{...widget.shape[shapeSize]?.location}
|
{...widget.shape[shapeSize]?.location}
|
||||||
{...widget.shape[shapeSize]?.size}
|
{...widget.shape[shapeSize]?.size}
|
||||||
@@ -60,7 +60,7 @@ export function WrapperContent({ apps, refs, widgets }: WrapperContentProps) {
|
|||||||
<WidgetWrapper
|
<WidgetWrapper
|
||||||
className="grid-stack-item-content"
|
className="grid-stack-item-content"
|
||||||
widget={widget}
|
widget={widget}
|
||||||
widgetId={widget.id}
|
widgetType={widget.type}
|
||||||
WidgetComponent={definition.component}
|
WidgetComponent={definition.component}
|
||||||
/>
|
/>
|
||||||
</GridstackTileWrapper>
|
</GridstackTileWrapper>
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const useConfigStore = create<UseConfigStoreType>((set, get) => ({
|
|||||||
const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value));
|
const previousConfig: ConfigType = JSON.parse(JSON.stringify(currentConfig.value));
|
||||||
|
|
||||||
const updatedConfig = updateCallback(currentConfig.value);
|
const updatedConfig = updateCallback(currentConfig.value);
|
||||||
|
|
||||||
set((old) => ({
|
set((old) => ({
|
||||||
...old,
|
...old,
|
||||||
configs: [
|
configs: [
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export function RadarrMediaDisplay(props: any) {
|
|||||||
export function SonarrMediaDisplay(props: any) {
|
export function SonarrMediaDisplay(props: any) {
|
||||||
const { media }: { media: any } = props;
|
const { media }: { media: any } = props;
|
||||||
const { config } = useConfigContext();
|
const { config } = useConfigContext();
|
||||||
const calendar = config?.widgets.find((w) => w.id === 'calendar');
|
const calendar = config?.widgets.find((w) => w.type === 'calendar');
|
||||||
const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false;
|
const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false;
|
||||||
|
|
||||||
// Find a poster CoverType
|
// Find a poster CoverType
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Consola from 'consola';
|
|||||||
|
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
import { AppIntegrationType } from '../../../types/app';
|
import { AppIntegrationType } from '../../../types/app';
|
||||||
import { getConfig } from '../../../tools/config/getConfig';
|
import { getConfig } from '../../../tools/config/getConfig';
|
||||||
|
|
||||||
@@ -18,28 +19,36 @@ export default async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getQuerySchema = z.object({
|
||||||
|
month: z
|
||||||
|
.string()
|
||||||
|
.regex(/^\d+$/)
|
||||||
|
.transform((x) => parseInt(x, 10)),
|
||||||
|
year: z
|
||||||
|
.string()
|
||||||
|
.regex(/^\d+$/)
|
||||||
|
.transform((x) => parseInt(x, 10)),
|
||||||
|
widgetId: z.string().uuid(),
|
||||||
|
configName: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
// Parse req.body as a AppItem
|
const parseResult = getQuerySchema.safeParse(req.query);
|
||||||
const {
|
|
||||||
month: monthString,
|
|
||||||
year: yearString,
|
|
||||||
configName,
|
|
||||||
} = req.query as { month: string; year: string; configName: string };
|
|
||||||
|
|
||||||
const month = parseInt(monthString, 10);
|
if (!parseResult.success) {
|
||||||
const year = parseInt(yearString, 10);
|
|
||||||
|
|
||||||
if (Number.isNaN(month) || Number.isNaN(year) || !configName) {
|
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
message: 'Missing required parameter in url: year, month or configName',
|
message: 'Invalid query parameters, please specify the widgetId, month, year and configName',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse req.body as a AppItem
|
||||||
|
const { month, year, widgetId, configName } = parseResult.data;
|
||||||
|
|
||||||
const config = getConfig(configName);
|
const config = getConfig(configName);
|
||||||
|
|
||||||
// Find the calendar widget in the config
|
// Find the calendar widget in the config
|
||||||
const calendar = config.widgets.find((w) => w.id === 'calendar');
|
const calendar = config.widgets.find((w) => w.type === 'calendar' && w.id === widgetId);
|
||||||
const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false;
|
const useSonarrv4 = calendar?.properties.useSonarrv4 ?? false;
|
||||||
|
|
||||||
const mediaAppIntegrationTypes: AppIntegrationType['type'][] = [
|
const mediaAppIntegrationTypes: AppIntegrationType['type'][] = [
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { z } from 'zod';
|
||||||
import { getConfig } from '../../../../tools/config/getConfig';
|
import { getConfig } from '../../../../tools/config/getConfig';
|
||||||
import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
|
import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
const getQuerySchema = z.object({
|
||||||
const { configName } = req.query;
|
configName: z.string(),
|
||||||
|
widgetId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
if (!configName || typeof configName !== 'string') {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const parseResult = getQuerySchema.safeParse(req.query);
|
||||||
|
|
||||||
|
if (!parseResult.success) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
message: 'Missing required configName in url',
|
statusCode: 400,
|
||||||
|
message: 'Invalid query parameters, please specify the widgetId and configName',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { configName, widgetId } = parseResult.data;
|
||||||
|
|
||||||
const config = getConfig(configName);
|
const config = getConfig(configName);
|
||||||
|
|
||||||
const dashDotWidget = config.widgets.find((x) => x.id === 'dashdot');
|
const dashDotWidget = config.widgets.find((x) => x.type === 'dashdot' && x.id === widgetId);
|
||||||
|
|
||||||
if (!dashDotWidget) {
|
if (!dashDotWidget) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
import { NextApiRequest, NextApiResponse } from 'next';
|
||||||
|
import { z } from 'zod';
|
||||||
import { getConfig } from '../../../../tools/config/getConfig';
|
import { getConfig } from '../../../../tools/config/getConfig';
|
||||||
import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
|
import { IDashDotTile } from '../../../../widgets/dashDot/DashDotTile';
|
||||||
|
|
||||||
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
const getQuerySchema = z.object({
|
||||||
const { configName } = req.query;
|
configName: z.string(),
|
||||||
|
widgetId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
if (!configName || typeof configName !== 'string') {
|
async function Get(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const parseResult = getQuerySchema.safeParse(req.query);
|
||||||
|
|
||||||
|
if (!parseResult.success) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
message: 'Missing required configName in url',
|
statusCode: 400,
|
||||||
|
message: 'Invalid query parameters, please specify the widgetId and configName',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { configName, widgetId } = parseResult.data;
|
||||||
|
|
||||||
const config = getConfig(configName);
|
const config = getConfig(configName);
|
||||||
const dashDotWidget = config.widgets.find((x) => x.id === 'dashdot');
|
const dashDotWidget = config.widgets.find((x) => x.type === 'dashdot' && x.id === widgetId);
|
||||||
|
|
||||||
if (!dashDotWidget) {
|
if (!dashDotWidget) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
|||||||
|
|
||||||
import Parser from 'rss-parser';
|
import Parser from 'rss-parser';
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
import { getConfig } from '../../../../tools/config/getConfig';
|
import { getConfig } from '../../../../tools/config/getConfig';
|
||||||
import { IRssWidget } from '../../../../widgets/rss/RssWidgetTile';
|
import { IRssWidget } from '../../../../widgets/rss/RssWidgetTile';
|
||||||
import { Stopwatch } from '../../../../tools/shared/time/stopwatch.tool';
|
import { Stopwatch } from '../../../../tools/shared/time/stopwatch.tool';
|
||||||
@@ -25,11 +26,24 @@ const parser: Parser<any, CustomItem> = new Parser({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getQuerySchema = z.object({
|
||||||
|
widgetId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
export const Get = async (request: NextApiRequest, response: NextApiResponse) => {
|
export const Get = async (request: NextApiRequest, response: NextApiResponse) => {
|
||||||
const configName = getCookie('config-name', { req: request });
|
const configName = getCookie('config-name', { req: request });
|
||||||
const config = getConfig(configName?.toString() ?? 'default');
|
const config = getConfig(configName?.toString() ?? 'default');
|
||||||
|
|
||||||
const rssWidget = config.widgets.find((x) => x.id === 'rss') as IRssWidget | undefined;
|
const parseResult = getQuerySchema.safeParse(request.query);
|
||||||
|
|
||||||
|
if (!parseResult.success) {
|
||||||
|
response.status(400).json({ message: 'invalid query parameters, please specify the widgetId' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rssWidget = config.widgets.find(
|
||||||
|
(x) => x.type === 'rss' && x.id === parseResult.data.widgetId
|
||||||
|
) as IRssWidget | undefined;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!rssWidget ||
|
!rssWidget ||
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import Consola from 'consola';
|
import Consola from 'consola';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { BackendConfigType, ConfigType } from '../../types/config';
|
import { BackendConfigType, ConfigType } from '../../types/config';
|
||||||
import { backendMigrateConfig } from './backendMigrateConfig';
|
import { backendMigrateConfig } from './backendMigrateConfig';
|
||||||
import { configExists } from './configExists';
|
import { configExists } from './configExists';
|
||||||
import { getFallbackConfig } from './getFallbackConfig';
|
import { getFallbackConfig } from './getFallbackConfig';
|
||||||
import { readConfig } from './readConfig';
|
import { readConfig } from './readConfig';
|
||||||
|
import { writeConfig } from './writeConfig';
|
||||||
|
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
||||||
|
|
||||||
export const getConfig = (name: string): BackendConfigType => {
|
export const getConfig = (name: string): BackendConfigType => {
|
||||||
if (!configExists(name)) return getFallbackConfig() as unknown as ConfigType;
|
if (!configExists(name)) return getFallbackConfig() as unknown as ConfigType;
|
||||||
@@ -12,9 +16,29 @@ export const getConfig = (name: string): BackendConfigType => {
|
|||||||
// to the new format.
|
// to the new format.
|
||||||
const config = readConfig(name);
|
const config = readConfig(name);
|
||||||
if (config.schemaVersion === undefined) {
|
if (config.schemaVersion === undefined) {
|
||||||
Consola.log('Migrating config file...', config);
|
Consola.log('Migrating config file...', config.name);
|
||||||
return backendMigrateConfig(config, name);
|
return backendMigrateConfig(config, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
let backendConfig = config as BackendConfigType;
|
||||||
|
|
||||||
|
if (backendConfig.widgets.some((widget) => !uuidRegex.test(widget.id))) {
|
||||||
|
backendConfig = {
|
||||||
|
...backendConfig,
|
||||||
|
widgets: backendConfig.widgets.map((widget) => ({
|
||||||
|
...widget,
|
||||||
|
id: uuidRegex.test(widget.id) ? widget.id : uuidv4(),
|
||||||
|
type: !uuidRegex.test(widget.id) ? widget.id : widget.type,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
Consola.log(
|
||||||
|
'Migrating config file to multiple widgets...',
|
||||||
|
backendConfig.configProperties.name
|
||||||
|
);
|
||||||
|
|
||||||
|
writeConfig(backendConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return backendConfig;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
case 'torrent-status':
|
case 'torrent-status':
|
||||||
case 'Torrent':
|
case 'Torrent':
|
||||||
return {
|
return {
|
||||||
id: 'torrents-status',
|
id: uuidv4(),
|
||||||
|
type: 'torrents-status',
|
||||||
properties: {
|
properties: {
|
||||||
refreshInterval: 10,
|
refreshInterval: 10,
|
||||||
displayCompletedTorrents: oldModule.options?.hideComplete?.value ?? false,
|
displayCompletedTorrents: oldModule.options?.hideComplete?.value ?? false,
|
||||||
@@ -199,7 +200,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
} as ITorrent;
|
} as ITorrent;
|
||||||
case 'weather':
|
case 'weather':
|
||||||
return {
|
return {
|
||||||
id: 'weather',
|
id: uuidv4(),
|
||||||
|
type: 'weather',
|
||||||
properties: {
|
properties: {
|
||||||
displayInFahrenheit: oldModule.options?.freedomunit?.value ?? false,
|
displayInFahrenheit: oldModule.options?.freedomunit?.value ?? false,
|
||||||
location: oldModule.options?.location?.value ?? 'Paris',
|
location: oldModule.options?.location?.value ?? 'Paris',
|
||||||
@@ -216,7 +218,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
case 'Dash.': {
|
case 'Dash.': {
|
||||||
const oldDashDotService = config.services.find((service) => service.type === 'Dash.');
|
const oldDashDotService = config.services.find((service) => service.type === 'Dash.');
|
||||||
return {
|
return {
|
||||||
id: 'dashdot',
|
id: uuidv4(),
|
||||||
|
type: 'dashdot',
|
||||||
properties: {
|
properties: {
|
||||||
url: oldModule.options?.url?.value ?? oldDashDotService?.url ?? '',
|
url: oldModule.options?.url?.value ?? oldDashDotService?.url ?? '',
|
||||||
cpuMultiView: oldModule.options?.cpuMultiView?.value ?? false,
|
cpuMultiView: oldModule.options?.cpuMultiView?.value ?? false,
|
||||||
@@ -235,7 +238,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
}
|
}
|
||||||
case 'date':
|
case 'date':
|
||||||
return {
|
return {
|
||||||
id: 'date',
|
id: uuidv4(),
|
||||||
|
type: 'date',
|
||||||
properties: {
|
properties: {
|
||||||
display24HourFormat: oldModule.options?.full?.value ?? true,
|
display24HourFormat: oldModule.options?.full?.value ?? true,
|
||||||
},
|
},
|
||||||
@@ -249,7 +253,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
} as IDateWidget;
|
} as IDateWidget;
|
||||||
case 'Download Speed' || 'dlspeed':
|
case 'Download Speed' || 'dlspeed':
|
||||||
return {
|
return {
|
||||||
id: 'dlspeed',
|
id: uuidv4(),
|
||||||
|
type: 'dlspeed',
|
||||||
properties: {},
|
properties: {},
|
||||||
area: {
|
area: {
|
||||||
type: 'wrapper',
|
type: 'wrapper',
|
||||||
@@ -261,7 +266,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
} as ITorrentNetworkTraffic;
|
} as ITorrentNetworkTraffic;
|
||||||
case 'calendar':
|
case 'calendar':
|
||||||
return {
|
return {
|
||||||
id: 'calendar',
|
id: uuidv4(),
|
||||||
|
type: 'calendar',
|
||||||
properties: {},
|
properties: {},
|
||||||
area: {
|
area: {
|
||||||
type: 'wrapper',
|
type: 'wrapper',
|
||||||
@@ -273,7 +279,8 @@ const migrateModules = (config: Config): IWidget<string, any>[] => {
|
|||||||
} as ICalendarWidget;
|
} as ICalendarWidget;
|
||||||
case 'usenet':
|
case 'usenet':
|
||||||
return {
|
return {
|
||||||
id: 'usenet',
|
id: uuidv4(),
|
||||||
|
type: 'usenet',
|
||||||
properties: {},
|
properties: {},
|
||||||
area: {
|
area: {
|
||||||
type: 'wrapper',
|
type: 'wrapper',
|
||||||
|
|||||||
10
src/tools/config/writeConfig.ts
Normal file
10
src/tools/config/writeConfig.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import { BackendConfigType } from '../../types/config';
|
||||||
|
import { generateConfigPath } from './generateConfigPath';
|
||||||
|
|
||||||
|
export function writeConfig(config: BackendConfigType) {
|
||||||
|
const path = generateConfigPath(config.configProperties.name);
|
||||||
|
return fs.writeFileSync(path, JSON.stringify(config, null, 4), {
|
||||||
|
encoding: 'utf8',
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComponentType, useMemo } from 'react';
|
import { ComponentType } from 'react';
|
||||||
import Widgets from '.';
|
import Widgets from '.';
|
||||||
import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
|
import { HomarrCardWrapper } from '../components/Dashboard/Tiles/HomarrCardWrapper';
|
||||||
import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
|
import { WidgetsMenu } from '../components/Dashboard/Tiles/Widgets/WidgetsMenu';
|
||||||
@@ -6,7 +6,7 @@ import ErrorBoundary from './boundary';
|
|||||||
import { IWidget } from './widgets';
|
import { IWidget } from './widgets';
|
||||||
|
|
||||||
interface WidgetWrapperProps {
|
interface WidgetWrapperProps {
|
||||||
widgetId: string;
|
widgetType: string;
|
||||||
widget: IWidget<string, any>;
|
widget: IWidget<string, any>;
|
||||||
className: string;
|
className: string;
|
||||||
WidgetComponent: ComponentType<{ widget: IWidget<string, any> }>;
|
WidgetComponent: ComponentType<{ widget: IWidget<string, any> }>;
|
||||||
@@ -14,26 +14,24 @@ interface WidgetWrapperProps {
|
|||||||
|
|
||||||
// If a property has no value, set it to the default value
|
// If a property has no value, set it to the default value
|
||||||
const useWidget = <T extends IWidget<string, any>>(widget: T): T => {
|
const useWidget = <T extends IWidget<string, any>>(widget: T): T => {
|
||||||
const definition = Widgets[widget.id as keyof typeof Widgets];
|
const definition = Widgets[widget.type as keyof typeof Widgets];
|
||||||
|
|
||||||
return useMemo(() => {
|
const newProps = { ...widget.properties };
|
||||||
const newProps = { ...widget.properties };
|
|
||||||
|
|
||||||
Object.entries(definition.options).forEach(([key, option]) => {
|
Object.entries(definition.options).forEach(([key, option]) => {
|
||||||
if (newProps[key] == null) {
|
if (newProps[key] == null) {
|
||||||
newProps[key] = option.defaultValue;
|
newProps[key] = option.defaultValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...widget,
|
...widget,
|
||||||
properties: newProps,
|
properties: newProps,
|
||||||
};
|
};
|
||||||
}, [widget]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WidgetWrapper = ({
|
export const WidgetWrapper = ({
|
||||||
widgetId,
|
widgetType,
|
||||||
widget,
|
widget,
|
||||||
className,
|
className,
|
||||||
WidgetComponent,
|
WidgetComponent,
|
||||||
@@ -43,7 +41,7 @@ export const WidgetWrapper = ({
|
|||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<HomarrCardWrapper className={className}>
|
<HomarrCardWrapper className={className}>
|
||||||
<WidgetsMenu integration={widgetId} widget={widgetWithDefaultProps} />
|
<WidgetsMenu integration={widgetType} widget={widgetWithDefaultProps} />
|
||||||
<WidgetComponent widget={widgetWithDefaultProps} />
|
<WidgetComponent widget={widgetWithDefaultProps} />
|
||||||
</HomarrCardWrapper>
|
</HomarrCardWrapper>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
|
|||||||
mt="md"
|
mt="md"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
{(this.props.t('modal.reportButton'))}
|
{this.props.t('modal.reportButton')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ function CalendarTile({ widget }: CalendarTileProps) {
|
|||||||
await fetch(
|
await fetch(
|
||||||
`/api/modules/calendar?year=${month.getFullYear()}&month=${
|
`/api/modules/calendar?year=${month.getFullYear()}&month=${
|
||||||
month.getMonth() + 1
|
month.getMonth() + 1
|
||||||
}&configName=${configName}`
|
}&configName=${configName}&widgetId=${widget.id}`
|
||||||
)
|
)
|
||||||
).json()) as MediasType,
|
).json()) as MediasType,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ import { DashDotInfo } from './DashDotCompactNetwork';
|
|||||||
|
|
||||||
interface DashDotCompactStorageProps {
|
interface DashDotCompactStorageProps {
|
||||||
info: DashDotInfo;
|
info: DashDotInfo;
|
||||||
|
widgetId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DashDotCompactStorage = ({ info }: DashDotCompactStorageProps) => {
|
export const DashDotCompactStorage = ({ info, widgetId }: DashDotCompactStorageProps) => {
|
||||||
const { t } = useTranslation('modules/dashdot');
|
const { t } = useTranslation('modules/dashdot');
|
||||||
const { data: storageLoad } = useDashDotStorage();
|
const { data: storageLoad } = useDashDotStorage(widgetId);
|
||||||
|
|
||||||
const totalUsed = calculateTotalLayoutSize({
|
const totalUsed = calculateTotalLayoutSize({
|
||||||
layout: storageLoad?.layout ?? [],
|
layout: storageLoad?.layout ?? [],
|
||||||
@@ -50,7 +51,7 @@ interface CalculateTotalLayoutSizeProps<TLayoutItem> {
|
|||||||
key: keyof TLayoutItem;
|
key: keyof TLayoutItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useDashDotStorage = () => {
|
const useDashDotStorage = (widgetId: string) => {
|
||||||
const { name: configName, config } = useConfigContext();
|
const { name: configName, config } = useConfigContext();
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -58,17 +59,18 @@ const useDashDotStorage = () => {
|
|||||||
'dashdot/storage',
|
'dashdot/storage',
|
||||||
{
|
{
|
||||||
configName,
|
configName,
|
||||||
url: config?.widgets.find((x) => x.id === 'dashdot')?.properties.url,
|
url: config?.widgets.find((x) => x.type === 'dashdot')?.properties.url,
|
||||||
|
widgetId,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: () => fetchDashDotStorageLoad(configName),
|
queryFn: () => fetchDashDotStorageLoad(configName, widgetId),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetchDashDotStorageLoad(configName: string | undefined) {
|
async function fetchDashDotStorageLoad(configName: string | undefined, widgetId: string) {
|
||||||
if (!configName) throw new Error('configName is undefined');
|
if (!configName) throw new Error('configName is undefined');
|
||||||
return (await (
|
return (await (
|
||||||
await axios.get('/api/modules/dashdot/storage', { params: { configName } })
|
await axios.get('/api/modules/dashdot/storage', { params: { configName, widgetId } })
|
||||||
).data) as DashDotStorageLoad;
|
).data) as DashDotStorageLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface DashDotGraphProps {
|
|||||||
dashDotUrl: string;
|
dashDotUrl: string;
|
||||||
usePercentages: boolean;
|
usePercentages: boolean;
|
||||||
info: DashDotInfo;
|
info: DashDotInfo;
|
||||||
|
widgetId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DashDotGraph = ({
|
export const DashDotGraph = ({
|
||||||
@@ -21,12 +22,13 @@ export const DashDotGraph = ({
|
|||||||
dashDotUrl,
|
dashDotUrl,
|
||||||
usePercentages,
|
usePercentages,
|
||||||
info,
|
info,
|
||||||
|
widgetId,
|
||||||
}: DashDotGraphProps) => {
|
}: DashDotGraphProps) => {
|
||||||
const { t } = useTranslation('modules/dashdot');
|
const { t } = useTranslation('modules/dashdot');
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
|
|
||||||
if (graph === 'storage' && isCompact) {
|
if (graph === 'storage' && isCompact) {
|
||||||
return <DashDotCompactStorage info={info} />;
|
return <DashDotCompactStorage info={info} widgetId={widgetId} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (graph === 'network' && isCompact) {
|
if (graph === 'network' && isCompact) {
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ function DashDotTile({ widget }: DashDotTileProps) {
|
|||||||
const { data: info } = useDashDotInfo({
|
const { data: info } = useDashDotInfo({
|
||||||
dashDotUrl,
|
dashDotUrl,
|
||||||
enabled: !detectedProtocolDowngrade,
|
enabled: !detectedProtocolDowngrade,
|
||||||
|
widgetId: widget.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (detectedProtocolDowngrade) {
|
if (detectedProtocolDowngrade) {
|
||||||
@@ -197,6 +198,7 @@ function DashDotTile({ widget }: DashDotTileProps) {
|
|||||||
isCompact={g.subValues.compactView ?? false}
|
isCompact={g.subValues.compactView ?? false}
|
||||||
multiView={g.subValues.multiView ?? false}
|
multiView={g.subValues.multiView ?? false}
|
||||||
usePercentages={usePercentages}
|
usePercentages={usePercentages}
|
||||||
|
widgetId={widget.id}
|
||||||
/>
|
/>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
))}
|
))}
|
||||||
@@ -207,7 +209,15 @@ function DashDotTile({ widget }: DashDotTileProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useDashDotInfo = ({ dashDotUrl, enabled }: { dashDotUrl: string; enabled: boolean }) => {
|
const useDashDotInfo = ({
|
||||||
|
dashDotUrl,
|
||||||
|
enabled,
|
||||||
|
widgetId,
|
||||||
|
}: {
|
||||||
|
dashDotUrl: string;
|
||||||
|
enabled: boolean;
|
||||||
|
widgetId: string;
|
||||||
|
}) => {
|
||||||
const { name: configName } = useConfigContext();
|
const { name: configName } = useConfigContext();
|
||||||
return useQuery({
|
return useQuery({
|
||||||
refetchInterval: 50000,
|
refetchInterval: 50000,
|
||||||
@@ -218,15 +228,15 @@ const useDashDotInfo = ({ dashDotUrl, enabled }: { dashDotUrl: string; enabled:
|
|||||||
dashDotUrl,
|
dashDotUrl,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
queryFn: () => fetchDashDotInfo(configName),
|
queryFn: () => fetchDashDotInfo(configName, widgetId),
|
||||||
enabled,
|
enabled,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchDashDotInfo = async (configName: string | undefined) => {
|
const fetchDashDotInfo = async (configName: string | undefined, widgetId: string) => {
|
||||||
if (!configName) return {} as DashDotInfo;
|
if (!configName) return {} as DashDotInfo;
|
||||||
return (await (
|
return (await (
|
||||||
await axios.get('/api/modules/dashdot/info', { params: { configName } })
|
await axios.get('/api/modules/dashdot/info', { params: { configName, widgetId } })
|
||||||
).data) as DashDotInfo;
|
).data) as DashDotInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import dayjs from 'dayjs';
|
|||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { defineWidget } from '../helper';
|
|
||||||
import { IWidget } from '../widgets';
|
import { IWidget } from '../widgets';
|
||||||
|
import { defineWidget } from '../helper';
|
||||||
|
|
||||||
const definition = defineWidget({
|
const definition = defineWidget({
|
||||||
id: 'rss',
|
id: 'rss',
|
||||||
@@ -56,11 +56,11 @@ interface RssTileProps {
|
|||||||
widget: IRssWidget;
|
widget: IRssWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useGetRssFeed = (feedUrl: string) =>
|
export const useGetRssFeed = (feedUrl: string, widgetId: string) =>
|
||||||
useQuery({
|
useQuery({
|
||||||
queryKey: ['rss-feed', feedUrl],
|
queryKey: ['rss-feed', feedUrl],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch('/api/modules/rss');
|
const response = await fetch(`/api/modules/rss?widgetId=${widgetId}`);
|
||||||
return response.json();
|
return response.json();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -68,7 +68,8 @@ const useGetRssFeed = (feedUrl: string) =>
|
|||||||
function RssTile({ widget }: RssTileProps) {
|
function RssTile({ widget }: RssTileProps) {
|
||||||
const { t } = useTranslation('modules/rss');
|
const { t } = useTranslation('modules/rss');
|
||||||
const { data, isLoading, isFetching, isError, refetch } = useGetRssFeed(
|
const { data, isLoading, isFetching, isError, refetch } = useGetRssFeed(
|
||||||
widget.properties.rssFeedUrl
|
widget.properties.rssFeedUrl,
|
||||||
|
widget.id
|
||||||
);
|
);
|
||||||
const { classes } = useStyles();
|
const { classes } = useStyles();
|
||||||
const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false);
|
const [loadingOverlayVisible, setLoadingOverlayVisible] = useState(false);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import { ShapeType } from '../types/shape';
|
|||||||
|
|
||||||
// Type of widgets which are saved to config
|
// Type of widgets which are saved to config
|
||||||
export type IWidget<TKey extends string, TDefinition extends IWidgetDefinition> = {
|
export type IWidget<TKey extends string, TDefinition extends IWidgetDefinition> = {
|
||||||
id: TKey;
|
id: string;
|
||||||
|
type: TKey;
|
||||||
properties: {
|
properties: {
|
||||||
[key in keyof TDefinition['options']]: MakeLessSpecific<
|
[key in keyof TDefinition['options']]: MakeLessSpecific<
|
||||||
TDefinition['options'][key]['defaultValue']
|
TDefinition['options'][key]['defaultValue']
|
||||||
|
|||||||
@@ -4914,6 +4914,7 @@ __metadata:
|
|||||||
vitest-fetch-mock: ^0.2.2
|
vitest-fetch-mock: ^0.2.2
|
||||||
xml-js: ^1.6.11
|
xml-js: ^1.6.11
|
||||||
yarn: ^1.22.19
|
yarn: ^1.22.19
|
||||||
|
zod: ^3.21.4
|
||||||
zustand: ^4.1.4
|
zustand: ^4.1.4
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
@@ -8760,6 +8761,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"zod@npm:^3.21.4":
|
||||||
|
version: 3.21.4
|
||||||
|
resolution: "zod@npm:3.21.4"
|
||||||
|
checksum: f185ba87342ff16f7a06686767c2b2a7af41110c7edf7c1974095d8db7a73792696bcb4a00853de0d2edeb34a5b2ea6a55871bc864227dace682a0a28de33e1f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"zustand@npm:^4.1.4":
|
"zustand@npm:^4.1.4":
|
||||||
version: 4.3.6
|
version: 4.3.6
|
||||||
resolution: "zustand@npm:4.3.6"
|
resolution: "zustand@npm:4.3.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user