diff --git a/packages/app/src/pages/live_control/index.jsx b/packages/app/src/pages/live_control/index.jsx
new file mode 100644
index 00000000..5903e7e8
--- /dev/null
+++ b/packages/app/src/pages/live_control/index.jsx
@@ -0,0 +1,147 @@
+import React from "react"
+import * as antd from "antd"
+import { Icons } from "components/Icons"
+
+import "./index.less"
+
+const StreamingKeyView = (props) => {
+ const [streamingKeyVisibility, setStreamingKeyVisibility] = React.useState(false)
+
+ const toogleVisibility = (to) => {
+ setStreamingKeyVisibility(to ?? !streamingKeyVisibility)
+ }
+
+ return
+ {streamingKeyVisibility ?
+ <>
+
toogleVisibility()} />
+
+ {props.streamingKey ?? "No streaming key available"}
+
+ > :
+ toogleVisibility()}
+ >
+
+ Click to show key
+
+ }
+
+}
+
+export default (props) => {
+ const [streamInfo, setStreamInfo] = React.useState(null)
+
+ const [isConnected, setIsConnected] = React.useState(false)
+ const [targetServer, setTargetServer] = React.useState("No available server")
+
+ const [streamingKey, setStreamingKey] = React.useState(null)
+
+ const checkStreamingKey = async () => {
+ const result = await app.api.withEndpoints("main").get.streamingKey().catch((error) => {
+ console.error(error)
+ antd.message.error(error.message)
+
+ return null
+ })
+
+ if (result) {
+ setStreamingKey(result.key)
+ }
+ }
+
+ const regenerateStreamingKey = async () => {
+ antd.Modal.confirm({
+ title: "Regenerate streaming key",
+ content: "Are you sure you want to regenerate the streaming key? After this, all other generated keys will be deleted.",
+ onOk: async () => {
+ const result = await app.api.withEndpoints("main").post.regenerateStreamingKey().catch((error) => {
+ console.error(error)
+ antd.message.error(error.message)
+
+ return null
+ })
+
+ if (result) {
+ setStreamingKey(result.key)
+ }
+ }
+ })
+ }
+
+ React.useEffect(() => {
+ checkStreamingKey()
+ }, [])
+
+ return
+
+
+

+
+
+
+
+
:
}
+ >
+ {isConnected ? "Connected" : "Disconnected"}
+
+
+
+
+ Title
+
+
+ {streamInfo?.title ?? "No title"}
+
+
+
+
+
+ Category
+
+
+ {streamInfo?.category ?? "No category"}
+
+
+
+
+
+
+
+
Emission
+
+
+ Ingestion URL
+
+
+ {targetServer}
+
+
+
+
+
+
+ Streaming key
+
+
+
regenerateStreamingKey()}>
+
+ Regenerate
+
+
+
+
+
+
+
+
+
+
+
+
Additional options
+
+
+
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/live_control/index.less b/packages/app/src/pages/live_control/index.less
new file mode 100644
index 00000000..bf0fe451
--- /dev/null
+++ b/packages/app/src/pages/live_control/index.less
@@ -0,0 +1,121 @@
+.streamingControlPanel {
+ display: flex;
+ flex-direction: column;
+
+ transition: all 0.3s ease-in-out;
+
+ .header {
+ display: flex;
+ flex-direction: row;
+
+ height: 20vh;
+
+ padding: 15px;
+
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+
+ margin-bottom: 20px;
+
+ transition: all 0.3s ease-in-out;
+
+ .preview {
+ height: 100%;
+ max-width: 400px;
+
+ img {
+ width: 100%;
+ height: 100%;
+
+ border-radius: 12px;
+ }
+
+ margin-right: 40px;
+ }
+
+ .details {
+ display: inline-flex;
+ flex-direction: column;
+
+ padding: 20px 0;
+
+ .status {
+ margin-bottom: 20px;
+ }
+ }
+ }
+
+ .config {
+ display: flex;
+
+ padding: 0 40px;
+
+ transition: all 0.3s ease-in-out;
+
+ code {
+ padding: 5px 8px;
+ font-size: 1rem;
+
+ background-color: var(--background-color-accent);
+ color: var(--text-color);
+
+ border-radius: 8px;
+
+ font-family: "DM Mono", monospace;
+
+ user-select: all;
+ }
+
+ .panel {
+ display: flex;
+ flex-direction: column;
+
+ margin-right: 50px;
+
+ .content {
+ display: flex;
+ flex-direction: column;
+
+ margin: 20px 20px 20px 0;
+
+ width: 100%;
+
+ .title {
+ display: inline-flex;
+ flex-direction: row;
+
+ justify-content: space-between;
+ align-items: center;
+
+ width: 100%;
+ margin-bottom: 8px;
+ }
+ }
+ }
+ }
+
+}
+
+.streamingKeyString {
+ display: inline-flex;
+ flex-direction: row;
+
+ align-items: center;
+ justify-content: center;
+
+ color: var(--text-color);
+
+ svg {
+ font-size: 1.5rem;
+
+ cursor: pointer;
+ }
+
+ div {
+ display: inline-flex;
+ flex-direction: row;
+
+ align-items: center;
+ justify-content: center;
+ }
+}
\ No newline at end of file
diff --git a/packages/app/src/pages/streaming_control/index.jsx b/packages/app/src/pages/streaming_control/index.jsx
deleted file mode 100755
index c54a38e9..00000000
--- a/packages/app/src/pages/streaming_control/index.jsx
+++ /dev/null
@@ -1,176 +0,0 @@
-import React from "react"
-import * as antd from "antd"
-import { Icons } from "components/Icons"
-
-import "./index.less"
-
-const StreamingKeyView = (props) => {
- const [streamingKeyVisibility, setStreamingKeyVisibility] = React.useState(false)
-
- const toogleVisibility = (to) => {
- setStreamingKeyVisibility(to ?? !streamingKeyVisibility)
- }
-
- return
- {streamingKeyVisibility ?
- <>
- toogleVisibility()} />
-
- {props.streamingKey ?? "No streaming key available"}
-
- > :
- <>
- toogleVisibility()} />
- Show key
- >
- }
-
-}
-
-export default (props) => {
- const [isConnected, setIsConnected] = React.useState(false)
- const [targetServer, setTargetServer] = React.useState("No available server")
-
- const [streamingKey, setStreamingKey] = React.useState(null)
- const [serverTier, setServerTier] = React.useState(null)
-
- const checkStreamingKey = async () => {
- const result = await app.api.withEndpoints("main").get.streamingKey().catch((error) => {
- console.error(error)
- antd.message.error(error.message)
-
- return null
- })
-
- if (result) {
- setStreamingKey(result.key)
- }
- }
-
- const checkTagetServer = async () => {
- const result = await app.api.withEndpoints("main").get.targetStreamingServer()
-
- if (result) {
- const targetServer = `${result.protocol}://${result.address}:${result.port}/${result.space}`
- setTargetServer(targetServer)
- }
- }
-
- const regenerateStreamingKey = async () => {
- antd.Modal.confirm({
- title: "Regenerate streaming key",
- content: "Are you sure you want to regenerate the streaming key? After this, all other generated keys will be deleted.",
- onOk: async () => {
- const result = await app.api.withEndpoints("main").post.regenerateStreamingKey().catch((error) => {
- console.error(error)
- antd.message.error(error.message)
-
- return null
- })
-
- if (result) {
- setStreamingKey(result.key)
- }
- }
- })
- }
-
- React.useEffect(() => {
- checkStreamingKey()
- checkTagetServer()
- // TODO: Use UserTier controller to check streaming service tier
- // by now, we just use a fixed value
- setServerTier("basic")
- }, [])
-
- return
-
-
Connection Status
-
-
-
:
}
- >
- {isConnected ? "Connected" : "Disconnected"}
-
-
-
-
-
-
Stream information
-
-
-
-
-
-
-
-
Server info
-
-
-
-
- Server Address
-
-
-
- {targetServer}
-
-
-
-
-
-
-
- Streaming Key
-
-
-
-
-
-
regenerateStreamingKey()}>
-
- Regenerate
-
-
-
-
-
-
-
-}
\ No newline at end of file
diff --git a/packages/app/src/pages/streaming_control/index.less b/packages/app/src/pages/streaming_control/index.less
deleted file mode 100755
index c128686c..00000000
--- a/packages/app/src/pages/streaming_control/index.less
+++ /dev/null
@@ -1,36 +0,0 @@
-.streamingControlPanel {
- display: inline-flex;
- flex-direction: column;
-
- .info {
- display: flex;
- flex-direction: column;
-
- margin-bottom: 10px;
-
- .label {
-
- }
-
- .value {
- margin-left: 10px;
- font-family: "DM Mono", monospace;
-
- h4 {
- // select all text
- user-select: all;
- margin: 0;
- }
- }
- }
-
- > div {
- margin-bottom: 20px;
- }
-}
-
-.streamingKeyString {
- display: inline-flex;
- flex-direction: row;
- align-items: center;
-}
\ No newline at end of file