mirror of
https://github.com/owncast/owncast.git
synced 2024-10-10 19:16:02 +00:00
clean up of home section; now with styling
This commit is contained in:
parent
3f1f96a768
commit
2211572ba1
@ -1,5 +1,9 @@
|
||||
import 'antd/dist/antd.compact.css';
|
||||
import "../styles/globals.scss";
|
||||
import '../styles/colors.scss';
|
||||
import '../styles/globals.scss';
|
||||
|
||||
// GW: I can't override ant design styles through components using NextJS's built-in CSS modules. So I'll just import styles here for now and figure out enabling SASS modules later.
|
||||
import '../styles/home.scss';
|
||||
|
||||
import { AppProps } from 'next/app';
|
||||
import ServerStatusProvider from '../utils/server-status-context';
|
||||
|
@ -126,4 +126,6 @@ export default function Chart({ data, title, color, unit, dataCollections }: Cha
|
||||
|
||||
Chart.defaultProps = {
|
||||
dataCollections: [],
|
||||
data: [],
|
||||
title: '',
|
||||
};
|
||||
|
@ -1,32 +1,25 @@
|
||||
import { Typography, Statistic, Card, Col, Progress} from "antd";
|
||||
const { Text } = Typography;
|
||||
|
||||
interface ItemProps {
|
||||
title: string,
|
||||
value: string,
|
||||
prefix: JSX.Element,
|
||||
color: string,
|
||||
interface StatisticItemProps {
|
||||
title?: string,
|
||||
value?: any,
|
||||
prefix?: JSX.Element,
|
||||
// color?: string,
|
||||
progress?: boolean,
|
||||
centered: boolean,
|
||||
centered?: boolean,
|
||||
};
|
||||
const defaultProps = {
|
||||
title: '',
|
||||
value: 0,
|
||||
prefix: null,
|
||||
// color: '',
|
||||
progress: false,
|
||||
centered: false,
|
||||
};
|
||||
|
||||
export default function StatisticItem(props: ItemProps) {
|
||||
const { title, value, prefix } = props;
|
||||
const View = props.progress ? ProgressView : StatisticView;
|
||||
const style = props.centered ? {display: 'flex', alignItems: 'center', justifyContent: 'center'} : {};
|
||||
|
||||
return (
|
||||
<Col span={8}>
|
||||
<Card>
|
||||
<div style={style}>
|
||||
<View {...props} />
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
|
||||
function ProgressView({title, value, prefix, color}) {
|
||||
function ProgressView({ title, value, prefix, color }: StatisticItemProps) {
|
||||
const endColor = value > 90 ? 'red' : color;
|
||||
const content = (
|
||||
<div>
|
||||
@ -36,22 +29,43 @@ function ProgressView({title, value, prefix, color}) {
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Progress type="dashboard" percent={value} width={120} strokeColor={{
|
||||
'0%': color,
|
||||
'90%': endColor,
|
||||
}} format={percent => content} />
|
||||
<Progress
|
||||
type="dashboard"
|
||||
percent={value}
|
||||
width={120}
|
||||
strokeColor={{
|
||||
'0%': color,
|
||||
'90%': endColor,
|
||||
}}
|
||||
format={percent => content}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ProgressView.defaultProps = defaultProps;
|
||||
|
||||
function StatisticView({title, value, prefix, color}) {
|
||||
const valueStyle = { fontSize: "1.8rem" };
|
||||
|
||||
function StatisticView({ title, value, prefix }: StatisticItemProps) {
|
||||
return (
|
||||
<Statistic
|
||||
title={title}
|
||||
value={value}
|
||||
valueStyle={valueStyle}
|
||||
prefix={prefix}
|
||||
title={title}
|
||||
value={value}
|
||||
prefix={prefix}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
StatisticView.defaultProps = defaultProps;
|
||||
|
||||
export default function StatisticItem(props: StatisticItemProps) {
|
||||
const { progress, centered } = props;
|
||||
const View = progress ? ProgressView : StatisticView;
|
||||
|
||||
const style = centered ? {display: 'flex', alignItems: 'center', justifyContent: 'center'} : {};
|
||||
|
||||
return (
|
||||
<Card type="inner">
|
||||
<div style={style}>
|
||||
<View {...props} />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
StatisticItem.defaultProps = defaultProps;
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
/*
|
||||
Will display an overview with the following datasources:
|
||||
1. Current broadcaster.
|
||||
@ -8,11 +9,7 @@ TODO: Link each overview value to the sub-page that focuses on it.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useContext } from "react";
|
||||
<<<<<<< HEAD
|
||||
import { Row, Skeleton, Typography } from "antd";
|
||||
=======
|
||||
import { Row, Col, Skeleton, Result, List, Typography, Card } from "antd";
|
||||
>>>>>>> 4cdf5b73baa0584a0e6b2f586c27ca53923c65c7
|
||||
import { Row, Col, Skeleton, Result, List, Typography, Card, Statistic } from "antd";
|
||||
import { UserOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { formatDistanceToNow, formatRelative } from "date-fns";
|
||||
import { ServerStatusContext } from "../utils/server-status-context";
|
||||
@ -21,264 +18,182 @@ import LogTable from "./components/log-table";
|
||||
import Offline from './offline-notice';
|
||||
|
||||
import {
|
||||
STATUS,
|
||||
SERVER_CONFIG,
|
||||
LOGS_WARN,
|
||||
fetchData,
|
||||
FETCH_INTERVAL,
|
||||
} from "../utils/apis";
|
||||
import { formatIPAddress, isEmptyObject } from "../utils/format";
|
||||
import { INITIAL_SERVER_CONFIG_STATE } from "./update-server-config";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
>>>>>>> 4cdf5b73baa0584a0e6b2f586c27ca53923c65c7
|
||||
|
||||
<<<<<<< HEAD
|
||||
export default function Home() {
|
||||
const context = useContext(BroadcastStatusContext);
|
||||
=======
|
||||
export default function Stats() {
|
||||
const context = useContext(ServerStatusContext);
|
||||
>>>>>>> ca90d28ec1d0a0f0059a4649dd00fb95b9d4fa3d
|
||||
const { broadcaster } = context || {};
|
||||
const serverStatusData = useContext(ServerStatusContext);
|
||||
const { broadcaster } = serverStatusData || {};
|
||||
const { remoteAddr, streamDetails } = broadcaster || {};
|
||||
|
||||
// Pull in the server status so we can show server overview.
|
||||
const [stats, setStats] = useState(null);
|
||||
const getStats = async () => {
|
||||
try {
|
||||
const result = await fetchData(STATUS);
|
||||
setStats(result);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
getConfig();
|
||||
getLogs();
|
||||
};
|
||||
|
||||
|
||||
// Pull in the server config so we can show config overview.
|
||||
const [config, setConfig] = useState({
|
||||
streamKey: "",
|
||||
yp: {
|
||||
enabled: false,
|
||||
},
|
||||
videoSettings: {
|
||||
videoQualityVariants: [
|
||||
{
|
||||
audioPassthrough: false,
|
||||
videoBitrate: 0,
|
||||
audioBitrate: 0,
|
||||
framerate: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const [logs, setLogs] = useState([]);
|
||||
|
||||
const [configData, setServerConfig] = useState(INITIAL_SERVER_CONFIG_STATE);
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const result = await fetchData(SERVER_CONFIG);
|
||||
setConfig(result);
|
||||
setServerConfig(result);
|
||||
console.log("CONFIG", result);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
const [logsData, setLogs] = useState([]);
|
||||
const getLogs = async () => {
|
||||
try {
|
||||
const result = await fetchData(LOGS_WARN);
|
||||
setLogs(result);
|
||||
console.log("LOGS", result);
|
||||
} catch (error) {
|
||||
console.log("==== error", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getMoreStats = () => {
|
||||
getLogs();
|
||||
getConfig();
|
||||
}
|
||||
useEffect(() => {
|
||||
setInterval(getStats, FETCH_INTERVAL);
|
||||
getStats();
|
||||
let intervalId = null;
|
||||
intervalId = setInterval(getMoreStats, FETCH_INTERVAL);
|
||||
return () => {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (isEmptyObject(config) || isEmptyObject(stats)) {
|
||||
if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) {
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Skeleton active />
|
||||
<Skeleton active />
|
||||
<Skeleton active />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const logTable = logs.length > 0 ? <LogTable logs={logs} pageSize={5} /> : null
|
||||
console.log(logs)
|
||||
|
||||
if (!broadcaster) {
|
||||
return <Offline />;
|
||||
return <Offline logs={logsData} />;
|
||||
}
|
||||
|
||||
const videoSettings = config.videoSettings.videoQualityVariants;
|
||||
const videoQualitySettings = videoSettings.map((setting) => {
|
||||
// map out settings
|
||||
const videoQualitySettings = configData?.videoSettings?.videoQualityVariants?.map((setting, index) => {
|
||||
const { audioPassthrough, audioBitrate, videoBitrate, framerate } = setting;
|
||||
const audioSetting =
|
||||
setting.audioPassthrough || setting.audioBitrate === 0
|
||||
audioPassthrough || audioBitrate === 0
|
||||
? `${streamDetails.audioBitrate} kpbs (passthrough)`
|
||||
: `${setting.audioBitrate} kbps`;
|
||||
: `${audioBitrate} kbps`;
|
||||
|
||||
let settingTitle = 'Outbound Stream Details';
|
||||
settingTitle = (videoQualitySettings?.length > 1) ?
|
||||
`${settingTitle} ${index + 1}` : settingTitle;
|
||||
return (
|
||||
<Row gutter={[16, 16]} key={`setting-${setting.videoBitrate}`}>
|
||||
<Card title={settingTitle} type="inner">
|
||||
<StatisticItem
|
||||
title="Outbound Video Stream"
|
||||
value={`${setting.videoBitrate} kbps ${setting.framerate} fps`}
|
||||
value={`${videoBitrate} kbps, ${framerate} fps`}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Outbound Audio Stream"
|
||||
value={audioSetting}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
const { viewerCount, sessionMaxViewerCount } = stats;
|
||||
const { viewerCount, sessionMaxViewerCount } = serverStatusData;
|
||||
const streamVideoDetailString = `${streamDetails.videoCodec} ${streamDetails.videoBitrate} kbps ${streamDetails.width}x${streamDetails.height}`;
|
||||
const streamAudioDetailString = `${streamDetails.audioCodec} ${streamDetails.audioBitrate} kpbs`;
|
||||
const streamAudioDetailString = `${streamDetails.audioCodec} ${streamDetails.audioBitrate} kbps`;
|
||||
|
||||
const broadcastDate = new Date(broadcaster.time);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Title>Server Overview</Title>
|
||||
<Row gutter={[16, 16]}>
|
||||
<StatisticItem
|
||||
title={`Stream started ${formatRelative(
|
||||
new Date(broadcaster.time),
|
||||
new Date()
|
||||
)}`}
|
||||
value={formatDistanceToNow(new Date(broadcaster.time))}
|
||||
prefix={<ClockCircleOutlined />}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Viewers"
|
||||
value={viewerCount}
|
||||
prefix={<UserOutlined />}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Peak viewer count"
|
||||
value={sessionMaxViewerCount}
|
||||
prefix={<UserOutlined />}
|
||||
color="#334"
|
||||
/>
|
||||
</Row>
|
||||
<div className="home-container">
|
||||
<Title>Stream Overview</Title>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<StatisticItem
|
||||
title="Input"
|
||||
value={formatIPAddress(remoteAddr)}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Inbound Video Stream"
|
||||
value={streamVideoDetailString}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Inbound Audio Stream"
|
||||
value={streamAudioDetailString}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
</Row>
|
||||
<div className="sections-container">
|
||||
|
||||
{videoQualitySettings}
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<StatisticItem
|
||||
title="Stream key"
|
||||
value={config.streamKey}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Directory registration enabled"
|
||||
value={config.yp.enabled.toString()}
|
||||
prefix={null}
|
||||
color="#334"
|
||||
/>
|
||||
</Row>
|
||||
<div className="section online-status-section">
|
||||
<Card title="Stream is online" type="inner">
|
||||
<Statistic
|
||||
title={`Stream started ${formatRelative(
|
||||
broadcastDate,
|
||||
Date.now()
|
||||
)}`}
|
||||
value={formatDistanceToNow(broadcastDate)}
|
||||
prefix={<ClockCircleOutlined />}
|
||||
/>
|
||||
<Statistic
|
||||
title="Viewers"
|
||||
value={viewerCount}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
<Statistic
|
||||
title="Peak viewer count"
|
||||
value={sessionMaxViewerCount}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{logTable}
|
||||
<div className="section stream-details-section">
|
||||
|
||||
<div className="details outbound-details">
|
||||
{videoQualitySettings}
|
||||
</div>
|
||||
|
||||
<div className="details other-details">
|
||||
<Card title="Inbound Stream Details" type="inner">
|
||||
<StatisticItem
|
||||
title="Input"
|
||||
value={formatIPAddress(remoteAddr)}
|
||||
prefix={null}
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Inbound Video Stream"
|
||||
value={streamVideoDetailString}
|
||||
prefix={null}
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Inbound Audio Stream"
|
||||
value={streamAudioDetailString}
|
||||
prefix={null}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<div className="server-detail">
|
||||
<Card title="Server Config" type="inner">
|
||||
<StatisticItem
|
||||
title="Stream key"
|
||||
value={configData.streamKey}
|
||||
prefix={null}
|
||||
/>
|
||||
<StatisticItem
|
||||
title="Directory registration enabled"
|
||||
value={configData.yp.enabled.toString()}
|
||||
prefix={null}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{logsData.length ? (
|
||||
<>
|
||||
<Title level={2}>Stream Logs</Title>
|
||||
<LogTable logs={logsData} pageSize={5} />
|
||||
</>
|
||||
): null}
|
||||
</div>
|
||||
);
|
||||
|
||||
function Offline() {
|
||||
const data = [
|
||||
{
|
||||
title: "Send some test content",
|
||||
content: (
|
||||
<div>
|
||||
Test your server with any video you have around. Pass it to the test script and start streaming it.
|
||||
<blockquote>
|
||||
<em>./test/ocTestStream.sh yourVideo.mp4</em>
|
||||
</blockquote>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Use your broadcasting software",
|
||||
content: (
|
||||
<div>
|
||||
<a href="https://owncast.online/docs/broadcasting/">Learn how to point your existing software to your new server and start streaming your content.</a>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Chat is disabled",
|
||||
content: "Chat will continue to be disabled until you begin a live stream."
|
||||
},
|
||||
{
|
||||
title: "Embed your video onto other sites",
|
||||
content: (
|
||||
<div>
|
||||
<a href="https://owncast.online/docs/embed">Learn how you can add your Owncast stream to other sites you control.</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Result
|
||||
icon={<OwncastLogo />}
|
||||
title="No stream is active."
|
||||
subTitle="You should start one."
|
||||
/>
|
||||
|
||||
<List
|
||||
grid={{
|
||||
gutter: 16,
|
||||
xs: 1,
|
||||
sm: 2,
|
||||
md: 2,
|
||||
lg: 6,
|
||||
xl: 3,
|
||||
xxl: 3,
|
||||
}}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<Card title={item.title}>{item.content}</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
{logTable}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,28 @@
|
||||
import { Result, List, Card } from "antd";
|
||||
import { Result, Card, Typography } from "antd";
|
||||
import { MessageTwoTone, BulbTwoTone, BookTwoTone, PlaySquareTwoTone } from '@ant-design/icons';
|
||||
import OwncastLogo from "./components/logo"
|
||||
import LogTable from "./components/log-table";
|
||||
|
||||
export default function Offline() {
|
||||
|
||||
const { Title } = Typography;
|
||||
const { Meta } = Card;
|
||||
|
||||
export default function Offline({ logs = [] }) {
|
||||
const data = [
|
||||
{
|
||||
icon: <BulbTwoTone twoToneColor="#ffd33d" />,
|
||||
title: "Send some test content",
|
||||
content: (
|
||||
<div>
|
||||
Test your server with any video you have around. Pass it to the test script and start streaming it.
|
||||
<blockquote>
|
||||
<em>./test/ocTestStream.sh yourVideo.mp4</em>
|
||||
</blockquote>
|
||||
<pre>
|
||||
<code>./test/ocTestStream.sh yourVideo.mp4</code>
|
||||
</pre>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
icon: <BookTwoTone twoToneColor="#6f42c1" />,
|
||||
title: "Use your broadcasting software",
|
||||
content: (
|
||||
<div>
|
||||
@ -23,10 +31,12 @@ export default function Offline() {
|
||||
)
|
||||
},
|
||||
{
|
||||
icon: <MessageTwoTone twoToneColor="#0366d6" />,
|
||||
title: "Chat is disabled",
|
||||
content: "Chat will continue to be disabled until you begin a live stream."
|
||||
},
|
||||
{
|
||||
icon: <PlaySquareTwoTone twoToneColor="#f9826c" />,
|
||||
title: "Embed your video onto other sites",
|
||||
content: (
|
||||
<div>
|
||||
@ -35,32 +45,37 @@ export default function Offline() {
|
||||
)
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<Result
|
||||
icon={<OwncastLogo />}
|
||||
title="No stream is active."
|
||||
subTitle="You should start one."
|
||||
/>
|
||||
|
||||
<List
|
||||
grid={{
|
||||
gutter: 16,
|
||||
xs: 1,
|
||||
sm: 2,
|
||||
md: 2,
|
||||
lg: 6,
|
||||
xl: 3,
|
||||
xxl: 3,
|
||||
}}
|
||||
dataSource={data}
|
||||
renderItem={(item) => (
|
||||
<List.Item>
|
||||
<Card title={item.title}>{item.content}</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
{logTable}
|
||||
return (
|
||||
<div className="offline-content">
|
||||
<div className="logo-section">
|
||||
<Result
|
||||
icon={<OwncastLogo />}
|
||||
title="No stream is active."
|
||||
subTitle="You should start one."
|
||||
/>
|
||||
</div>
|
||||
<div className="list-section">
|
||||
{
|
||||
data.map(item => (
|
||||
<Card key={item.title}>
|
||||
<Meta
|
||||
avatar={item.icon}
|
||||
title={item.title}
|
||||
description={item.content}
|
||||
/>
|
||||
</Card>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
{logs.length ? (
|
||||
<>
|
||||
<Title level={2}>Stream Logs</Title>
|
||||
<LogTable logs={logs} pageSize={5} />
|
||||
</>
|
||||
): null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,6 +8,22 @@ import KeyValueTable from "./components/key-value-table";
|
||||
const { Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
|
||||
export const INITIAL_SERVER_CONFIG_STATE = {
|
||||
streamKey: '',
|
||||
yp: {
|
||||
enabled: false,
|
||||
},
|
||||
videoSettings: {
|
||||
videoQualityVariants: [
|
||||
{
|
||||
audioPassthrough: false,
|
||||
videoBitrate: 0,
|
||||
audioBitrate: 0,
|
||||
framerate: 0,
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
function SocialHandles({ config }) {
|
||||
if (!config) {
|
||||
@ -121,12 +137,12 @@ function PageContent({ config }) {
|
||||
}
|
||||
|
||||
export default function ServerConfig() {
|
||||
const [config, setConfig] = useState({});
|
||||
const [config, setConfig] = useState(INITIAL_SERVER_CONFIG_STATE);
|
||||
|
||||
const getInfo = async () => {
|
||||
try {
|
||||
const result = await fetchData(SERVER_CONFIG);
|
||||
console.log("viewers result", result)
|
||||
console.log("SERVER_CONFIG", result)
|
||||
|
||||
setConfig({ ...result });
|
||||
|
||||
@ -134,18 +150,9 @@ export default function ServerConfig() {
|
||||
setConfig({ ...config, message: error.message });
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let getStatusIntervalId = null;
|
||||
|
||||
getInfo();
|
||||
getStatusIntervalId = setInterval(getInfo, FETCH_INTERVAL);
|
||||
getInfo();
|
||||
|
||||
// returned function will be called on component unmount
|
||||
return () => {
|
||||
clearInterval(getStatusIntervalId);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
6
web/styles/colors.scss
Normal file
6
web/styles/colors.scss
Normal file
@ -0,0 +1,6 @@
|
||||
:root {
|
||||
--owncast-purple: rgba(90,103,216,1);
|
||||
--owncast-purple-highlight: #ccd;
|
||||
|
||||
--online-color: #73dd3f;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
$owncast-purple: rgba(90,103,216,1);;
|
||||
$owncast-purple: rgba(90,103,216,1);
|
||||
|
||||
html,
|
||||
body {
|
||||
@ -19,6 +19,12 @@ a {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
padding: 1rem;
|
||||
margin: .5rem 0;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
|
||||
background-color: $owncast-purple;
|
||||
@ -31,4 +37,4 @@ a {
|
||||
|
||||
.recharts-wrapper {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
160
web/styles/home.scss
Normal file
160
web/styles/home.scss
Normal file
@ -0,0 +1,160 @@
|
||||
.home-container {
|
||||
max-width: 1000px;
|
||||
|
||||
.section {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.online-status-section {
|
||||
> .ant-card {
|
||||
box-shadow: 0px 1px 1px 0px rgba(0, 22, 40, 0.1);
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
background-color: var(--owncast-purple);
|
||||
border-color: #ccc;
|
||||
color:#fff;
|
||||
|
||||
}
|
||||
.ant-card-head-title {
|
||||
font-size: .88rem;
|
||||
}
|
||||
.ant-statistic-title {
|
||||
font-size: .88rem;
|
||||
}
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
.ant-statistic {
|
||||
width: 30%;
|
||||
text-align: center;
|
||||
margin: 0 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.stream-details-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
.details {
|
||||
width: 49%;
|
||||
|
||||
> .ant-card {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.ant-card-head {
|
||||
background-color: #ccd;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
.server-detail {
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
.ant-card {
|
||||
width: 49%;
|
||||
}
|
||||
}
|
||||
.ant-card-head {
|
||||
background-color: #669;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.online-status-section{
|
||||
.ant-card-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
.ant-statistic {
|
||||
width: auto;
|
||||
text-align: left;
|
||||
margin: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stream-details-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
.details {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.offline-content {
|
||||
max-width: 1000px;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
.logo-section {
|
||||
width: 50%;
|
||||
.ant-result-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
.ant-result-subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ant-result-icon svg {
|
||||
height: 8rem;
|
||||
width: 8rem;
|
||||
}
|
||||
}
|
||||
.list-section {
|
||||
width: 50%;
|
||||
> .ant-card {
|
||||
margin-bottom: 1rem;
|
||||
.ant-card-head {
|
||||
background-color: #dde;
|
||||
}
|
||||
.ant-card-head-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.ant-card-meta-avatar {
|
||||
margin-top: .25rem;
|
||||
svg {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
}
|
||||
.ant-card-body {
|
||||
font-size: .88rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
.logo-section,
|
||||
.list-section {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -61,10 +61,10 @@
|
||||
color: #999;
|
||||
}
|
||||
.online .statusIcon svg {
|
||||
fill: #52c41a;
|
||||
fill: var(--online-color)
|
||||
}
|
||||
.online .statusLabel {
|
||||
color: #52c41a;
|
||||
color: var(--online-color)
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,6 +8,7 @@ const initialState = {
|
||||
broadcaster: null,
|
||||
online: false,
|
||||
viewerCount: 0,
|
||||
sessionMaxViewerCount: 0,
|
||||
sessionPeakViewerCount: 0,
|
||||
overallPeakViewerCount: 0,
|
||||
disableUpgradeChecks: true,
|
||||
@ -25,7 +26,7 @@ const ServerStatusProvider = ({ children }) => {
|
||||
setStatus({ ...result });
|
||||
|
||||
} catch (error) {
|
||||
// setBroadcasterStatus({ ...broadcasterStatus, message: error.message });
|
||||
// todo
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user