rustream/templates/
index.rs1pub 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}