v1.6.2/mass message, filters

This commit is contained in:
2020-08-15 18:02:04 -04:00
parent 43dc657582
commit 917d30ce05
9 changed files with 688 additions and 212 deletions
+14 -8
View File
@@ -1,7 +1,7 @@
import firebase from 'firebase/app'; import firebase from "firebase/app";
import 'firebase/auth'; import "firebase/auth";
const API = 'http://localhost:5000/watch-party-crx/us-central1/admin'; const API = "http://localhost:5000/watch-party-crx/us-central1/admin";
const fetcher = async ({ endpoint, method, bodyData }) => { const fetcher = async ({ endpoint, method, bodyData }) => {
const idToken = await firebase.auth().currentUser.getIdToken(); const idToken = await firebase.auth().currentUser.getIdToken();
@@ -10,15 +10,21 @@ const fetcher = async ({ endpoint, method, bodyData }) => {
method, method,
headers: { headers: {
Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`, Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`,
'Access-Control-Allow-Origin': '*', "Access-Control-Allow-Origin": "*",
}, },
body, body,
}); });
}; };
export const getPartyDashboardData = () => fetcher({ endpoint: 'partyDashboard', method: 'GET' }); export const getPartyDashboardData = () =>
export const getServerDashboardData = () => fetcher({ endpoint: 'serverDashboard', method: 'GET' }); fetcher({ endpoint: "partyDashboard", method: "GET" });
export const getServerDashboardData = () =>
fetcher({ endpoint: "serverDashboard", method: "GET" });
export const postUpdateVersionActive = (bodyData) => export const postUpdateVersionActive = (bodyData) =>
fetcher({ endpoint: 'updateVersionActive', method: 'POST', bodyData }); fetcher({ endpoint: "updateVersionActive", method: "POST", bodyData });
export const postUpdateVideoSelectors = (bodyData) => export const postUpdateVideoSelectors = (bodyData) =>
fetcher({ endpoint: 'updateVideoSelectors', method: 'POST', bodyData }); fetcher({ endpoint: "updateVideoSelectors", method: "POST", bodyData });
export const postUpdateVideoNameSelectors = (bodyData) =>
fetcher({ endpoint: "updateVideoNameSelectors", method: "POST", bodyData });
export const postSendMessage = (bodyData) =>
fetcher({ endpoint: "sendMessage", method: "POST", bodyData });
+501 -155
View File
@@ -1,7 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from "react";
import firebase from 'firebase/app'; import firebase from "firebase/app";
import 'firebase/auth'; import "firebase/auth";
import 'firebase/firestore'; import "firebase/firestore";
import { import {
Button, Button,
Card, Card,
@@ -15,13 +15,21 @@ import {
Row, Row,
Col, Col,
PageHeader, PageHeader,
} from 'antd'; InputNumber,
Descriptions,
Badge,
Collapse,
Modal,
Select,
} from "antd";
import { import {
getPartyDashboardData, getPartyDashboardData,
getServerDashboardData, getServerDashboardData,
postUpdateVersionActive, postUpdateVersionActive,
postUpdateVideoSelectors, postUpdateVideoSelectors,
} from './Api'; postUpdateVideoNameSelectors,
postSendMessage,
} from "./Api";
import { import {
CheckCircleTwoTone, CheckCircleTwoTone,
CloseCircleTwoTone, CloseCircleTwoTone,
@@ -35,88 +43,60 @@ import {
LogoutOutlined, LogoutOutlined,
SendOutlined, SendOutlined,
PlusCircleOutlined, PlusCircleOutlined,
} from '@ant-design/icons'; } from "@ant-design/icons";
const PLAYER_SOURCES = [ const PLAYER_SOURCES = [
{ text: 'Netflix', value: 'netflix.' }, { text: "Netflix", value: "netflix." },
{ text: 'Amazon', value: 'amazon.' }, { text: "Amazon", value: "amazon." },
{ text: 'Hulu', value: 'hulu.' }, { text: "Hulu", value: "hulu." },
{ text: 'Disney+', value: 'disneyplus.' }, { text: "Disney+", value: "disneyplus." },
{ text: 'YouTube', value: 'youtube.' }, { text: "YouTube", value: "youtube." },
{ text: 'HBO Now', value: 'hbonow.' }, { text: "HBO Now", value: "hbonow." },
{ text: 'HBO Max', value: 'hbomax.' }, { text: "HBO Max", value: "hbomax." },
{ text: 'Peacock', value: 'peacocktv.' }, { text: "Peacock", value: "peacocktv." },
{ text: 'Google Play', value: 'play.google.' }, { text: "Google Play", value: "play.google." },
{ text: 'Crunchyroll', value: 'crunchyroll.' }, { text: "Crunchyroll", value: "crunchyroll." },
]; ];
const partiesColumns = [ const MESSAGE_TYPES = [
{ {
title: 'Party Name', text: "REPLY",
dataIndex: 'id', value: "interactive/text",
sorter: (a, b) => (a.id.toLowerCase() < b.id.toLowerCase() ? -1 : 1),
}, },
{ {
title: 'Source', text: "LINK",
dataIndex: 'source', value: "interactive/link",
sorter: (a, b) => (a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1),
filters: PLAYER_SOURCES,
onFilter: (v, r) => r.source === v,
render: (a) => PLAYER_SOURCES.filter((p) => p.value === a)[0].text,
}, },
{ {
title: 'Created At', text: "MESSAGE",
dataIndex: 'createTime', value: "",
defaultSortOrder: 'descend',
sorter: (a, b) => Date.parse(a.createTime) - Date.parse(b.createTime),
render: (a) => new Date(Date.parse(a)).toLocaleString(),
},
{
title: 'Updated At',
dataIndex: 'updateTime',
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
render: (a) => {
const minutes = (Date.now() - Date.parse(a)) / 60000;
const dS = new Date(a).toLocaleString();
return (
<Tag color={minutes <= 5 ? 'success' : minutes <= 15 ? 'processing' : 'default'}>{dS}</Tag>
);
},
},
{
title: 'On Call',
dataIndex: 'onCall',
filters: [
{
text: 'On Call',
value: true,
},
{
text: 'Not On Call',
value: false,
},
],
render: (a) =>
a ? (
<CheckCircleTwoTone color="#c0f300" id="call-icon" />
) : (
<CloseCircleTwoTone id="call-icon" />
),
onFilter: (v, r) => r.onCall === v,
}, },
]; ];
const emptyMessage = {
type: "",
text: "",
uid: "<system>",
name: "",
endpoint: "",
// time set on send,
};
export default class Dashboard extends Component { export default class Dashboard extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
partiesOverview: null, partiesOverview: null,
activeTab: '1', activeTab: "1",
refreshing: true, refreshing: true,
selectedParties: [], selectedParties: [],
nameSelectors: {}, nameSelectors: {},
selectors: {}, selectors: {},
versions: [], versions: [],
activeLimit: 5,
afkLimit: 10,
messageModel: false,
newMessage: emptyMessage,
}; };
this.serverStatusListener = null; this.serverStatusListener = null;
} }
@@ -128,7 +108,6 @@ export default class Dashboard extends Component {
getPartyDashboardData() getPartyDashboardData()
.then((res) => res.json()) .then((res) => res.json())
.then((res) => { .then((res) => {
console.log(res);
this.setState({ partiesOverview: res.partyData, refreshing: false }); this.setState({ partiesOverview: res.partyData, refreshing: false });
}); });
}; };
@@ -141,14 +120,16 @@ export default class Dashboard extends Component {
this.setState({ refreshing: true }); this.setState({ refreshing: true });
const { activeTab } = this.state; const { activeTab } = this.state;
switch (activeTab) { switch (activeTab) {
case '1': case "1":
this.getPartiesOverview(); this.getPartiesOverview();
break; break;
case '2': case "2":
case '3': case "3":
case '4': case "4":
this.getVersionsOverview(); this.getVersionsOverview();
break; break;
default:
break;
} }
}; };
tabChange = (activeTab) => { tabChange = (activeTab) => {
@@ -157,14 +138,18 @@ export default class Dashboard extends Component {
updateVersionMessage = (message, version) => { updateVersionMessage = (message, version) => {
this.setState({ refreshing: true }); this.setState({ refreshing: true });
const { versions } = this.state; const { versions } = this.state;
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0]; const versionData = versions.filter(
(v) => v.version.split(".").join("") === version
)[0];
versionData.message = message; versionData.message = message;
this.updateVersion(version, versionData); this.updateVersion(version, versionData);
}; };
updateVersionActive = (active, version) => { updateVersionActive = (active, version) => {
this.setState({ refreshing: true }); this.setState({ refreshing: true });
const { versions } = this.state; const { versions } = this.state;
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0]; const versionData = versions.filter(
(v) => v.version.split(".").join("") === version
)[0];
versionData.active = active; versionData.active = active;
this.updateVersion(version, versionData); this.updateVersion(version, versionData);
}; };
@@ -174,13 +159,10 @@ export default class Dashboard extends Component {
.catch((err) => .catch((err) =>
notification.error({ notification.error({
description: err.message, description: err.message,
message: 'Unable To Update', message: "Unable To Update",
}) })
); );
}; };
viewPartyDetails(party) {
console.log(party);
}
getSourceName(source) { getSourceName(source) {
return PLAYER_SOURCES.filter((p) => p.value === source)[0].text; return PLAYER_SOURCES.filter((p) => p.value === source)[0].text;
} }
@@ -197,7 +179,7 @@ export default class Dashboard extends Component {
.catch((err) => { .catch((err) => {
notification.error({ notification.error({
description: err.message, description: err.message,
message: 'Unable To Update', message: "Unable To Update",
}); });
this.setState({ refreshing: false }); this.setState({ refreshing: false });
@@ -210,32 +192,43 @@ export default class Dashboard extends Component {
}; };
handleAlertTitleChange = (value, version, index) => { handleAlertTitleChange = (value, version, index) => {
const { versions } = this.state; const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].title = value; versions.filter((vs) => vs.version === version)[0].alerts[
index
].title = value;
this.setState({ versions }); this.setState({ versions });
}; };
handleAlertDescriptionChange = (value, version, index) => { handleAlertDescriptionChange = (value, version, index) => {
const { versions } = this.state; const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].description = value; versions.filter((vs) => vs.version === version)[0].alerts[
index
].description = value;
this.setState({ versions }); this.setState({ versions });
}; };
handleAlertLinkChange = (value, version, index) => { handleAlertLinkChange = (value, version, index) => {
const { versions } = this.state; const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].link = value; versions.filter((vs) => vs.version === version)[0].alerts[
index
].link = value;
this.setState({ versions }); this.setState({ versions });
}; };
handleAlertLinkTextChange = (value, version, index) => { handleAlertLinkTextChange = (value, version, index) => {
const { versions } = this.state; const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts[index].linkText = value; versions.filter((vs) => vs.version === version)[0].alerts[
index
].linkText = value;
this.setState({ versions }); this.setState({ versions });
}; };
deleteAlert = (version, index) => { deleteAlert = (version, index) => {
const { versions } = this.state; const { versions } = this.state;
versions.filter((vs) => vs.version === version)[0].alerts.splice(index, 1); versions.filter((vs) => vs.version === version)[0].alerts.splice(index, 1);
if ( if (
versions.filter((vs) => vs.version === version)[0].importantAlert === index || versions.filter((vs) => vs.version === version)[0].importantAlert ===
index ||
versions.filter((vs) => vs.version === version)[0].alerts.length < index versions.filter((vs) => vs.version === version)[0].alerts.length < index
) { ) {
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined; versions.filter(
(vs) => vs.version === version
)[0].importantAlert = undefined;
} }
this.setState({ versions }); this.setState({ versions });
}; };
@@ -244,10 +237,10 @@ export default class Dashboard extends Component {
versions versions
.filter((vs) => vs.version === version)[0] .filter((vs) => vs.version === version)[0]
.alerts.push({ .alerts.push({
title: '', title: "",
description: '', description: "",
link: '', link: "",
linkText: '', linkText: "",
}); });
this.setState({ versions }); this.setState({ versions });
}; };
@@ -255,11 +248,12 @@ export default class Dashboard extends Component {
this.setState({ refreshing: true }); this.setState({ refreshing: true });
const { versions } = this.state; const { versions } = this.state;
const versionData = versions.filter((v) => v.version === version)[0]; const versionData = versions.filter((v) => v.version === version)[0];
if (versionData.alerts.length > 0) this.updateVersion(version.split('.').join(''), versionData); if (versionData.alerts.length > 0)
this.updateVersion(version.split(".").join(""), versionData);
else { else {
notification.error({ notification.error({
message: 'Missing Alerts', message: "Missing Alerts",
description: 'Must have atleast one alert', description: "Must have atleast one alert",
}); });
this.setState({ refreshing: false }); this.setState({ refreshing: false });
@@ -268,7 +262,9 @@ export default class Dashboard extends Component {
changeImportantAlert = (active, version, index) => { changeImportantAlert = (active, version, index) => {
const { versions } = this.state; const { versions } = this.state;
if (active === false) { if (active === false) {
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined; versions.filter(
(vs) => vs.version === version
)[0].importantAlert = undefined;
} else { } else {
versions.filter((vs) => vs.version === version)[0].importantAlert = index; versions.filter((vs) => vs.version === version)[0].importantAlert = index;
} }
@@ -277,16 +273,73 @@ export default class Dashboard extends Component {
getAddMessageText = () => { getAddMessageText = () => {
const { activeTab } = this.state; const { activeTab } = this.state;
switch (activeTab) { switch (activeTab) {
case '1': case "1":
return 'Send Message'; return "Send Message";
case '2': case "2":
return 'Add Version'; return "Add Version";
case '3': case "3":
return 'Add Video Selector'; return "Add Video Selector";
case '4': case "4":
return 'Add Name Selector'; return "Add Name Selector";
default:
return "";
} }
}; };
getPrettyTime = (t) => {
return t > 3600
? new Date(t * 1000).toISOString().substr(11, 8)
: new Date(t * 1000).toISOString().substr(14, 5);
};
handleVideoNameSelectorChange = (event, source, type) => {
const { nameSelectors } = this.state;
nameSelectors[source][type] = event.target.value;
this.setState({ nameSelectors });
};
updateVideoNameSelector = () => {
this.setState({ refreshing: true });
const { nameSelectors } = this.state;
postUpdateVideoNameSelectors({ nameSelectors })
.then(() => this.refreshData())
.catch((err) => {
notification.error({
description: err.message,
message: "Unable To Update",
});
this.setState({ refreshing: false });
});
};
sendOrAdd = () => {
const { activeTab } = this.state;
switch (activeTab) {
case "1":
this.setState({ messageModel: true });
break;
default:
break;
}
};
editMessage = (value, field) => {
const { newMessage } = this.state;
newMessage[field] = value;
this.setState({ newMessage });
};
sendMessage = async () => {
this.setState({ refreshing: true });
const { selectedParties, newMessage } = this.state;
newMessage.time = new Date().toLocaleString();
await postSendMessage({ selectedParties, newMessage }).catch((err) => {
notification.error({
description: err.message,
message: "Unable To Send Message",
});
});
this.setState({
refreshing: false,
newMessage: emptyMessage,
messageModel: false,
});
};
render() { render() {
const { const {
partiesOverview, partiesOverview,
@@ -296,26 +349,133 @@ export default class Dashboard extends Component {
versions, versions,
nameSelectors, nameSelectors,
selectors, selectors,
activeLimit,
afkLimit,
messageModel,
newMessage,
} = this.state; } = this.state;
const versionsColumns = [ const partiesColumns = [
{ {
title: 'Version', title: "Party Name",
dataIndex: 'version', dataIndex: "id",
defaultSortOrder: 'descend', sorter: (a, b) => (a.id.toLowerCase() < b.id.toLowerCase() ? -1 : 1),
sorter: (a, b) =>
parseInt(a.version.split('.').join('')) - parseInt(b.version.split('.').join('')),
}, },
{ {
title: 'Active', title: "Source",
dataIndex: 'active', dataIndex: "source",
sorter: (a, b) =>
a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1,
filters: [
...PLAYER_SOURCES,
{
text: "Is Playing",
value: "PLAYING",
},
{
text: "Is Paused",
value: "PAUSED",
},
],
onFilter: (v, r) => r.source === v || r.status === v,
render: (a, r) => (
<Badge
status={
r.status === "PLAYING"
? "success"
: r.status === "PAUSED"
? "warning"
: "error"
}
text={this.getSourceName(a)}
/>
),
},
{
title: "Created At",
dataIndex: "createTime",
defaultSortOrder: "descend",
sorter: (a, b) => Date.parse(a.createTime) - Date.parse(b.createTime),
render: (a) => new Date(Date.parse(a)).toLocaleString(),
},
{
title: "Updated At",
dataIndex: "updateTime",
filters: [ filters: [
{ {
text: 'Active', text: "Active",
value: "a",
},
{
text: "Inactive",
value: "i",
},
],
onFilter: (v, r) =>
v === "a"
? (Date.now() - Date.parse(r.updateTime)) / 60000 < afkLimit
: (Date.now() - Date.parse(r.updateTime)) / 60000 > afkLimit,
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
render: (a) => {
const minutes = (Date.now() - Date.parse(a)) / 60000;
const dS = new Date(a).toLocaleString();
return (
<Tag
color={
minutes <= activeLimit
? "success"
: minutes <= afkLimit
? "processing"
: "default"
}
>
{dS}
</Tag>
);
},
},
{
title: "On Call",
dataIndex: "onCall",
filters: [
{
text: "On Call",
value: true, value: true,
}, },
{ {
text: 'Inactive', text: "Not On Call",
value: false,
},
],
render: (a) =>
a ? (
<CheckCircleTwoTone twoToneColor="#c0f300" id="call-icon" />
) : (
<CloseCircleTwoTone twoToneColor="#ff5f5f" id="call-icon" />
),
onFilter: (v, r) => r.onCall === v,
},
];
const versionsColumns = [
{
title: "Version",
dataIndex: "version",
defaultSortOrder: "descend",
sorter: (a, b) =>
parseInt(a.version.split(".").join("")) -
parseInt(b.version.split(".").join("")),
},
{
title: "Active",
dataIndex: "active",
filters: [
{
text: "Active",
value: true,
},
{
text: "Inactive",
value: false, value: false,
}, },
], ],
@@ -323,18 +483,22 @@ export default class Dashboard extends Component {
render: (a, r) => ( render: (a, r) => (
<Switch <Switch
disabled={refreshing} disabled={refreshing}
onChange={(c) => this.updateVersionActive(c, r.version.split('.').join(''))} onChange={(c) =>
this.updateVersionActive(c, r.version.split(".").join(""))
}
checked={a} checked={a}
/> />
), ),
}, },
{ {
title: 'Inactive Message', title: "Inactive Message",
dataIndex: 'message', dataIndex: "message",
render: (a, r) => ( render: (a, r) => (
<Input.Search <Input.Search
disabled={refreshing} disabled={refreshing}
onSearch={(v) => this.updateVersionMessage(v, r.version.split('.').join(''))} onSearch={(v) =>
this.updateVersionMessage(v, r.version.split(".").join(""))
}
value={a} value={a}
onChange={(e) => this.handleVersionMessageChange(e, r.version)} onChange={(e) => this.handleVersionMessageChange(e, r.version)}
enterButton={<EnterOutlined />} enterButton={<EnterOutlined />}
@@ -344,24 +508,11 @@ export default class Dashboard extends Component {
]; ];
return ( return (
<div className="dashboard-wrapper"> <div className="dashboard-wrapper">
<Card>
<PageHeader <PageHeader
className="title-header"
title="WATCH PARTY"
subTitle="ADMIN DASHBOARD"
extra={[ extra={[
<Button
key="3"
loading={refreshing}
disabled={refreshing}
onClick={this.refreshData}
shape="circle"
icon={refreshing ? <LoadingOutlined /> : <ReloadOutlined />}
/>,
<Button
key="2"
icon={activeTab === '1' ? <SendOutlined /> : <PlusCircleOutlined />}
disabled={activeTab === '1' && selectedParties.length === 0}
>
{this.getAddMessageText()}
</Button>,
<Button <Button
onClick={() => firebase.auth().signOut()} onClick={() => firebase.auth().signOut()}
key="1" key="1"
@@ -373,16 +524,135 @@ export default class Dashboard extends Component {
</Button>, </Button>,
]} ]}
/> />
<Modal
visible={messageModel}
closable={false}
destroyOnClose
maskClosable={false}
cancelButtonProps={{
disabled: refreshing,
onClick: () => this.setState({ messageModel: false }),
}}
okButtonProps={{
disabled: refreshing,
icon: <SendOutlined />,
onClick: this.sendMessage,
}}
okText="Send Message"
>
<Select
onChange={(e) => this.editMessage(e, "type")}
value={newMessage.type}
placeholder="Message Type"
disabled={refreshing}
>
{MESSAGE_TYPES.map((v) => (
<Select.Option key={`msg-type-option-${v.value}`} value={v.value}>
{v.text}
</Select.Option>
))}
</Select>
<Input.TextArea
disabled={refreshing}
value={newMessage.text}
onChange={(e) => this.editMessage(e.target.value, "text")}
placeholder="Message"
/>
{(newMessage.type === "interactive/link" ||
newMessage.type === "interactive/text") && (
<Input
disabled={refreshing}
value={newMessage.endpoint}
onChange={(e) => this.editMessage(e.target.value, "endpoint")}
placeholder={
newMessage.type === "interactive/link" ? "Link" : "Endpoint"
}
/>
)}
{newMessage.type === "interactive/link" && (
<Input
disabled={refreshing}
value={newMessage.name}
onChange={(e) => this.editMessage(e.target.value, "name")}
placeholder="Link Text"
/>
)}
</Modal>
<Card>
<PageHeader
extra={[
activeTab === "1" && (
<Badge key="5" count="ACTIVE" className="active">
<InputNumber
prefix="Active"
value={activeLimit}
onChange={(activeLimit) => this.setState({ activeLimit })}
/>
</Badge>
),
activeTab === "1" && (
<Badge key="4" count="AFK" className="afk">
<InputNumber
prefix="AFK"
key="4"
value={afkLimit}
onChange={(afkLimit) => this.setState({ afkLimit })}
/>
</Badge>
),
<Button
key="3"
loading={refreshing}
disabled={refreshing}
onClick={this.refreshData}
shape="circle"
icon={refreshing ? <LoadingOutlined /> : <ReloadOutlined />}
/>,
<Button
key="2"
onClick={this.sendOrAdd}
icon={
activeTab === "1" ? <SendOutlined /> : <PlusCircleOutlined />
}
disabled={activeTab === "1" && selectedParties.length === 0}
>
{this.getAddMessageText()}
</Button>,
activeTab === "3" && (
<Button
type="primary"
onClick={this.updateVideoSelector}
icon={<SaveOutlined />}
key="6"
loading={refreshing}
>
Save Video Selectors
</Button>
),
activeTab === "4" && (
<Button
type="primary"
onClick={this.updateVideoNameSelector}
icon={<SaveOutlined />}
key="7"
loading={refreshing}
>
Save Video Name Selectors
</Button>
),
]}
/>
<Tabs activeKey={activeTab} onChange={this.tabChange}> <Tabs activeKey={activeTab} onChange={this.tabChange}>
<Tabs.TabPane tab="Parties" key="1"> <Tabs.TabPane tab="Parties" key="1">
<Table <Table
rowSelection={{ rowSelection={{
selectedRowKeys: selectedParties, selectedRowKeys: selectedParties,
onChange: (selectedParties) => this.setState({ selectedParties }), onChange: (selectedParties) =>
this.setState({ selectedParties }),
}} }}
loading={partiesOverview === null || refreshing} loading={partiesOverview === null || refreshing}
pagination={{ pagination={{
position: ['bottomCenter'], position: ["bottomCenter"],
total: partiesOverview ? partiesOverview.length : 0, total: partiesOverview ? partiesOverview.length : 0,
showTotal: (t, r) => `${r[0]} to ${r[1]} of ${t}`, showTotal: (t, r) => `${r[0]} to ${r[1]} of ${t}`,
defaultPageSize: 10, defaultPageSize: 10,
@@ -390,23 +660,52 @@ export default class Dashboard extends Component {
pageSizeOptions: [10, 25, 50], pageSizeOptions: [10, 25, 50],
}} }}
rowKey={(p) => p.id} rowKey={(p) => p.id}
onRow={(r) => ({
onClick: () => this.viewPartyDetails(r),
})}
dataSource={partiesOverview} dataSource={partiesOverview}
columns={partiesColumns} columns={partiesColumns}
expandable={{
rowExpandable: (r) => true,
expandedRowClassName: () => "party-details-wrapper",
expandedRowRender: (r) => (
<Descriptions bordered column={5}>
<Descriptions.Item span={2} label="# Of Users">
{r.userCount}
</Descriptions.Item>
<Descriptions.Item span={3} label="# Of Messages">
{r.messageCount}
</Descriptions.Item>
<Descriptions.Item span={5} label="Show Name">
{r.showName}
</Descriptions.Item>
<Descriptions.Item span={2} label="Show Time">
{this.getPrettyTime(r.time)}
</Descriptions.Item>
<Descriptions.Item span={3} label="Show Status">
<Badge
status={
r.status === "PLAYING"
? "success"
: r.status === "PAUSED"
? "warning"
: "error"
}
text={r.status}
/>
</Descriptions.Item>
</Descriptions>
),
}}
/> />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab="Versions" key="2"> <Tabs.TabPane tab="Versions" key="2">
<Table <Table
loading={refreshing} loading={refreshing}
pagination={{ position: ['bottomCenter'] }} pagination={{ position: ["bottomCenter"] }}
rowKey={(p) => p.version} rowKey={(p) => p.version}
dataSource={versions} dataSource={versions}
columns={versionsColumns} columns={versionsColumns}
expandable={{ expandable={{
rowExpandable: (r) => r.alerts, rowExpandable: (r) => r.alerts,
expandedRowClassName: () => 'alerts-wrapper', expandedRowClassName: () => "alerts-wrapper",
expandedRowRender: (r) => ( expandedRowRender: (r) => (
<Row> <Row>
{r.alerts.map((a, i) => ( {r.alerts.map((a, i) => (
@@ -422,9 +721,15 @@ export default class Dashboard extends Component {
/>, />,
<Switch <Switch
onClick={(active) => onClick={(active) =>
this.changeImportantAlert(active, r.version, i) this.changeImportantAlert(
active,
r.version,
i
)
}
checked={
r.importantAlert && r.importantAlert === i
} }
checked={r.importantAlert && r.importantAlert === i}
/>, />,
]} ]}
> >
@@ -432,7 +737,11 @@ export default class Dashboard extends Component {
placeholder="title" placeholder="title"
value={a.title} value={a.title}
onChange={(e) => onChange={(e) =>
this.handleAlertTitleChange(e.target.value, r.version, i) this.handleAlertTitleChange(
e.target.value,
r.version,
i
)
} }
/> />
<Input.TextArea <Input.TextArea
@@ -440,7 +749,11 @@ export default class Dashboard extends Component {
autoSize={{ minRows: 2, maxRows: 2 }} autoSize={{ minRows: 2, maxRows: 2 }}
value={a.description} value={a.description}
onChange={(e) => onChange={(e) =>
this.handleAlertDescriptionChange(e.target.value, r.version, i) this.handleAlertDescriptionChange(
e.target.value,
r.version,
i
)
} }
/> />
<Divider orientation="left">Link</Divider> <Divider orientation="left">Link</Divider>
@@ -449,14 +762,22 @@ export default class Dashboard extends Component {
value={a.linkText} value={a.linkText}
placeholder="Link Text" placeholder="Link Text"
onChange={(e) => onChange={(e) =>
this.handleAlertLinkTextChange(e.target.value, r.version, i) this.handleAlertLinkTextChange(
e.target.value,
r.version,
i
)
} }
/> />
<Input <Input
value={a.link} value={a.link}
placeholder="Link" placeholder="Link"
onChange={(e) => onChange={(e) =>
this.handleAlertLinkChange(e.target.value, r.version, i) this.handleAlertLinkChange(
e.target.value,
r.version,
i
)
} }
/> />
</Input.Group> </Input.Group>
@@ -472,7 +793,10 @@ export default class Dashboard extends Component {
</Card> </Card>
</Col> </Col>
<Col> <Col>
<Card onClick={() => this.saveAlerts(r.version)} className="add-alert-card"> <Card
onClick={() => this.saveAlerts(r.version)}
className="add-alert-card"
>
<SaveFilled /> <SaveFilled />
</Card> </Card>
</Col> </Col>
@@ -493,19 +817,41 @@ export default class Dashboard extends Component {
onChange={(e) => this.handleVideoSelectorChange(e, source)} onChange={(e) => this.handleVideoSelectorChange(e, source)}
/> />
))} ))}
<Button
block
type="primary"
icon={<SaveOutlined />}
className="save-video-selector"
onClick={this.updateVideoSelector}
loading={refreshing}
>
Save Video Selectors
</Button>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane tab="Name Selectors" key="4"> <Tabs.TabPane tab="Name Selectors" key="4">
name selectors <Collapse ghost>
{Object.keys(nameSelectors)
.sort()
.map((source) => (
<Collapse.Panel
key={`name-selector-${source}`}
header={this.getSourceName(source)}
>
<Input
key={`video-selector-${source}-show`}
className="video-selector-input"
addonBefore={"Show"}
value={nameSelectors[source]["show"]}
onChange={(e) =>
this.handleVideoNameSelectorChange(e, source, "show")
}
/>
<Input
key={`video-selector-${source}-episode`}
className="video-selector-input"
addonBefore={"Episode"}
value={nameSelectors[source]["episode"]}
onChange={(e) =>
this.handleVideoNameSelectorChange(
e,
source,
"episode"
)
}
/>
</Collapse.Panel>
))}
</Collapse>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
</Card> </Card>
+52 -3
View File
@@ -1,5 +1,5 @@
@import '~antd/dist/antd.css'; @import "~antd/dist/antd.css";
@import '~antd/dist/antd.dark.css' screen and (prefers-color-scheme: dark); @import "~antd/dist/antd.dark.css" screen and (prefers-color-scheme: dark);
#root, #root,
#root .app-wrapper { #root .app-wrapper {
@@ -29,8 +29,12 @@
border-top-left-radius: 0px; border-top-left-radius: 0px;
} }
.dashboard-wrapper {
padding: 35px 70px;
padding-top: 0;
}
.dashboard-wrapper .ant-card { .dashboard-wrapper .ant-card {
margin: 35px 70px;
border-radius: 26px; border-radius: 26px;
filter: drop-shadow(0px 0px 5px rgba(221, 221, 221, 0.438)); filter: drop-shadow(0px 0px 5px rgba(221, 221, 221, 0.438));
} }
@@ -52,6 +56,7 @@
margin: 5px 2px !important; margin: 5px 2px !important;
filter: none !important; filter: none !important;
border-radius: initial !important; border-radius: initial !important;
max-width: 400px;
} }
.alerts-wrapper .ant-card-body input, .alerts-wrapper .ant-card-body input,
@@ -81,3 +86,47 @@ span#call-icon svg {
width: 25px; width: 25px;
height: 25px; height: 25px;
} }
.title-header {
margin-bottom: 17.5px;
}
.title-header .ant-page-header-heading-title,
.title-header .ant-page-header-heading-sub-title {
letter-spacing: 1.5px;
}
.party-details-wrapper > td {
padding: 0 !important;
}
.party-details-wrapper .ant-descriptions-view table {
width: 100% !important;
}
.party-details-wrapper .ant-descriptions-view table td,
.party-details-wrapper .ant-descriptions-view table th {
border-bottom: 1px solid #f0f0f0;
}
.ant-page-header .ant-badge {
margin-right: 10px;
}
.ant-page-header .ant-badge-count {
right: 100%;
z-index: 9;
top: -10%;
}
.ant-page-header .active .ant-badge-count {
background: green;
}
.ant-page-header .afk .ant-badge-count {
background: rgb(79, 120, 209);
}
.ant-modal-body > * {
margin: 5px 0px;
width: 100%;
}
+6 -4
View File
@@ -9,14 +9,16 @@ ALL CONTENT ON WATCH PARTY'S WEBSITE OR IN WATCH PARTY'S CHROME EXTENSION APP, U
CITED ARE THE SOLE INTELECTUAL PROPERTY OF AYUSHYA AMITABH, AND MAY NOT BE CITED ARE THE SOLE INTELECTUAL PROPERTY OF AYUSHYA AMITABH, AND MAY NOT BE
COPIED, EDITED, OR REPRODUCED WITHOUT PERMISSION. COPIED, EDITED, OR REPRODUCED WITHOUT PERMISSION.
************************************************************************/ ************************************************************************/
import React from 'react'; import React from "react";
import { Typography } from '@material-ui/core'; import { Typography } from "@material-ui/core";
import { Warning } from '@material-ui/icons'; import { Warning } from "@material-ui/icons";
const Inactive = (props) => ( const Inactive = (props) => (
<div id="party-loading" className="inactive"> <div id="party-loading" className="inactive">
<Warning /> <Warning />
<Typography variant="overline">SORRY, WATCH PARTY IS UNAVAILABLE RIGHT NOW</Typography> <Typography variant="overline">
SORRY, WATCH PARTY IS UNAVAILABLE RIGHT NOW
</Typography>
<Typography variant="caption">{props.status}</Typography> <Typography variant="caption">{props.status}</Typography>
</div> </div>
); );
+9 -5
View File
@@ -1,10 +1,14 @@
const superAuth = require('./superAuth'); const superAuth = require("./superAuth");
const partyDashboard = require('./partyDashboard'); const partyDashboard = require("./partyDashboard");
const serverDashboard = require('./serverDashboard'); const serverDashboard = require("./serverDashboard");
const updateVersionActive = require('./updateVersionActive'); const updateVersionActive = require("./updateVersionActive");
const updateVideoSelectors = require('./updateVideoSelectors'); const updateVideoSelectors = require("./updateVideoSelectors");
const updateVideoNameSelectors = require("./updateVideoNameSelectors");
const sendMessage = require("./sendMessage");
module.exports = { module.exports = {
sendMessage,
updateVideoNameSelectors,
updateVideoSelectors, updateVideoSelectors,
updateVersionActive, updateVersionActive,
serverDashboard, serverDashboard,
+22 -17
View File
@@ -1,14 +1,14 @@
/* eslint-disable promise/no-nesting */ /* eslint-disable promise/no-nesting */
const admin = require('firebase-admin'); const admin = require("firebase-admin");
const Logger = require('../Logger'); const Logger = require("../Logger");
const mLogger = new Logger('admin', 'partyDashboard'); const mLogger = new Logger("admin", "partyDashboard");
module.exports = async (req, res) => { module.exports = async (req, res) => {
mLogger.start(); mLogger.start();
var partyData = await admin var partyData = await admin
.firestore() .firestore()
.collection('rooms') .collection("rooms")
.get() .get()
.then((roomSnapshot) => { .then((roomSnapshot) => {
const docData = []; const docData = [];
@@ -18,38 +18,43 @@ module.exports = async (req, res) => {
createTime: doc.createTime.toDate().toUTCString(), createTime: doc.createTime.toDate().toUTCString(),
updateTime: doc.updateTime.toDate().toUTCString(), updateTime: doc.updateTime.toDate().toUTCString(),
source: doc.data().player.source, source: doc.data().player.source,
onCall: doc.data().hasOwnProperty('conference'), onCall: doc.data().hasOwnProperty("conference"),
showName: doc.data().player.showName || '', showName: doc.data().player.showName || "",
time: doc.data().player.time || 0, time: doc.data().player.time || 0,
status: doc.data().player.status || '', status: doc.data().player.status || "",
}); });
}); });
return docData; return docData;
}) })
.catch((err) => { .catch((err) => {
mLogger.error('FAILED TO RESOLVE PARTIES', err); mLogger.error("FAILED TO RESOLVE PARTIES", err);
res.status(500).send({ message: 'Unable to get all parties', error: err }); res
.status(500)
.send({ message: "Unable to get all parties", error: err });
}); });
await partyData.forEach((party) => { const p = await partyData.map(async (party) => {
const messageCount = admin const messageCount = await admin
.firestore() .firestore()
.collection('rooms') .collection("rooms")
.doc(party.id) .doc(party.id)
.collection('messages') .collection("messages")
.get() .get()
.then((snap) => snap.size); .then((snap) => snap.size);
const userCount = admin const userCount = await admin
.firestore() .firestore()
.collection('rooms') .collection("rooms")
.doc(party.id) .doc(party.id)
.collection('users') .collection("users")
.get() .get()
.then((snap) => snap.size); .then((snap) => snap.size);
party.messageCount = messageCount || 0; party.messageCount = messageCount || 0;
party.userCount = userCount || 0; party.userCount = userCount || 0;
return party;
}); });
mLogger.success('GOT ALL PARTIES', { parties: partyData.length }); return Promise.all(p).then((v) => {
mLogger.success("GOT ALL PARTIES", { parties: partyData.length });
return res.status(200).send({ partyData }); return res.status(200).send({ partyData });
});
}; };
+32
View File
@@ -0,0 +1,32 @@
const admin = require("firebase-admin");
const Logger = require("../Logger");
const mLogger = new Logger("admin", "sendMessage");
module.exports = async (req, res) => {
mLogger.start();
const { selectedParties, newMessage } = JSON.parse(req.body);
if (selectedParties && newMessage && selectedParties.length > 0) {
if (newMessage.text && newMessage.time && newMessage.uid) {
newMessage.time = new Date();
await selectedParties.map(async (partyId) => {
await admin
.firestore()
.collection("rooms")
.doc(partyId)
.collection("messages")
.add(newMessage)
.catch((err) => {
mLogger.error("FAILED TO SEND MESSAGE", { partyId, err });
});
});
mLogger.success("SENT MESSAGES", { newMessage, selectedParties });
res.status(200).send({ message: "SENT MESSAGES" });
} else {
mLogger.error("MESSAGE NOT VALID");
res.status(500).send({ message: "Message is missing fields" });
}
} else {
mLogger.error("VALUES NOT RECEIVED");
res.status(500).send({ message: "Values not received" });
}
};
@@ -0,0 +1,26 @@
const admin = require("firebase-admin");
const Logger = require("../Logger");
const mLogger = new Logger("admin", "updateVideoNameSelectors");
module.exports = async (req, res) => {
mLogger.start();
const { nameSelectors } = JSON.parse(req.body);
if (nameSelectors) {
await admin
.firestore()
.collection("server")
.doc("status")
.update({
nameSelectors,
})
.catch((err) => {
mLogger.error("FAILED TO UPDATE", err);
res.status(500).send({ message: "FAILED TO UPDATE", error: err });
});
mLogger.success("UPDATED NAME SELECTORS");
res.status(200).send({ message: "Updated video name selectors" });
} else {
mLogger.error("VALUES NOT RECEIVED");
res.status(500).send({ message: "Values not received" });
}
};
+30 -24
View File
@@ -1,40 +1,46 @@
const FunctionModules = require('./modules/index'); const FunctionModules = require("./modules/index");
const Webhooks = require('./webhooks/index'); const Webhooks = require("./webhooks/index");
const AdminModules = require('./admin/index'); const AdminModules = require("./admin/index");
const express = require('express'); const express = require("express");
const cors = require('cors'); const cors = require("cors");
// WEBHOOKS // WEBHOOKS
const webhooks = express(); const webhooks = express();
webhooks.use(cors({ origin: true })); webhooks.use(cors({ origin: true }));
webhooks.all('/conferenceEnded', Webhooks.conferenceEnd); webhooks.all("/conferenceEnded", Webhooks.conferenceEnd);
webhooks.all('/scheduledCleanup', Webhooks.scheduledCleanUp); webhooks.all("/scheduledCleanup", Webhooks.scheduledCleanUp);
webhooks.all('/ask', Webhooks.ask); webhooks.all("/ask", Webhooks.ask);
webhooks.all('/userCleanUp', Webhooks.userCleanUp); webhooks.all("/userCleanUp", Webhooks.userCleanUp);
// VERSION 1 // VERSION 1
const app = express(); const app = express();
app.use(cors({ origin: true })); app.use(cors({ origin: true }));
app.use(FunctionModules.authenticator); app.use(FunctionModules.authenticator);
app.get('/validator/:partyName', FunctionModules.tokenValidator); app.get("/validator/:partyName", FunctionModules.tokenValidator);
app.get('/refresher/:token', FunctionModules.tokenRefresher); app.get("/refresher/:token", FunctionModules.tokenRefresher);
app.post('/create', FunctionModules.createRoom); app.post("/create", FunctionModules.createRoom);
app.post('/join', FunctionModules.joinRoom); app.post("/join", FunctionModules.joinRoom);
app.post('/message', FunctionModules.sendMessage); app.post("/message", FunctionModules.sendMessage);
app.post('/updatePlayer', FunctionModules.updatePlayer); app.post("/updatePlayer", FunctionModules.updatePlayer);
app.post('/updateVoxeet', FunctionModules.updateVoxeet); app.post("/updateVoxeet", FunctionModules.updateVoxeet);
app.delete('/leave/:rid/users/:uid', FunctionModules.leaveRoom); app.delete("/leave/:rid/users/:uid", FunctionModules.leaveRoom);
app.delete('/delete/:rid', FunctionModules.deleteRoom); app.delete("/delete/:rid", FunctionModules.deleteRoom);
app.put('/updateAdminOnly/:rid/:adminOnly', FunctionModules.updateAdminOnly); app.put("/updateAdminOnly/:rid/:adminOnly", FunctionModules.updateAdminOnly);
// ADMIN // ADMIN
const admin = express(); const admin = express();
admin.use(cors({ origin: ['http://localhost:3000', 'https://admin.chromewatchparty.com/*'] })); admin.use(
cors({
origin: ["http://localhost:3000", "https://admin.chromewatchparty.com/*"],
})
);
admin.use(AdminModules.superAuth); admin.use(AdminModules.superAuth);
admin.get('/partyDashboard', AdminModules.partyDashboard); admin.get("/partyDashboard", AdminModules.partyDashboard);
admin.get('/serverDashboard', AdminModules.serverDashboard); admin.get("/serverDashboard", AdminModules.serverDashboard);
admin.post('/updateVersionActive', AdminModules.updateVersionActive); admin.post("/updateVersionActive", AdminModules.updateVersionActive);
admin.post('/updateVideoSelectors', AdminModules.updateVideoSelectors); admin.post("/updateVideoSelectors", AdminModules.updateVideoSelectors);
admin.post("/updateVideoNameSelectors", AdminModules.updateVideoNameSelectors);
admin.post("/sendMessage", AdminModules.sendMessage);
module.exports = { module.exports = {
v1: app, v1: app,