rustream/templates/
index.rs

1/// Get the HTML content to render the index/root page.
2///
3/// # See Also
4///
5/// - This page is served as a response for the `/` entry point.
6///
7/// # Returns
8///
9/// A `String` version of the HTML, CSS and JS content.
10pub fn get_content() -> String {
11    r###"<!DOCTYPE html>
12<!--suppress JSUnresolvedLibraryURL -->
13<html lang="en">
14<head>
15    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
16    <title>RuStream - Self-hosted Streaming Engine - v{{ version }}</title>
17    <meta property="og:type" content="MediaStreaming">
18    <meta name="keywords" content="Rust, streaming, actix, JavaScript, HTML, CSS">
19    <meta name="author" content="Vignesh Rao">
20    <meta content="width=device-width, initial-scale=1" name="viewport">
21    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
22    <script src="https://thevickypedia.github.io/open-source/crypto/crypto.js"></script>
23    <!-- Favicon.ico and Apple Touch Icon -->
24    <link rel="icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.ico">
25    <link rel="apple-touch-icon" href="https://thevickypedia.github.io/open-source/images/logo/actix.png">
26    <!-- Font Awesome icons -->
27    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/fontawesome.min.css">
28    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/solid.min.css">
29    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/regular.min.css">
30    <style>
31        @import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@400;500;700&display=swap');
32        * {
33            font-family: 'Ubuntu', 'PT Serif', sans-serif;
34        }
35        body {
36            margin: 0;
37            padding: 0;
38            background-color: #151515;
39        }
40
41        .container {
42            max-width: 400px;
43            margin: 50px auto;
44            background: #fff;
45            padding: 20px;
46            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
47            border-radius: 8px;
48        }
49
50        .header {
51            text-align: center;
52            margin-bottom: 20px;
53        }
54
55        .header img {
56            max-width: 150px;
57        }
58
59        .content {
60            display: flex;
61            flex-direction: column;
62        }
63
64        form {
65            display: flex;
66            flex-direction: column;
67        }
68
69        label {
70            margin-top: 10px;
71            color: #000000;
72            font-size: large;
73            font-weight: normal;
74        }
75
76        input {
77            padding: 10px;
78            margin-bottom: 15px;
79            border: 1px solid #ddd;
80            border-radius: 5px;
81        }
82
83        button {
84            padding: 12px;
85            background-color: #000000;
86            color: #fff;
87            border: none;
88            border-radius: 5px;
89            cursor: pointer;
90            font-weight: bold;
91        }
92
93        button:hover {
94            background-color: #494949;
95        }
96
97    </style>
98    <style>
99        .password-container {
100            position: relative;
101        }
102        .password-container input[type="password"],
103        .password-container input[type="text"]{
104            width: 100%;
105            padding: 12px 36px 12px 12px;
106            box-sizing: border-box;
107        }
108        .fa-eye, .fa-eye-slash {
109            position: absolute;
110            top: 40%;
111            right: 4%;
112            transform: translateY(-50%);
113            cursor: pointer;
114            color: lightgray;
115        }
116    </style>
117    <noscript>
118        <style>
119            body {
120                width: 100%;
121                height: 100%;
122                overflow: hidden;
123            }
124        </style>
125        <div style="position: fixed; text-align:center; height: 100%; width: 100%; background-color: #151515;">
126            <h2 style="margin-top:5%">This page requires JavaScript
127                to be enabled.
128                <br><br>
129                Please refer <a href="https://www.enable-javascript.com/">enable-javascript</a> for how to.
130            </h2>
131            <form>
132                <button type="submit" onClick="<meta httpEquiv='refresh' content='0'>">RETRY</button>
133            </form>
134        </div>
135    </noscript>
136</head>
137<body>
138<div class="container">
139    <div class="header">
140        <i class="fa-solid fa-user"></i>
141    </div>
142    <div class="content">
143        <!-- <form action="{ url_for('signin') }" method="post"> -->
144        <form>
145            <label for="username">Username:</label>
146            <input type="text" id="username" name="username" required>
147            <label for="password">Password:</label>
148            <div class="password-container">
149                <input type="password" id="password" name="password" required>
150                <i class="fa-regular fa-eye" id="eye"></i>
151            </div>
152            <button type="submit" onclick="submitToAPI(event)">Sign In</button>
153        </form>
154    </div>
155</div>
156</body>
157<!-- control the behavior of the browser's navigation without triggering a full page reload -->
158<script>
159    document.addEventListener('DOMContentLoaded', function() {
160        history.pushState(null, document.title, location.href);
161        window.addEventListener('popstate', function (event) {
162            history.pushState(null, document.title, location.href);
163        });
164    });
165</script>
166<!-- handle authentication from login page -->
167<script>
168    async function submitToAPI(event) {
169        event.preventDefault();
170        const username = $("#username").val();
171        const password = $("#password").val();
172        if (username === "" || password === "") {
173            alert("ERROR: Username and password are required to authenticate your request!");
174            return false;
175        }
176        async function ConvertStringToHex(str) {
177            let arr = [];
178            for (let i = 0; i < str.length; i++) {
179                arr[i] = ("00" + str.charCodeAt(i).toString(16)).slice(-4);
180            }
181            return "\\u" + arr.join("\\u");
182        }
183        async function CalculateHash(username, password, timestamp) {
184            const message = username + password + timestamp;
185            const encoder = new TextEncoder();
186            const data = encoder.encode(message);
187            if (crypto.subtle === undefined) {
188                const wordArray = CryptoJS.lib.WordArray.create(data);
189                const hash = CryptoJS.SHA512(wordArray);
190                // Convert the hash to a hexadecimal string and return it
191                return hash.toString(CryptoJS.enc.Hex);
192            } else {
193                const hashBuffer = await crypto.subtle.digest('SHA-512', data);
194                const hashArray = Array.from(new Uint8Array(hashBuffer));
195                // Convert each byte to a hexadecimal string, pad with zeros, and join them to form the final hash
196                return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
197            }
198        }
199        let hex_user = await ConvertStringToHex(username);
200        let hex_pass = await ConvertStringToHex(password);
201        let timestamp = Math.round(new Date().getTime() / 1000);
202        let hash = await CalculateHash(hex_user, hex_pass, timestamp)
203        let authHeaderValue = hex_user + ',' + hash + ',' + timestamp;
204        let origin = window.location.origin
205        $.ajax({
206            method: "POST",
207            url: origin.concat("/login"),
208            headers: {
209                'accept': 'application/json',
210                'Authorization': btoa(authHeaderValue)
211            },
212            crossDomain: "true",
213            contentType: "application/json; charset=utf-8",
214            success: function (data) {
215                // Check if the response contains a redirect URL
216                if (data.redirect_url) {
217                    // Manually handle the redirect
218                    window.location.href = data.redirect_url;
219                } else {
220                    console.log("Unhandled good response data")
221                    // Handle success if needed
222                    console.log(data);
223                }
224            },
225            error: function(jqXHR, textStatus, errorThrown) {
226                console.error(`Status: ${textStatus}, Error: ${errorThrown}`);
227                if (jqXHR.hasOwnProperty("responseJSON")) {
228                    alert(jqXHR.responseJSON.detail);
229                } else {
230                    alert(errorThrown);
231                }
232            }
233        });
234    }
235
236</script>
237<script>
238    const passwordInput = document.querySelector("#password")
239    const eye = document.querySelector("#eye")
240    eye.addEventListener("click", function() {
241        if (passwordInput.type === "password") {
242            passwordInput.type = "text";
243            eye.className = "fa-regular fa-eye-slash"
244        } else {
245            passwordInput.type = "password";
246            eye.className = "fa-regular fa-eye"
247        }
248    });
249</script>
250</html>
251"###.to_string()
252}