Implemented RSS support.

This commit is contained in:
Tom Butcher 2026-06-20 22:06:02 +01:00
parent cb24c6ff48
commit 133adece5f
10 changed files with 105 additions and 11 deletions

View File

@ -2,6 +2,9 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"> <svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.069972,0,0,0.069972,-2.986098,-11.802594)"> <g transform="matrix(0.069972,0,0,0.069972,-2.986098,-11.802594)">
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226ZM802,325L802,401C731.333,401.667 636.667,401.667 518,401L519,326L802,325ZM481,326C481.667,342.667 482,367.667 482,401L200,401L200,326L481,326ZM518,438L802,438L802,514L518,514L518,438ZM482,438L482,514L200,513L200,439L482,438ZM554,550C590.667,550 645.667,550.333 719,551L801,551L801,627L518,628L518,550L554,550ZM482,550L482,628L200,628L200,551L482,551L482,550ZM518,665L801,665C801.667,677 801.667,695.333 801,720L801,740L519,740L518,665ZM311,700C327.667,700 343.833,703.5 359.5,710.5C375.167,717.5 388.333,727.333 399,740C413.667,756.667 422,776.5 424,799.5C426,822.5 422,844.5 412,865.5C402,886.5 387,902 367,912C348.333,922.667 327.5,927.167 304.5,925.5C281.5,923.833 260.833,916.333 242.5,903C224.167,889.667 212,872.333 206,851C198,831.667 196.333,811.333 201,790C205.667,768.667 215.333,750.167 230,734.5C244.667,718.833 262.333,708.667 283,704C292.333,701.333 301.667,700 311,700Z" style="fill:rgb(255,152,0);fill-rule:nonzero;"/> <path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226Z" style="fill:rgb(255,152,0);fill-rule:nonzero;"/>
<g transform="matrix(14.291431,0,0,14.291431,42.675613,168.675956)">
<path d="M35.778,26.682C38.344,26.682 42.193,26.705 47.324,26.752L53.061,26.752L53.061,32.07L33.259,32.14L33.259,26.682L35.778,26.682ZM53.131,10.938L53.131,16.256C48.187,16.303 41.563,16.303 33.259,16.256L33.329,11.008L53.131,10.938ZM30.74,18.845L30.74,24.163L11.008,24.093L11.008,18.915L30.74,18.845ZM30.67,11.008C30.717,12.175 30.74,13.924 30.74,16.256L11.008,16.256L11.008,11.008L30.67,11.008ZM18.775,37.178C19.941,37.178 21.073,37.423 22.169,37.913C23.265,38.402 24.186,39.09 24.933,39.977C25.959,41.143 26.542,42.531 26.682,44.14C26.822,45.749 26.542,47.289 25.842,48.758C25.143,50.228 24.093,51.312 22.694,52.012C21.387,52.758 19.93,53.073 18.32,52.956C16.711,52.84 15.265,52.315 13.982,51.382C12.699,50.449 11.848,49.236 11.428,47.744C10.868,46.391 10.752,44.968 11.078,43.475C11.405,41.983 12.081,40.688 13.107,39.592C14.134,38.496 15.37,37.784 16.816,37.458C17.469,37.271 18.122,37.178 18.775,37.178ZM33.259,18.845L53.131,18.845L53.131,24.163L33.259,24.163L33.259,18.845ZM30.74,26.752L30.74,32.14L11.008,32.14L11.008,26.752L30.74,26.752ZM33.259,34.729L53.061,34.729C53.108,35.568 53.108,36.851 53.061,38.577L53.061,39.977L33.329,39.977L33.259,34.729Z" style="fill:white;"/>
</g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

18
assets/icons/rssicon.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(0.069972,0,0,0.069972,-2.986098,-11.802594)">
<path d="M242,226C206,232 174.833,248.333 148.5,275C122.167,301.667 106,333 100,369L100,884C106,919.333 122,950 148,976C174,1002 204.333,1018.333 239,1025L243,1026L758,1026L761,1025C796.333,1018.333 826.833,1001.833 852.5,975.5C878.167,949.167 894,918.333 900,883L900,368C894,332.667 877.667,301.667 851,275C824.333,248.333 793,232 757,226L242,226Z" style="fill:rgb(204,93,21);fill-rule:nonzero;"/>
<g transform="matrix(1,0,0,1,0.001401,0)">
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
<circle cx="68" cy="189" r="24" style="fill:white;"/>
</g>
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
<path d="M160,213L126,213C126,168.016 88.984,131 44,131L44,97C107.636,97 160,149.364 160,213Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(3.343963,0,0,3.343963,60.268889,206.334409)">
<path d="M184,213C184,136.198 120.802,73 44,73L44,38C140.002,38 219,116.998 219,213L184,213Z" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -52,10 +52,10 @@ const RegenerateAppPasswordSecret = ({ id }) => {
<Flex justify='center' style={{ minWidth: '395px' }}> <Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'> <Flex justify='center'>
<Flex gap='small' align='center' justify='center'> <Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={appPassword} />
<Text code style={{ fontSize: '18px' }}> <Text code style={{ fontSize: '18px' }}>
{appPassword || '••••••••••••••••••••••••••••••••'} {appPassword || '••••••••••••••••••••••••••••••••'}
</Text> </Text>
<CopyButton size='default' text={appPassword} />
<Button <Button
type='text' type='text'
loading={loading} loading={loading}

View File

@ -85,11 +85,6 @@ const HostOTP = ({ id }) => {
> >
<Flex justify='center'> <Flex justify='center'>
<Flex gap={'small'} align='center' justify='center'> <Flex gap={'small'} align='center' justify='center'>
<CopyButton
size='default'
text={hostObject?.otp}
disabled={loading}
/>
<div> <div>
<Input.OTP <Input.OTP
disabled={loading} disabled={loading}
@ -100,6 +95,11 @@ const HostOTP = ({ id }) => {
onPaste={(e) => e.preventDefault()} // prevent pasting onPaste={(e) => e.preventDefault()} // prevent pasting
/> />
</div> </div>
<CopyButton
size='default'
text={hostObject?.otp}
disabled={loading}
/>
<div style={{ margin: '0 6px 0 8px', paddingBottom: '5px' }}> <div style={{ margin: '0 6px 0 8px', paddingBottom: '5px' }}>
{loading ? ( {loading ? (
<Text> <Text>

View File

@ -47,11 +47,10 @@ const SetAppPassword = ({ id }) => {
<Flex justify='center' style={{ minWidth: '395px' }}> <Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'> <Flex justify='center'>
<Flex gap='small' align='center' justify='center'> <Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={appPassword} />
<Text code style={{ fontSize: '18px' }}> <Text code style={{ fontSize: '18px' }}>
{appPassword || '••••••••••••••••••••••••••••••••'} {appPassword || '••••••••••••••••••••••••••••••••'}
</Text> </Text>
<CopyButton size='default' text={appPassword} />
<Button <Button
type='texts' type='texts'
loading={loading} loading={loading}

View File

@ -35,10 +35,10 @@ const ConfigureMarketplace = ({
> >
<Flex justify='center'> <Flex justify='center'>
<Flex gap='small' align='center' justify='center'> <Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={callbackUrl} />
<Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}> <Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}>
{callbackUrl} {callbackUrl}
</Text> </Text>
<CopyButton size='default' text={callbackUrl} />
</Flex> </Flex>
</Flex> </Flex>
<Flex justify='center'> <Flex justify='center'>

View File

@ -3,11 +3,13 @@ import { useState, useContext } from 'react'
import { Button, Dropdown, Modal } from 'antd' import { Button, Dropdown, Modal } from 'antd'
import ExcelIcon from '../../Icons/ExcelIcon' import ExcelIcon from '../../Icons/ExcelIcon'
import ODataIcon from '../../Icons/ODataIcon' import ODataIcon from '../../Icons/ODataIcon'
import RssIcon from '../../Icons/RssIcon'
import CsvIcon from '../../Icons/CsvIcon' import CsvIcon from '../../Icons/CsvIcon'
import DownloadIcon from '../../Icons/DownloadIcon' import DownloadIcon from '../../Icons/DownloadIcon'
import OpenAppIcon from '../../Icons/OpenAppIcon' import OpenAppIcon from '../../Icons/OpenAppIcon'
import ExportIcon from '../../Icons/ExportIcon' import ExportIcon from '../../Icons/ExportIcon'
import ODataURL from './ODataURL' import ODataURL from './ODataURL'
import RSSFeedURL from './RSSFeedURL'
import { ApiServerContext } from '../context/ApiServerContext' import { ApiServerContext } from '../context/ApiServerContext'
const ExportListButton = ({ const ExportListButton = ({
@ -17,6 +19,7 @@ const ExportListButton = ({
...buttonProps ...buttonProps
}) => { }) => {
const [odataModalOpen, setOdataModalOpen] = useState(false) const [odataModalOpen, setOdataModalOpen] = useState(false)
const [rssModalOpen, setRssModalOpen] = useState(false)
const [excelLoading, setExcelLoading] = useState(false) const [excelLoading, setExcelLoading] = useState(false)
const [csvLoading, setCsvLoading] = useState(false) const [csvLoading, setCsvLoading] = useState(false)
const { exportToExcel, exportToCsv } = useContext(ApiServerContext) const { exportToExcel, exportToCsv } = useContext(ApiServerContext)
@ -76,6 +79,12 @@ const ExportListButton = ({
label: 'OData Connection', label: 'OData Connection',
icon: <ODataIcon />, icon: <ODataIcon />,
onClick: () => setOdataModalOpen(true) onClick: () => setOdataModalOpen(true)
},
{
key: 'rss',
label: 'RSS Feed Connection',
icon: <RssIcon />,
onClick: () => setRssModalOpen(true)
} }
] ]
@ -103,6 +112,15 @@ const ExportListButton = ({
> >
<ODataURL objectType={objectType} /> <ODataURL objectType={objectType} />
</Modal> </Modal>
<Modal
open={rssModalOpen}
destroyOnClose
width={750}
onCancel={() => setRssModalOpen(false)}
footer={null}
>
<RSSFeedURL objectType={objectType} />
</Modal>
</> </>
) )
} }

View File

@ -26,10 +26,10 @@ const ODataURL = ({ objectType }) => {
<Flex justify='center' style={{ minWidth: '395px' }}> <Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'> <Flex justify='center'>
<Flex gap='small' align='center' justify='center'> <Flex gap='small' align='center' justify='center'>
<CopyButton size='default' text={odataUrl} />
<Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}> <Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}>
{odataUrl} {odataUrl}
</Text> </Text>
<CopyButton size='default' text={odataUrl} />
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -0,0 +1,50 @@
import PropTypes from 'prop-types'
import { useContext } from 'react'
import { Result, Typography, Flex } from 'antd'
import CopyButton from './CopyButton'
import RssIcon from '../../Icons/RssIcon'
import config from '../../../config'
import { AuthContext } from '../context/AuthContext'
const { Text } = Typography
const RSSFeedURL = ({ objectType }) => {
const { userProfile } = useContext(AuthContext)
const baseUrl = config.backendUrl?.replace(/\/$/, '') || ''
const feedUsername = encodeURIComponent(userProfile?.username || 'USERNAME')
const feedPassword = 'APP_PASSWORD'
const rssUrl = `${baseUrl}/rss/${objectType}?u=${feedUsername}&p=${feedPassword}`
return (
<Flex vertical align='center'>
<Result
title='RSS Feed Connection'
subTitle={
<Text>
Use this URL to subscribe from RSS readers or automation tools. An
app password is required and can be configured in your user
settings. Use your app password, not your account password.
</Text>
}
icon={<RssIcon />}
>
<Flex justify='center' style={{ minWidth: '395px' }}>
<Flex justify='center'>
<Flex gap='small' align='center' justify='center'>
<Text code style={{ fontSize: '14px', wordBreak: 'break-all' }}>
{rssUrl}
</Text>
<CopyButton size='default' text={rssUrl} />
</Flex>
</Flex>
</Flex>
</Result>
</Flex>
)
}
RSSFeedURL.propTypes = {
objectType: PropTypes.string.isRequired
}
export default RSSFeedURL

View File

@ -0,0 +1,6 @@
import Icon from '@ant-design/icons'
import CustomIconSvg from '../../../assets/icons/rssicon.svg?react'
const RssIcon = (props) => <Icon component={CustomIconSvg} {...props} />
export default RssIcon