mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-07-26 19:48:02 +00:00
lots of ui updates
This commit is contained in:
parent
7a7cd0711d
commit
ebd569b0c0
17 changed files with 217 additions and 321 deletions
|
|
@ -8,7 +8,11 @@ server {
|
|||
}
|
||||
|
||||
location /assets/ {
|
||||
root /app/static; # Base directory for static files
|
||||
root /app/static;
|
||||
}
|
||||
|
||||
location /static/ {
|
||||
root /app;
|
||||
}
|
||||
|
||||
# admin disabled when not in dev mode
|
||||
|
|
|
|||
|
|
@ -62,7 +62,6 @@ export const WebsocketProvider = ({ children }) => {
|
|||
break;
|
||||
|
||||
case 'channel_stats':
|
||||
console.log(JSON.parse(event.data.stats));
|
||||
setChannelStats(JSON.parse(event.data.stats));
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -849,8 +849,8 @@ export default class API {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${await API.getAuthToken()}`,
|
||||
body: JSON.stringify({ client_id: clientId }),
|
||||
},
|
||||
body: JSON.stringify({ client_id: clientId }),
|
||||
});
|
||||
|
||||
const retval = await response.json();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import {
|
|||
SlidersHorizontal,
|
||||
LayoutGrid,
|
||||
Settings as LucideSettings,
|
||||
Copy,
|
||||
ChartLine,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Avatar,
|
||||
|
|
@ -23,7 +25,6 @@ import logo from '../images/logo.png';
|
|||
import useChannelsStore from '../store/channels';
|
||||
import './sidebar.css';
|
||||
import useSettingsStore from '../store/settings';
|
||||
import { ContentCopy } from '@mui/icons-material';
|
||||
|
||||
const NavLink = ({ item, isActive, collapsed }) => {
|
||||
return (
|
||||
|
|
@ -79,7 +80,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
|
|||
path: '/stream-profiles',
|
||||
},
|
||||
{ label: 'TV Guide', icon: <LayoutGrid size={20} />, path: '/guide' },
|
||||
{ label: 'Stats', icon: <LayoutGrid size={20} />, path: '/stats' },
|
||||
{ label: 'Stats', icon: <ChartLine size={20} />, path: '/stats' },
|
||||
{
|
||||
label: 'Settings',
|
||||
icon: <LucideSettings size={20} />,
|
||||
|
|
@ -196,7 +197,7 @@ const Sidebar = ({ collapsed, toggleDrawer, drawerWidth, miniDrawerWidth }) => {
|
|||
color="gray.9"
|
||||
onClick={copyPublicIP}
|
||||
>
|
||||
<ContentCopy />
|
||||
<Copy />
|
||||
</ActionIcon>
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import * as Yup from 'yup';
|
|||
import useChannelsStore from '../../store/channels';
|
||||
import API from '../../api';
|
||||
import useStreamProfilesStore from '../../store/streamProfiles';
|
||||
import { Add as AddIcon, Remove as RemoveIcon } from '@mui/icons-material';
|
||||
import useStreamsStore from '../../store/streams';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import ChannelGroupForm from './ChannelGroup';
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useMemo } from 'react';
|
||||
import API from '../../api';
|
||||
import M3UProfile from './M3UProfile';
|
||||
import { Delete as DeleteIcon, Edit as EditIcon } from '@mui/icons-material';
|
||||
import usePlaylistsStore from '../../store/playlists';
|
||||
import {
|
||||
Card,
|
||||
|
|
@ -13,6 +12,7 @@ import {
|
|||
ActionIcon,
|
||||
Text,
|
||||
} from '@mantine/core';
|
||||
import { SquareMinus, SquarePen } from 'lucide-react';
|
||||
|
||||
const M3UProfiles = ({ playlist = null, isOpen, onClose }) => {
|
||||
const profiles = usePlaylistsStore((state) => state.profiles[playlist.id]);
|
||||
|
|
@ -69,14 +69,19 @@ const M3UProfiles = ({ playlist = null, isOpen, onClose }) => {
|
|||
onChange={() => toggleActive(item)}
|
||||
color="primary"
|
||||
/>
|
||||
<ActionIcon onClick={() => editProfile(item)} color="yellow.5">
|
||||
<EditIcon />
|
||||
<ActionIcon
|
||||
onClick={() => editProfile(item)}
|
||||
color="yellow.5"
|
||||
variant="transparent"
|
||||
>
|
||||
<SquarePen size="18" />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
onClick={() => deleteProfile(item.id)}
|
||||
color="error"
|
||||
color="red.9"
|
||||
variant="transparent"
|
||||
>
|
||||
<DeleteIcon />
|
||||
<SquareMinus size="18" />
|
||||
</ActionIcon>
|
||||
</Box>
|
||||
</Card>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,6 @@ import { useEffect, useMemo, useRef, useState, useCallback } from 'react';
|
|||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import useChannelsStore from '../../store/channels';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import {
|
||||
Add as AddIcon,
|
||||
LiveTv as LiveTvIcon,
|
||||
ContentCopy,
|
||||
IndeterminateCheckBox,
|
||||
CompareArrows,
|
||||
Code,
|
||||
AddBox,
|
||||
} from '@mui/icons-material';
|
||||
import API from '../../api';
|
||||
import ChannelForm from '../forms/Channel';
|
||||
import { TableHelper } from '../../helpers';
|
||||
|
|
@ -29,6 +20,7 @@ import {
|
|||
Binary,
|
||||
ArrowDown01,
|
||||
SquarePlus,
|
||||
Copy,
|
||||
} from 'lucide-react';
|
||||
import ghostImage from '../../images/ghost.svg';
|
||||
import {
|
||||
|
|
@ -641,7 +633,7 @@ const ChannelsTable = ({}) => {
|
|||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<ContentCopy size="18" fontSize="small" />
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
|
|
@ -671,7 +663,7 @@ const ChannelsTable = ({}) => {
|
|||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<ContentCopy size="18" fontSize="small" />
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
|
|
@ -702,7 +694,7 @@ const ChannelsTable = ({}) => {
|
|||
variant="transparent"
|
||||
color="gray.5"
|
||||
>
|
||||
<ContentCopy size="18" fontSize="small" />
|
||||
<Copy size="18" fontSize="small" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Popover.Dropdown>
|
||||
|
|
@ -712,25 +704,13 @@ const ChannelsTable = ({}) => {
|
|||
</Flex>
|
||||
|
||||
{/* Paper container: contains top toolbar and table (or ghost state) */}
|
||||
<Paper
|
||||
style={{
|
||||
// bgcolor: theme.palette.background.paper,
|
||||
// borderRadius: 2,
|
||||
// overflow: 'hidden',
|
||||
// display: 'flex',
|
||||
// flexDirection: 'column',
|
||||
height: 'calc(100vh - 75px)',
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
{/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */}
|
||||
<Box
|
||||
style={{
|
||||
display: 'flex',
|
||||
// alignItems: 'center',
|
||||
// backgroundColor: theme.palette.background.paper,
|
||||
justifyContent: 'flex-end',
|
||||
padding: 10,
|
||||
// gap: 1,
|
||||
}}
|
||||
>
|
||||
<Flex gap={6}>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import API from '../../api';
|
||||
import {
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
Refresh as RefreshIcon,
|
||||
} from '@mui/icons-material';
|
||||
import useEPGsStore from '../../store/epgs';
|
||||
import EPGForm from '../forms/EPG';
|
||||
import { TableHelper } from '../../helpers';
|
||||
|
|
|
|||
|
|
@ -1,25 +1,6 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import API from '../../api';
|
||||
import {
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
SwapVert as SwapVertIcon,
|
||||
Check as CheckIcon,
|
||||
Close as CloseIcon,
|
||||
} from '@mui/icons-material';
|
||||
import {
|
||||
LiveTv as LiveTvIcon,
|
||||
ContentCopy,
|
||||
Tv as TvIcon,
|
||||
Clear as ClearIcon,
|
||||
IndeterminateCheckBox,
|
||||
CompareArrows,
|
||||
Code,
|
||||
AddBox,
|
||||
Hd as HdIcon,
|
||||
} from '@mui/icons-material';
|
||||
import usePlaylistsStore from '../../store/playlists';
|
||||
import M3UForm from '../forms/M3U';
|
||||
import { TableHelper } from '../../helpers';
|
||||
|
|
@ -46,6 +27,8 @@ import {
|
|||
TvMinimalPlay,
|
||||
SquarePen,
|
||||
RefreshCcw,
|
||||
Check,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
IconArrowDown,
|
||||
|
|
@ -103,11 +86,7 @@ const Example = () => {
|
|||
},
|
||||
Cell: ({ cell }) => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{cell.getValue() ? (
|
||||
<CheckIcon color="success" />
|
||||
) : (
|
||||
<CloseIcon color="error" />
|
||||
)}
|
||||
{cell.getValue() ? <Check color="green.5" /> : <X color="red.9" />}
|
||||
</Box>
|
||||
),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import API from '../../api';
|
||||
import {
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
Check as CheckIcon,
|
||||
Close as CloseIcon,
|
||||
} from '@mui/icons-material';
|
||||
import StreamProfileForm from '../forms/StreamProfile';
|
||||
import useStreamProfilesStore from '../../store/streamProfiles';
|
||||
import { TableHelper } from '../../helpers';
|
||||
|
|
@ -24,7 +17,7 @@ import {
|
|||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import { IconSquarePlus } from '@tabler/icons-react';
|
||||
import { SquareMinus, SquarePen } from 'lucide-react';
|
||||
import { SquareMinus, SquarePen, Check, X } from 'lucide-react';
|
||||
|
||||
const StreamProfiles = () => {
|
||||
const [profile, setProfile] = useState(null);
|
||||
|
|
@ -62,11 +55,7 @@ const StreamProfiles = () => {
|
|||
},
|
||||
Cell: ({ cell }) => (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{cell.getValue() ? (
|
||||
<CheckIcon color="success" />
|
||||
) : (
|
||||
<CloseIcon color="error" />
|
||||
)}
|
||||
{cell.getValue() ? <Check color="success" /> : <X color="error" />}
|
||||
</Box>
|
||||
),
|
||||
Filter: ({ column }) => (
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
import { useEffect, useMemo, useCallback, useState, useRef } from 'react';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import API from '../../api';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
MoreVert as MoreVertIcon,
|
||||
PlaylistAdd as PlaylistAddIcon,
|
||||
IndeterminateCheckBox,
|
||||
AddBox,
|
||||
} from '@mui/icons-material';
|
||||
import { TableHelper } from '../../helpers';
|
||||
import StreamForm from '../forms/Stream';
|
||||
import usePlaylistsStore from '../../store/playlists';
|
||||
import useChannelsStore from '../../store/channels';
|
||||
import { useDebounce } from '../../utils';
|
||||
import { SquarePlus, ListPlus, SquareMinus } from 'lucide-react';
|
||||
import {
|
||||
SquarePlus,
|
||||
ListPlus,
|
||||
SquareMinus,
|
||||
EllipsisVertical,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
TextInput,
|
||||
ActionIcon,
|
||||
|
|
@ -49,8 +44,6 @@ import {
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const StreamsTable = ({}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
/**
|
||||
* useState
|
||||
*/
|
||||
|
|
@ -538,7 +531,7 @@ const StreamsTable = ({}) => {
|
|||
variant="transparent"
|
||||
size="sm"
|
||||
>
|
||||
<MoreVertIcon />
|
||||
<EllipsisVertical size="18" />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
|
||||
|
|
@ -620,25 +613,13 @@ const StreamsTable = ({}) => {
|
|||
</Text>
|
||||
</Flex>
|
||||
|
||||
<Paper
|
||||
style={{
|
||||
// bgcolor: theme.palette.background.paper,
|
||||
// borderRadius: 2,
|
||||
// overflow: 'hidden',
|
||||
height: 'calc(100vh - 75px)',
|
||||
// display: 'flex',
|
||||
// flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Paper>
|
||||
{/* Top toolbar with Remove, Assign, Auto-match, and Add buttons */}
|
||||
<Box
|
||||
style={{
|
||||
display: 'flex',
|
||||
// alignItems: 'center',
|
||||
// backgroundColor: theme.palette.background.paper,
|
||||
justifyContent: 'flex-end',
|
||||
padding: 10,
|
||||
// gap: 1,
|
||||
}}
|
||||
>
|
||||
<Flex gap={6}>
|
||||
|
|
@ -647,6 +628,7 @@ const StreamsTable = ({}) => {
|
|||
variant="default"
|
||||
size="xs"
|
||||
onClick={deleteStreams}
|
||||
disabled={rowSelection.length === 0}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import API from '../../api';
|
||||
import {
|
||||
Delete as DeleteIcon,
|
||||
Edit as EditIcon,
|
||||
Add as AddIcon,
|
||||
Check as CheckIcon,
|
||||
Close as CloseIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { Check as CheckIcon, Close as CloseIcon } from '@mui/icons-material';
|
||||
import useUserAgentsStore from '../../store/userAgents';
|
||||
import UserAgentForm from '../forms/UserAgent';
|
||||
import { TableHelper } from '../../helpers';
|
||||
|
|
@ -24,14 +18,7 @@ import {
|
|||
Box,
|
||||
Button,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconArrowDown,
|
||||
IconArrowUp,
|
||||
IconDeviceDesktopSearch,
|
||||
IconSelector,
|
||||
IconSortAscendingNumbers,
|
||||
IconSquarePlus,
|
||||
} from '@tabler/icons-react';
|
||||
import { IconSquarePlus } from '@tabler/icons-react';
|
||||
import { SquareMinus, SquarePen } from 'lucide-react';
|
||||
|
||||
const UserAgentsTable = () => {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,17 @@ export default {
|
|||
size: 'xs',
|
||||
},
|
||||
mantineTableBodyRowProps: ({ isDetailPanel, row }) => {
|
||||
if (isDetailPanel && row.getIsSelected()) {
|
||||
return {
|
||||
style: {
|
||||
backgroundColor: '#163632',
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
style: {
|
||||
...(isDetailPanel && {
|
||||
border: 'none',
|
||||
}),
|
||||
...(isDetailPanel &&
|
||||
row.getIsSelected() && {
|
||||
backgroundColor: '#163632',
|
||||
}),
|
||||
},
|
||||
};
|
||||
},
|
||||
mantineTableBodyCellProps: {
|
||||
style: {
|
||||
|
|
@ -48,6 +52,9 @@ export default {
|
|||
style: {
|
||||
paddingLeft: 10,
|
||||
paddingRight: 10,
|
||||
paddingTop: 2,
|
||||
paddingBottom: 2,
|
||||
fontWeight: 'normal',
|
||||
color: '#CFCFCF',
|
||||
backgroundColor: '#383A3F',
|
||||
borderColor: '#444',
|
||||
|
|
|
|||
|
|
@ -7,34 +7,10 @@ const ChannelsPage = () => {
|
|||
return (
|
||||
<Grid style={{ padding: 18 }}>
|
||||
<Grid.Col span={6}>
|
||||
<Box
|
||||
style={{
|
||||
height: '100vh - 20px', // Full viewport height
|
||||
paddingTop: 0, // Top padding
|
||||
paddingBottom: 1, // Bottom padding
|
||||
paddingRight: 0.5,
|
||||
paddingLeft: 0,
|
||||
boxSizing: 'border-box', // Include padding in height calculation
|
||||
overflow: 'hidden', // Prevent parent scrolling
|
||||
}}
|
||||
>
|
||||
<ChannelsTable />
|
||||
</Box>
|
||||
<ChannelsTable />
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<Box
|
||||
style={{
|
||||
height: '100vh - 20px', // Full viewport height
|
||||
paddingTop: 0, // Top padding
|
||||
paddingBottom: 1, // Bottom padding
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0.5,
|
||||
boxSizing: 'border-box', // Include padding in height calculation
|
||||
overflow: 'hidden', // Prevent parent scrolling
|
||||
}}
|
||||
>
|
||||
<StreamsTable />
|
||||
</Box>
|
||||
<StreamsTable />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import React, { useMemo, useState, useEffect, useCallback } from 'react';
|
||||
import { ActionIcon, Box, Center, Grid } from '@mantine/core';
|
||||
import { ActionIcon, Box, Center, Grid, Text } from '@mantine/core';
|
||||
import { MantineReactTable, useMantineReactTable } from 'mantine-react-table';
|
||||
import { TableHelper } from '../helpers';
|
||||
import API from '../api';
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from 'lucide-react';
|
||||
|
||||
const ChannelsPage = () => {
|
||||
const { channels, stats: channelStats } = useChannelsStore();
|
||||
const { channels, channelsByUUID, stats: channelStats } = useChannelsStore();
|
||||
const [activeChannels, setActiveChannels] = useState([]);
|
||||
const [clients, setClients] = useState([]);
|
||||
|
||||
|
|
@ -106,8 +106,8 @@ const ChannelsPage = () => {
|
|||
await API.stopChannel(id);
|
||||
};
|
||||
|
||||
const stopClient = async (id) => {
|
||||
await API.stopClient(id);
|
||||
const stopClient = async (channelId, clientId) => {
|
||||
await API.stopClient(channelId, clientId);
|
||||
};
|
||||
|
||||
const channelsTable = useMantineReactTable({
|
||||
|
|
@ -165,6 +165,12 @@ const ChannelsPage = () => {
|
|||
header: 'Channel',
|
||||
accessorKey: 'channel.name',
|
||||
size: 100,
|
||||
mantineTableBodyCellProps: {
|
||||
style: {
|
||||
whiteSpace: 'nowrap',
|
||||
maxWidth: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'User-Agent',
|
||||
|
|
@ -210,19 +216,21 @@ const ChannelsPage = () => {
|
|||
</Center>
|
||||
</Box>
|
||||
),
|
||||
mantineTableContainerProps: {
|
||||
style: {
|
||||
height: '100%',
|
||||
overflowY: 'auto',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const stats = channelStats.channels.map((ch) => ({
|
||||
...ch,
|
||||
...Object.values(channels).filter(
|
||||
(channel) => channel.uuid === channelStats.channels[0].channel_id
|
||||
)[0],
|
||||
...channels[channelsByUUID[ch.channel_id]],
|
||||
}));
|
||||
setActiveChannels(stats);
|
||||
|
||||
console.log(stats);
|
||||
|
||||
const clientStats = stats.reduce((acc, ch) => {
|
||||
return acc.concat(
|
||||
ch.clients.map((client) => ({
|
||||
|
|
@ -232,66 +240,47 @@ const ChannelsPage = () => {
|
|||
);
|
||||
}, []);
|
||||
setClients(clientStats);
|
||||
console.log(clientStats);
|
||||
}, [channelStats]);
|
||||
|
||||
// const fetchData = useCallback(async () => {
|
||||
// const response = await API.getChannelStats();
|
||||
// const channelStats = response.channels.map((ch) => ({
|
||||
// ...ch,
|
||||
// ...Object.values(channels).filter(
|
||||
// (channel) => channel.uuid === response.channels[0].channel_id
|
||||
// )[0],
|
||||
// }));
|
||||
// setActiveChannels(channelStats);
|
||||
|
||||
// console.log(channelStats);
|
||||
|
||||
// const clientStats = channelStats.reduce((acc, ch) => {
|
||||
// return acc.concat(
|
||||
// ch.clients.map((client) => ({
|
||||
// ...client,
|
||||
// channel: ch,
|
||||
// }))
|
||||
// );
|
||||
// }, []);
|
||||
// setClients(clientStats);
|
||||
// console.log(clientStats);
|
||||
// }, [channels]);
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData();
|
||||
// }, [fetchData]);
|
||||
|
||||
return (
|
||||
<Grid style={{ padding: 18 }}>
|
||||
<Grid.Col span={6}>
|
||||
<Box
|
||||
<Text
|
||||
w={88}
|
||||
h={24}
|
||||
style={{
|
||||
height: '100vh - 20px', // Full viewport height
|
||||
paddingTop: 0, // Top padding
|
||||
paddingBottom: 1, // Bottom padding
|
||||
paddingRight: 0.5,
|
||||
paddingLeft: 0,
|
||||
boxSizing: 'border-box', // Include padding in height calculation
|
||||
overflow: 'hidden', // Prevent parent scrolling
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontWeight: 500,
|
||||
fontSize: '20px',
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.3px',
|
||||
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
Channels
|
||||
</Text>
|
||||
<Box style={{ paddingTop: 10 }}>
|
||||
<MantineReactTable table={channelsTable} />
|
||||
</Box>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<Box
|
||||
<Text
|
||||
w={88}
|
||||
h={24}
|
||||
style={{
|
||||
height: '100vh - 20px', // Full viewport height
|
||||
paddingTop: 0, // Top padding
|
||||
paddingBottom: 1, // Bottom padding
|
||||
paddingRight: 0,
|
||||
paddingLeft: 0.5,
|
||||
boxSizing: 'border-box', // Include padding in height calculation
|
||||
overflow: 'hidden', // Prevent parent scrolling
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontWeight: 500,
|
||||
fontSize: '20px',
|
||||
lineHeight: 1,
|
||||
letterSpacing: '-0.3px',
|
||||
color: 'gray.6', // Adjust this to match MUI's theme.palette.text.secondary
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
Clients
|
||||
</Text>
|
||||
<Box style={{ paddingTop: 10 }}>
|
||||
<MantineReactTable table={clientsTable} />
|
||||
</Box>
|
||||
</Grid.Col>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { create } from 'zustand';
|
||||
import api from '../api';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
|
||||
const useChannelsStore = create((set) => ({
|
||||
const useChannelsStore = create((set, get) => ({
|
||||
channels: [],
|
||||
channelsByUUID: {},
|
||||
channelGroups: [],
|
||||
channelsPageSelection: [],
|
||||
stats: [],
|
||||
stats: {},
|
||||
activeChannels: {},
|
||||
activeClients: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
|
|
@ -13,11 +17,15 @@ const useChannelsStore = create((set) => ({
|
|||
set({ isLoading: true, error: null });
|
||||
try {
|
||||
const channels = await api.getChannels();
|
||||
const channelsByUUID = {};
|
||||
const channelsByID = channels.reduce((acc, channel) => {
|
||||
acc[channel.id] = channel;
|
||||
channelsByUUID[channel.uuid] = channel.id;
|
||||
return acc;
|
||||
}, {});
|
||||
set({
|
||||
channels: channels.reduce((acc, channel) => {
|
||||
acc[channel.id] = channel;
|
||||
return acc;
|
||||
}, {}),
|
||||
channels: channelsByID,
|
||||
channelsByUUID,
|
||||
isLoading: false,
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
@ -43,35 +51,59 @@ const useChannelsStore = create((set) => ({
|
|||
...state.channels,
|
||||
[newChannel.id]: newChannel,
|
||||
},
|
||||
})),
|
||||
|
||||
addChannels: (newChannels) =>
|
||||
set((state) => ({
|
||||
channels: {
|
||||
...state.channels,
|
||||
...newChannels.reduce((acc, channel) => {
|
||||
acc[channel.id] = channel;
|
||||
return acc;
|
||||
}, {}),
|
||||
channelsByUUID: {
|
||||
...state.channelsByUUID,
|
||||
[newChannel.uuid]: newChannel.id,
|
||||
},
|
||||
})),
|
||||
|
||||
addChannels: (newChannels) => {
|
||||
const channelsByUUID = {};
|
||||
const channelsByID = newChannels.reduce((acc, channel) => {
|
||||
acc[channel.id] = channel;
|
||||
channelsByUUID[channel.uuid] = channel.id;
|
||||
return acc;
|
||||
}, {});
|
||||
return set((state) => ({
|
||||
channels: {
|
||||
...state.channels,
|
||||
...channelsByID,
|
||||
},
|
||||
channelsByUUID: {
|
||||
...state.channelsByUUID,
|
||||
...channelsByUUID,
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
updateChannel: (channel) =>
|
||||
set((state) => ({
|
||||
channels: {
|
||||
...state.channels,
|
||||
[channel.id]: channel,
|
||||
},
|
||||
channelsByUUID: {
|
||||
...state.channelsByUUID,
|
||||
[channel.uuid]: channel.id,
|
||||
},
|
||||
})),
|
||||
|
||||
removeChannels: (channelIds) =>
|
||||
set((state) => {
|
||||
const updatedChannels = { ...state.channels };
|
||||
const channelsByUUID = { ...state.channelsByUUID };
|
||||
for (const id of channelIds) {
|
||||
delete updatedChannels[id];
|
||||
|
||||
for (const uuid in channelsByUUID) {
|
||||
if (channelsByUUID[uuid] == id) {
|
||||
delete channelsByUUID[uuid];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { channels: updatedChannels };
|
||||
return { channels: updatedChannels, channelsByUUID };
|
||||
}),
|
||||
|
||||
addChannelGroup: (newChannelGroup) =>
|
||||
|
|
@ -89,10 +121,75 @@ const useChannelsStore = create((set) => ({
|
|||
setChannelsPageSelection: (channelsPageSelection) =>
|
||||
set((state) => ({ channelsPageSelection })),
|
||||
|
||||
setChannelStats: (stats) =>
|
||||
set((state) => ({
|
||||
setChannelStats: (stats) => {
|
||||
const {
|
||||
channels,
|
||||
stats: currentStats,
|
||||
activeChannels: oldChannels,
|
||||
activeClients: oldClients,
|
||||
channelsByUUID,
|
||||
} = get();
|
||||
|
||||
const newClients = {};
|
||||
const newChannels = stats.channels.reduce((acc, ch) => {
|
||||
acc[ch.channel_id] = ch;
|
||||
|
||||
if (currentStats.channels) {
|
||||
if (oldChannels[ch.channel_id] === undefined) {
|
||||
notifications.show({
|
||||
title: 'New channel streaming',
|
||||
message: channels[channelsByUUID[ch.channel_id]].name,
|
||||
color: 'blue.5',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ch.clients.map((client) => {
|
||||
newClients[client.client_id] = client;
|
||||
// This check prevents the notifications if streams are active on page load
|
||||
if (currentStats.channels) {
|
||||
if (oldClients[client.client_id] === undefined) {
|
||||
notifications.show({
|
||||
title: 'New client started streaming',
|
||||
message: `Client streaming from ${client.ip_address}`,
|
||||
color: 'blue.5',
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// This check prevents the notifications if streams are active on page load
|
||||
if (currentStats.channels) {
|
||||
for (const uuid in oldChannels) {
|
||||
if (newChannels[uuid] === undefined) {
|
||||
notifications.show({
|
||||
title: 'Channel streaming stopped',
|
||||
message: channels[channelsByUUID[uuid]].name,
|
||||
color: 'blue.5',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const clientId in oldClients) {
|
||||
if (newClients[clientId] === undefined) {
|
||||
notifications.show({
|
||||
title: 'Client stopped streaming',
|
||||
message: `Client stopped streaming from ${oldClients[clientId].ip_address}`,
|
||||
color: 'blue.5',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set((state) => ({
|
||||
stats,
|
||||
})),
|
||||
activeChannels: newChannels,
|
||||
activeClients: newClients,
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||
export default useChannelsStore;
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
// src/theme.js
|
||||
import { createTheme } from '@mui/material/styles';
|
||||
|
||||
const theme = createTheme({
|
||||
palette: {
|
||||
mode: 'dark',
|
||||
background: {
|
||||
default: '#18181b', // Global background color (Tailwind zinc-900)
|
||||
paper: '#27272a', // Paper background (Tailwind zinc-800)
|
||||
},
|
||||
primary: {
|
||||
main: '#4A90E2',
|
||||
contrastText: '#FFFFFF',
|
||||
},
|
||||
secondary: {
|
||||
main: '#F5A623',
|
||||
contrastText: '#FFFFFF',
|
||||
},
|
||||
text: {
|
||||
primary: '#FFFFFF',
|
||||
secondary: '#d4d4d8', // Updated secondary text color (Tailwind zinc-300)
|
||||
},
|
||||
// Custom colors for components (chip buttons, borders, etc.)
|
||||
custom: {
|
||||
// For chip buttons:
|
||||
greenMain: '#90C43E',
|
||||
greenHoverBg: 'rgba(144,196,62,0.1)',
|
||||
|
||||
indigoMain: '#4F39F6',
|
||||
indigoHoverBg: 'rgba(79,57,246,0.1)',
|
||||
|
||||
greyBorder: '#707070',
|
||||
greyHoverBg: 'rgba(112,112,112,0.1)',
|
||||
greyText: '#a0a0a0',
|
||||
|
||||
// Common border colors:
|
||||
borderDefault: '#3f3f46', // Tailwind zinc-700
|
||||
borderHover: '#5f5f66', // Approximate Tailwind zinc-600
|
||||
|
||||
// For the "Add" button:
|
||||
successBorder: '#00a63e',
|
||||
successBg: '#0d542b',
|
||||
successBgHover: '#0a4020',
|
||||
successIcon: '#05DF72',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
// Set Inter as the global font
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
h1: { fontSize: '2.5rem', fontWeight: 700 },
|
||||
h2: { fontSize: '2rem', fontWeight: 700 },
|
||||
body1: { fontSize: '1rem' },
|
||||
},
|
||||
spacing: 8,
|
||||
components: {
|
||||
MuiButton: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
borderRadius: 4,
|
||||
textTransform: 'none',
|
||||
fontWeight: 500,
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiDrawer: {
|
||||
styleOverrides: {
|
||||
paper: {
|
||||
backgroundColor: '#27272a', // Use the same paper color
|
||||
color: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
},
|
||||
MuiAppBar: {
|
||||
styleOverrides: {
|
||||
root: {
|
||||
backgroundColor: '#18181b',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
custom: {
|
||||
sidebar: {
|
||||
activeBackground: 'rgba(21, 69, 62, 0.67)',
|
||||
activeBorder: '#14917e',
|
||||
hoverBackground: '#27272a',
|
||||
hoverBorder: '#3f3f46',
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default theme;
|
||||
Loading…
Add table
Add a link
Reference in a new issue