Add save/load configurations (#25)
This commit is contained in:
		
							
								
								
									
										9
									
								
								.github/workflows/generator-android.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/generator-android.yml
									
									
									
									
										vendored
									
									
								
							| @@ -365,6 +365,15 @@ jobs: | ||||
|           sed -i -e "s|launchUrlString('https://rustdesk.com/privacy.html')|launchUrlString('${{ fromJson(inputs.extras).urlLink }}/privacy.html')|" ./flutter/lib/mobile/pages/settings_page.dart | ||||
|           sed -i -e "s|https://rustdesk.com/privacy.html|${{ fromJson(inputs.extras).urlLink }}/privacy.html|" ./flutter/lib/desktop/pages/install_page.dart | ||||
|  | ||||
|       - name: change download link to custom | ||||
|         if: fromJson(inputs.extras).downloadLink != 'https://rustdesk.com/download' | ||||
|         continue-on-error: true | ||||
|         shell: bash | ||||
|         run: | | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/desktop/pages/desktop_home_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/mobile/pages/connection_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./src/ui/index.tis | ||||
|  | ||||
|       - name: allow custom.txt | ||||
|         continue-on-error: true | ||||
|         shell: bash | ||||
|   | ||||
							
								
								
									
										9
									
								
								.github/workflows/generator-linux.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/generator-linux.yml
									
									
									
									
										vendored
									
									
								
							| @@ -311,6 +311,15 @@ jobs: | ||||
|           sed -i -e "s|launchUrlString('https://rustdesk.com/privacy.html')|launchUrlString('${{ fromJson(inputs.extras).urlLink }}/privacy.html')|" ./flutter/lib/mobile/pages/settings_page.dart | ||||
|           sed -i -e "s|https://rustdesk.com/privacy.html|${{ fromJson(inputs.extras).urlLink }}/privacy.html|" ./flutter/lib/desktop/pages/install_page.dart | ||||
|  | ||||
|       - name: change download link to custom | ||||
|         if: fromJson(inputs.extras).downloadLink != 'https://rustdesk.com/download' | ||||
|         continue-on-error: true | ||||
|         shell: bash | ||||
|         run: | | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/desktop/pages/desktop_home_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/mobile/pages/connection_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./src/ui/index.tis | ||||
|  | ||||
|       - name: fix connection delay | ||||
|         continue-on-error: true | ||||
|         if: ${{ fromJson(inputs.extras).delayFix == 'true' }} | ||||
|   | ||||
							
								
								
									
										13
									
								
								.github/workflows/generator-windows.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								.github/workflows/generator-windows.yml
									
									
									
									
										vendored
									
									
								
							| @@ -210,10 +210,22 @@ jobs: | ||||
|           sed -i -e "s|launchUrlString('https://rustdesk.com/privacy.html')|launchUrlString('${{ fromJson(inputs.extras).urlLink }}/privacy.html')|" ./flutter/lib/mobile/pages/settings_page.dart | ||||
|           sed -i -e "s|https://rustdesk.com/privacy.html|${{ fromJson(inputs.extras).urlLink }}/privacy.html|" ./flutter/lib/desktop/pages/install_page.dart | ||||
|  | ||||
|       - name: change download link to custom | ||||
|         if: fromJson(inputs.extras).downloadLink != 'https://rustdesk.com/download' | ||||
|         continue-on-error: true | ||||
|         shell: bash | ||||
|         run: | | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/desktop/pages/desktop_home_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./flutter/lib/mobile/pages/connection_page.dart | ||||
|           sed -i -e 's|https://rustdesk.com/download|${{ fromJson(inputs.extras).downloadLink }}|' ./src/ui/index.tis | ||||
|  | ||||
|       - name: allow custom.txt | ||||
|         continue-on-error: true | ||||
|         shell: bash | ||||
|         run: | | ||||
|           sed -i -e 's|rs-ny.rustdesk.com|${{ env.RENDEZVOUS_SERVER }}|' ./libs/hbb_common/src/config.rs | ||||
|           sed -i -e 's|OeVuKk5nlHiXp+APNn0Y3pC1Iwpwn44JGqrQCsWqmBw=|${{ env.RS_PUB_KEY }}|' ./libs/hbb_common/src/config.rs | ||||
|           sed -i -e 's|For faster connection, please set up your own server||' ./src/lang/en.rs | ||||
|           sed -i -e '/const KEY:/,/};/d' ./src/common.rs | ||||
|           sed -i -e '/let Ok(data) = sign::verify(&data, &pk)/,/};/d' ./src/common.rs | ||||
|           # ./flutter/pubspec.yaml | ||||
| @@ -241,7 +253,6 @@ jobs: | ||||
|  | ||||
|       # https://github.com/flutter/flutter/issues/155685 | ||||
|       - name: Replace engine with rustdesk custom flutter engine | ||||
|         if: false | ||||
|         run: | | ||||
|           flutter doctor -v | ||||
|           flutter precache --windows | ||||
|   | ||||
| @@ -28,10 +28,13 @@ class GenerateForm(forms.Form): | ||||
|     apiServer = forms.CharField(label="API Server", required=False) | ||||
|     key = forms.CharField(label="Key", required=False) | ||||
|     urlLink = forms.CharField(label="Custom URL for links", required=False) | ||||
|     downloadLink = forms.CharField(label="Custom URL for downloading new versions", required=False) | ||||
|  | ||||
|     #Visual | ||||
|     iconfile = forms.FileField(label="Custom App Icon (in .png format)", required=False, widget=forms.FileInput(attrs={'accept': 'image/png'})) | ||||
|     logofile = forms.FileField(label="Custom App Logo (in .png format)", required=False, widget=forms.FileInput(attrs={'accept': 'image/png'})) | ||||
|     iconbase64 = forms.CharField(required=False) | ||||
|     logobase64 = forms.CharField(required=False) | ||||
|     theme = forms.ChoiceField(choices=[ | ||||
|         ('light', 'Light'), | ||||
|         ('dark', 'Dark'), | ||||
|   | ||||
| @@ -106,11 +106,37 @@ | ||||
|             max-height: 100px; | ||||
|             margin-top: 10px; | ||||
|         } | ||||
|         .save-load-section-container { /* New container for fixed positioning */ | ||||
|             position: fixed; | ||||
|             top: 20px; /* Adjust as needed */ | ||||
|             left: 20px; /* Adjust as needed */ | ||||
|             background-color: #111; /* Match your section background */ | ||||
|             padding: 0px; | ||||
|             border-radius: 8px; | ||||
|             box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||||
|             z-index: 100; /* Ensure it's above other content */ | ||||
|         } | ||||
|         .save-load-section { | ||||
|             display: none; /* Initially hidden */ | ||||
|         } | ||||
|         .error { | ||||
|             color: red; | ||||
|         } | ||||
|       </style> | ||||
| </head> | ||||
| <body> | ||||
|     <h1><i class="fas fa-cogs"></i> RustDesk Custom Client Builder</h1> | ||||
|     <form action="/generator" method="post" enctype="multipart/form-data"> | ||||
|     <form id="myForm" action="/generator" method="post" enctype="multipart/form-data"> | ||||
|         <div class="save-load-section-container"> | ||||
|             <div class="section"> | ||||
|                 <h2 id="saveLoadTitle">Save/Load Configuration <i class="fas fa-chevron-down"></i></h2>   | ||||
|                 <div class="save-load-section">   | ||||
|                     <button type="button" onclick="saveFormData()">Save Configuration</button> | ||||
|                     <input type="file" id="fileInput" style="display:none;" accept=".json"> | ||||
|                     <button type="button" onclick="loadFormData()">Load Configuration</button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="platform"> | ||||
|             <h2><i class="fas fa-desktop"></i> Select Platform</h2> | ||||
|                 <div class="platform-icons"> | ||||
| @@ -134,7 +160,7 @@ | ||||
|             <div class="section"> | ||||
|                 <h2><i class="fas fa-sliders-h"></i> General</h2> | ||||
|                     <label for="{{ form.exename.id_for_label }}">Name of the configuration (no spaces or special characters, English characters only):</label> | ||||
|                     {{ form.exename }}<br><br> | ||||
|                     {{ form.exename }}<span id="filenameError" class="error"></span><br><br> | ||||
|                     <label for="{{ form.appname.id_for_label }}">Custom Application Name (leave blank to use default):</label> | ||||
|                     {{ form.appname }}<br><br> | ||||
|                     <label for="{{ form.direction.id_for_label }}">Connection Type:</label> | ||||
| @@ -155,6 +181,8 @@ | ||||
|                     {{ form.apiServer }}<br><br> | ||||
|                     <label for="{{ form.urlLink.id_for_label }}">Custom URL for links (replaces https://rustdesk.com):</label> | ||||
|                     {{ form.urlLink }}<br><br> | ||||
|                     <label for="{{ form.downloadLink.id_for_label }}">Custom URL for downloading updates (replaces https://rustdesk.com/download):</label> | ||||
|                     {{ form.downloadLink }}<br><br> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="container"> | ||||
| @@ -177,6 +205,8 @@ | ||||
|          | ||||
|             <div class="section"> | ||||
|                 <h2><i class="fas fa-paint-brush"></i> Visual</h2> | ||||
|                     {{ form.iconbase64.as_hidden }} | ||||
|                     {{ form.logobase64.as_hidden }} | ||||
|                     <label for="{{ form.iconfile.id_for_label }}">Custom App Icon (in .png format)</label> | ||||
|                     {{ form.iconfile }}<br><br> | ||||
|                     <!-- <input type="file" name="iconfile" id="iconfile" accept="image/png"> --> | ||||
| @@ -260,6 +290,142 @@ | ||||
|                 reader.readAsDataURL(input.files[0]); | ||||
|             } | ||||
|         } | ||||
|         const saveLoadTitle = document.getElementById("saveLoadTitle"); | ||||
|         const saveLoadSection = document.querySelector(".save-load-section"); | ||||
|  | ||||
|         saveLoadTitle.addEventListener("click", () => { | ||||
|             if (!saveLoadSection.style.display || saveLoadSection.style.display === "none") { | ||||
|                 saveLoadSection.style.display = "block"; | ||||
|             } else { | ||||
|                 saveLoadSection.style.display = "none"; | ||||
|             } | ||||
|  | ||||
|             const icon = saveLoadTitle.querySelector("i"); | ||||
|             icon.classList.toggle("fa-chevron-down"); | ||||
|             icon.classList.toggle("fa-chevron-up"); | ||||
|         }); | ||||
|  | ||||
|         async function saveFormData() { | ||||
|             const filename = document.getElementById("{{ form.exename.id_for_label }}").value; | ||||
|             if (!filename) { | ||||
|                 document.getElementById("filenameError").textContent = "Filename is required."; | ||||
|                 return; | ||||
|             } else { | ||||
|                 document.getElementById("filenameError").textContent = ""; | ||||
|             } | ||||
|  | ||||
|             const formData = {}; | ||||
|             const form = document.getElementById("myForm"); // Get the form element | ||||
|  | ||||
|             // 1. Use FormData for robust data collection (handles most input types): | ||||
|             const formDataObj = new FormData(form);  // Create a FormData object | ||||
|  | ||||
|             formDataObj.forEach((value, key) => { | ||||
|                 formData[key] = value; // Add each key/value pair to the formData object. | ||||
|             }); | ||||
|  | ||||
|             // 2. Handle select elements separately (more reliable): | ||||
|             const selectElements = form.querySelectorAll('select'); | ||||
|             selectElements.forEach(select => { | ||||
|                 formData[select.name] = select.value; | ||||
|             }); | ||||
|  | ||||
|             // 3. Handle checkboxes and radio buttons (important!): | ||||
|             const checkboxRadioElements = form.querySelectorAll('input[type="checkbox"], input[type="radio"]'); | ||||
|             checkboxRadioElements.forEach(input => { | ||||
|                 if (input.name) { //only add to form data if it has a name | ||||
|                     if (input.checked) { | ||||
|                         formData[input.name] = input.value; | ||||
|                     } else if (!formData.hasOwnProperty(input.name) && input.type === 'checkbox') { //if it's a checkbox and it's not checked, add it as false | ||||
|                         formData[input.name] = false; | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             // 4. Handle icon and logo | ||||
|             const iconPreview = document.getElementById('icon-preview'); | ||||
|             if (iconPreview.firstChild && iconPreview.firstChild.src.startsWith('data:image/png;base64')) { | ||||
|                 formData.iconfile = iconPreview.firstChild.src;  // Use existing base64 | ||||
|             } else { //if it's a file upload | ||||
|                 const iconFile = document.getElementById("{{ form.iconfile.id_for_label }}").files[0]; | ||||
|                 if (iconFile) { | ||||
|                     formData.iconfile = await readFileAsBase64(iconFile); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             const logoPreview = document.getElementById('logo-preview'); | ||||
|             if (logoPreview.firstChild && logoPreview.firstChild.src.startsWith('data:image/png;base64')) { | ||||
|                 formData.logofile = logoPreview.firstChild.src; // Use existing base64 | ||||
|             } else { //if it's a file upload | ||||
|                 const logoFile = document.getElementById("{{ form.logofile.id_for_label }}").files[0]; | ||||
|                 if (logoFile) { | ||||
|                     formData.logofile = await readFileAsBase64(logoFile); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             const jsonData = JSON.stringify(formData, null, 2); | ||||
|             const blob = new Blob([jsonData], { type: "application/json" }); | ||||
|             const url = URL.createObjectURL(blob); | ||||
|  | ||||
|             const a = document.createElement("a"); | ||||
|             a.href = url; | ||||
|             a.download = filename + ".json"; | ||||
|             document.body.appendChild(a); | ||||
|             a.click(); | ||||
|             document.body.removeChild(a); | ||||
|             URL.revokeObjectURL(url); | ||||
|         } | ||||
|  | ||||
|         async function readFileAsBase64(file) { | ||||
|             return new Promise((resolve, reject) => { | ||||
|                 const reader = new FileReader(); | ||||
|                 reader.onload = (event) => resolve(event.target.result); | ||||
|                 reader.onerror = (error) => reject(error); | ||||
|                 reader.readAsDataURL(file); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         function loadFormData() { | ||||
|             const fileInput = document.getElementById("fileInput"); | ||||
|             fileInput.click(); | ||||
|  | ||||
|             fileInput.addEventListener("change", (event) => { | ||||
|                 const file = event.target.files[0]; | ||||
|                 if (file) { | ||||
|                     const reader = new FileReader(); | ||||
|                     reader.onload = (e) => { | ||||
|                         try { | ||||
|                             const formData = JSON.parse(e.target.result); | ||||
|                             for (const key in formData) { | ||||
|                                 const element = document.querySelector(`[name="${key}"]`); | ||||
|  | ||||
|                                 if (element) { // Check if the element exists | ||||
|                                     if (element.type === 'checkbox' || element.type === 'radio') { | ||||
|                                         element.checked = formData[key]; // Set checked property | ||||
|                                     } else if (element.type !== 'file') { //for other elements | ||||
|                                         element.value = formData[key]; | ||||
|                                     } | ||||
|  | ||||
|                                     // Handle image previews (as before) | ||||
|                                     if (key === 'iconfile' && formData[key]) { | ||||
|                                         document.getElementById('id_iconbase64').value = formData[key] | ||||
|                                         document.getElementById('icon-preview').innerHTML = `<img src="${formData[key]}" style="max-width: 300px; max-height: 60px;">`; | ||||
|                                     } | ||||
|                                     if (key === 'logofile' && formData[key]) { | ||||
|                                         document.getElementById('id_logobase64').value = formData[key] | ||||
|                                         document.getElementById('logo-preview').innerHTML = `<img src="${formData[key]}" style="max-width: 300px; max-height: 60px;">`; | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } catch (error) { | ||||
|                             alert("Error loading data. Invalid JSON file."); | ||||
|                         } | ||||
|                     }; | ||||
|                     reader.readAsText(file); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     </script> | ||||
| </body> | ||||
| </html> | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user