add save/load configuration
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,6 +28,7 @@ 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'}))
|
||||
|
@@ -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">
|
||||
@@ -260,6 +288,140 @@
|
||||
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('icon-preview').innerHTML = `<img src="${formData[key]}" style="max-width: 300px; max-height: 60px;">`;
|
||||
}
|
||||
if (key === 'logofile' && 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>
|
@@ -32,6 +32,7 @@ def generator_view(request):
|
||||
key = form.cleaned_data['key']
|
||||
apiServer = form.cleaned_data['apiServer']
|
||||
urlLink = form.cleaned_data['urlLink']
|
||||
downloadLink = form.cleaned_data['downloadLink']
|
||||
if not server:
|
||||
server = 'rs-ny.rustdesk.com' #default rustdesk server
|
||||
if not key:
|
||||
@@ -40,6 +41,8 @@ def generator_view(request):
|
||||
apiServer = server+":21114"
|
||||
if not urlLink:
|
||||
urlLink = "https://rustdesk.com"
|
||||
if not downloadLink:
|
||||
downloadLink = "https://rustdesk.com/download"
|
||||
direction = form.cleaned_data['direction']
|
||||
installation = form.cleaned_data['installation']
|
||||
settings = form.cleaned_data['settings']
|
||||
@@ -158,6 +161,7 @@ def generator_view(request):
|
||||
extras['genurl'] = _settings.GENURL
|
||||
extras['runasadmin'] = runasadmin
|
||||
extras['urlLink'] = urlLink
|
||||
extras['downloadLink'] = downloadLink
|
||||
extras['delayFix'] = 'true' if delayFix else 'false'
|
||||
extras['version'] = version
|
||||
extras['rdgen'] = 'true'
|
||||
@@ -365,6 +369,19 @@ def startgh(request):
|
||||
def save_png(file, uuid, domain, name):
|
||||
file_save_path = "png/%s/%s" % (uuid, name)
|
||||
Path("png/%s" % uuid).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if isinstance(file, str): # Check if it's a base64 string
|
||||
try:
|
||||
header, encoded = file.split(';base64,')
|
||||
decoded_img = base64.b64decode(encoded)
|
||||
file = ContentFile(decoded_img, name=name) # Create a file-like object
|
||||
except ValueError:
|
||||
print("Invalid base64 data")
|
||||
return None # Or handle the error as you see fit
|
||||
except Exception as e: # Catch general exceptions during decoding
|
||||
print(f"Error decoding base64: {e}")
|
||||
return None
|
||||
|
||||
with open(file_save_path, "wb+") as f:
|
||||
for chunk in file.chunks():
|
||||
f.write(chunk)
|
||||
|
Reference in New Issue
Block a user