Skip to content

Commit 72eb5cb

Browse files
feat(settings): add reorder functionality for search engines, API keys, and proxied domains
1 parent e427083 commit 72eb5cb

3 files changed

Lines changed: 105 additions & 15 deletions

File tree

app.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,31 @@ def update_settings():
222222
return {"error": str(e)}, 500
223223

224224

225+
@app.route("/reorder_settings", methods=["POST"])
226+
@login_required
227+
def reorder_settings():
228+
"""Reorder search engines, API keys or proxied domains."""
229+
try:
230+
_type = request.form.get("type")
231+
_order = request.form.get("order")
232+
if not (_type and _order):
233+
return {"error": "Missing required parameters"}, 400
234+
match _type:
235+
case "engine":
236+
reorder_env_file(ENG_PATH, search_engines, json.loads(_order))
237+
case "api":
238+
reorder_env_file(API_PATH, api_keys, json.loads(_order))
239+
case "proxy":
240+
reorder_proxy_file(json.loads(_order))
241+
signal_workers() # [NOTE]: works only in unix-based systems
242+
return {"success": True}
243+
except json.JSONDecodeError:
244+
return {"error": "Invalid JSON format"}, 400
245+
except Exception as e:
246+
app.logger.error(f"\n\n[ERROR]: reordering settings -> {e}\n----------\n")
247+
return {"error": str(e)}, 500
248+
249+
225250
if __name__ == "__main__":
226251
"""Main entry point for the Flask application on development server (for testing purposes)."""
227252
app.run()

templates/index.html

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ <h3>Edit Settings</h3>
287287
<button id="editDelete" class="btn btn-danger w-100 mb-2" onclick="handleDelete()" disabled>Delete</button>
288288
<button id="editClear" class="btn btn-secondary w-100" onclick="ClearEditFields()" disabled>Clear</button>
289289
</div>
290+
<div class="d-flex gap-1">
291+
<button id="editMoveUp" class="btn btn-outline-secondary flex-fill" onclick="handleMoveUp()" title="Move Up" disabled><i class="fas fa-arrow-up"></i></button>
292+
<button id="editMoveDown" class="btn btn-outline-secondary flex-fill" onclick="handleMoveDown()" title="Move Down" disabled><i class="fas fa-arrow-down"></i></button>
293+
</div>
290294
<div>
291295
<button id="saveEditButton" class="btn btn-success" onclick="saveEditSettings()" disabled>Save</button>
292296
</div>
@@ -357,6 +361,7 @@ <h2 class="text-center">Websites Preview</h2>
357361
let settings = {},
358362
currentEditTab = 'engine',
359363
selectedItem = null,
364+
pendingReorder = false,
360365
pendingChanges = { add: [], upd: [], del: [] };
361366

362367
// [DEBUG]: reference variables for debugging
@@ -406,7 +411,9 @@ <h2 class="text-center">Websites Preview</h2>
406411
editList = document.getElementById('editList'),
407412
editAction = document.getElementById('editAction'),
408413
editDelete = document.getElementById('editDelete'),
409-
editClear = document.getElementById('editClear');
414+
editClear = document.getElementById('editClear'),
415+
editMoveUp = document.getElementById('editMoveUp'),
416+
editMoveDown = document.getElementById('editMoveDown');
410417

411418
// Create simple select options
412419
function populateOptions(elem, items, selected = null) {
@@ -801,6 +808,7 @@ <h2 class="text-center">Websites Preview</h2>
801808
}
802809

803810
function handleItemSelect() {
811+
if (pendingReorder) { updateMoveButtons(); return; } // reorder in progress — update boundary buttons, ignore CRUD
804812
editName.value = selectedItem = editList.value;
805813
editDelete.disabled = editClear.disabled = false;
806814
editAction.textContent = 'Update';
@@ -809,6 +817,7 @@ <h2 class="text-center">Websites Preview</h2>
809817
editValue.value = pendingChanges.add.find(i => i.name === selectedItem)?.value ||
810818
pendingChanges.upd.find(i => i.name === selectedItem)?.value ||
811819
'';
820+
updateMoveButtons();
812821
}
813822

814823
function handleAction() { // handles Add/Update actions only
@@ -868,12 +877,22 @@ <h2 class="text-center">Websites Preview</h2>
868877
function __postActionReset() { // common reset tasks after Add/Update/Delete
869878
ClearEditFields();
870879
saveEditBtn.disabled = !(pendingChanges.add.length || pendingChanges.upd.length || pendingChanges.del.length);
880+
updateMoveButtons(); // move buttons only enabled when no CRUD changes pending
881+
}
882+
883+
function __postMoveReset() { // common reset tasks after Move Up/Down
884+
pendingReorder = true;
885+
saveEditBtn.disabled = false;
886+
updateMoveButtons();
887+
lockCRUD();
871888
}
872889

873890
function resetEditForm() {
891+
unlockCRUD();
874892
ClearEditFields();
875893
saveEditBtn.disabled = true;
876894
pendingChanges = { add: [], upd: [], del: [] }; // clear pending changes (for current tab)
895+
pendingReorder = false;
877896
}
878897

879898
function ClearEditFields(){
@@ -887,32 +906,65 @@ <h2 class="text-center">Websites Preview</h2>
887906
editList.value = '';
888907
editDelete.disabled = true;
889908
editAction.textContent = 'Add';
909+
editMoveUp.disabled = editMoveDown.disabled = true;
910+
}
911+
912+
function updateMoveButtons() { // enable based on selection; CRUD and reorder are mutually exclusive
913+
const idx = editList.selectedIndex,
914+
noSelection = !selectedItem || idx === -1;
915+
editMoveUp.disabled = noSelection || idx === 0;
916+
editMoveDown.disabled = noSelection || idx === editList.options.length - 1;
917+
}
918+
919+
function lockCRUD() { // disable all CRUD controls while a reorder is pending
920+
editAction.disabled = editDelete.disabled = editClear.disabled = true;
921+
editName.disabled = editValue.disabled = true;
922+
}
923+
924+
function unlockCRUD() { // re-enable inputs after reorder is cleared (resetEditForm handles button states)
925+
editName.disabled = editValue.disabled = false;
890926
}
891927

892-
function saveEditSettings() { // save all pending changes
928+
function handleMoveUp() {
929+
const idx = editList.selectedIndex;
930+
if (idx <= 0) return;
931+
editList.insertBefore(editList.options[idx], editList.options[idx - 1]);
932+
editList.selectedIndex = idx - 1;
933+
__postMoveReset();
934+
}
935+
936+
function handleMoveDown() {
937+
const idx = editList.selectedIndex;
938+
if (idx === -1 || idx >= editList.options.length - 1) return;
939+
editList.insertBefore(editList.options[idx + 1], editList.options[idx]);
940+
editList.selectedIndex = idx + 1;
941+
__postMoveReset();
942+
}
943+
944+
function dispatchSave(endpoint, payloadKey, payloadVal, label, deletedKeys) { // handles the actual fetch — no branching, pure execution
893945
const formData = new FormData();
894946
formData.append('type', currentEditTab);
895-
formData.append('changes', JSON.stringify(pendingChanges));
896-
fetch('/update_settings', {
897-
method: 'POST',
898-
body: formData,
899-
headers: {'Cache-Control': 'no-cache, no-store, must-revalidate'} // prevent caching sensitive data
900-
})
947+
formData.append(payloadKey, JSON.stringify(payloadVal));
948+
fetch(endpoint, { method: 'POST', body: formData, headers: {'Cache-Control': 'no-cache, no-store, must-revalidate'} })
901949
.then(response => response.json())
902950
.then(data => {
903951
if (data.error) { alert(data.error); return; }
904-
if (currentEditTab !== 'proxy') { // handle localStorage cleanup for deleted items
952+
if (deletedKeys.length) { // handle localStorage cleanup for deleted items
905953
const key = currentEditTab === 'api' ? 'apiKey' : 'searchEngine', value = localStorage.getItem(key);
906-
if(value && pendingChanges.del.includes(value)) localStorage.removeItem(key);
954+
if (value && deletedKeys.includes(value)) localStorage.removeItem(key);
907955
}
908956
loadSettingsOptions(); // reload settings options for the main modal
909957
// closeEditSettingsModal(); // [NOTE]: not needed, user can continue editing & close manually
910-
alert('Changes saved successfully!'); // [NOTE]: can be replaced with a non-intrusive toast notification (like floating timeout msg in top middle)
958+
alert(`${label} saved successfully!`); // [NOTE]: can be replaced with a non-intrusive toast notification
911959
})
912-
.catch(error => {
913-
console.error('Error saving changes:', error);
914-
alert('Failed to save changes. Please try again.');
915-
});
960+
.catch(error => { console.error('Error saving settings:', error); alert('Failed to save. Please try again.'); });
961+
}
962+
963+
function saveEditSettings() { // decides what to save, delegates execution to dispatchSave
964+
pendingReorder
965+
? dispatchSave('/reorder_settings', 'order', Array.from(editList.options, o => o.value), 'Order', [])
966+
: dispatchSave('/update_settings', 'changes', pendingChanges, 'Changes',
967+
currentEditTab !== 'proxy' ? [...pendingChanges.del] : []); // snapshot del before resetEditForm clears it
916968
resetEditForm();
917969
}
918970

utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,16 @@ def update_proxy_file(domains, changes):
8383
with open(DOM_PATH, 'w') as f:
8484
for domain in domains:
8585
f.write(f"{domain}\n")
86+
87+
def reorder_env_file(file_path, env_data, order):
88+
"""Rewrite a .env file with keys in the given order (reorder only, no add/delete)."""
89+
with open(file_path, 'w') as f:
90+
for k in order:
91+
if k in env_data: # guard against stale keys
92+
f.write(f"'{k}'='{env_data[k]}'\n")
93+
94+
def reorder_proxy_file(order):
95+
"""Rewrite the proxied domains file in the given order."""
96+
with open(DOM_PATH, 'w') as f:
97+
for domain in order:
98+
f.write(f"{domain}\n")

0 commit comments

Comments
 (0)