Dispatcharr/templates/settings.html
Dispatcharr d89bf35c0d Pre-Alpha v4
Added settings page
Added EPG functionality
2025-02-24 15:04:03 -06:00

786 lines
29 KiB
HTML
Executable file

{% extends "base.html" %}
{% block title %}Settings - Dispatcharr{% endblock %}
{% block page_header %}Settings{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="/core/dashboard/">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Settings</li>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Settings</h3>
</div>
<div class="card-body">
<!-- Nav Tabs -->
<ul class="nav nav-tabs" id="settingsTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="stream-profiles-tab" data-bs-toggle="tab" href="#stream-profiles"
role="tab" aria-controls="stream-profiles" aria-selected="true">
Stream Profiles
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="user-agents-tab" data-bs-toggle="tab" href="#user-agents" role="tab"
aria-controls="user-agents" aria-selected="false">
User Agents
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="logo-caching-tab" data-bs-toggle="tab" href="#logo-caching" role="tab"
aria-controls="logo-caching" aria-selected="false">
Logo Caching
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="backup-restore-tab" data-bs-toggle="tab" href="#backup-restore" role="tab"
aria-controls="backup-restore" aria-selected="false">
Backup/Restore
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="hdhr-tab" data-bs-toggle="tab" href="#hdhr" role="tab"
aria-controls="hdhr" aria-selected="false">
HDHR Emulation
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="user-management-tab" data-bs-toggle="tab" href="#user-management"
role="tab" aria-controls="user-management" aria-selected="false">
Users &amp; Groups
</a>
</li>
</ul>
<!-- Tab Content -->
<div class="tab-content mt-3" id="settingsTabContent">
<!-- STREAM PROFILES -->
<div class="tab-pane fade show active" id="stream-profiles" role="tabpanel"
aria-labelledby="stream-profiles-tab">
<button id="addStreamProfileBtn" class="btn btn-primary mb-2">
<i class="bi bi-plus"></i> Add Stream Profile
</button>
<div class="table-responsive">
<table id="streamProfilesTable" class="table table-striped nowrap">
<thead>
<tr>
<th>ID</th>
<th>Profile Name</th>
<th>Command</th>
<th>Parameters</th>
<th>Active</th>
<th>User Agent</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- USER AGENTS -->
<div class="tab-pane fade" id="user-agents" role="tabpanel" aria-labelledby="user-agents-tab">
<button id="addUserAgentBtn" class="btn btn-primary mb-2">
<i class="bi bi-plus"></i> Add User Agent
</button>
<div class="table-responsive">
<table id="userAgentsTable" class="table table-striped nowrap">
<thead>
<tr>
<th>ID</th>
<th>User Agent Name</th>
<th>User Agent</th>
<th>Description</th>
<th>Active</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- LOGO CACHING -->
<div class="tab-pane fade" id="logo-caching" role="tabpanel" aria-labelledby="logo-caching-tab">
<form id="logoCachingForm" class="mt-3">
<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="cacheLogosToggle" name="cache_logos">
<label class="form-check-label" for="cacheLogosToggle">
Cache Channel Logos Locally
</label>
</div>
<button type="submit" class="btn btn-primary">Save Logo Caching Settings</button>
</form>
</div>
<!-- BACKUP / RESTORE -->
<div class="tab-pane fade" id="backup-restore" role="tabpanel" aria-labelledby="backup-restore-tab">
<div class="mt-3">
<button id="createBackupBtn" class="btn btn-success mb-2">
<i class="bi bi-download"></i> Create Backup
</button>
<button id="restoreBackupBtn" class="btn btn-warning mb-2">
<i class="bi bi-upload"></i> Restore Backup
</button>
</div>
</div>
<!-- HDHR EMULATION -->
<div class="tab-pane fade" id="hdhr" role="tabpanel" aria-labelledby="hdhr-tab">
<form id="hdhrEmulationForm" class="mt-3">
<div class="form-check form-switch mb-3">
<input type="checkbox" class="form-check-input" id="hdhrEmulationToggle" name="hdhr_emulation">
<label class="form-check-label" for="hdhrEmulationToggle">Enable HDHR Emulation</label>
</div>
<button type="submit" class="btn btn-primary">Save HDHR Settings</button>
</form>
</div>
<!-- USERS & GROUPS -->
<div class="tab-pane fade" id="user-management" role="tabpanel" aria-labelledby="user-management-tab">
<div class="row mt-3">
<!-- USERS -->
<div class="col-md-6">
<h5>Users</h5>
<button id="addUserBtn" class="btn btn-primary mb-2">
<i class="bi bi-plus"></i> Add User
</button>
<div class="table-responsive">
<table id="usersTable" class="table table-striped nowrap">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Groups</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- GROUPS -->
<div class="col-md-6">
<h5>Groups</h5>
<button id="addGroupBtn" class="btn btn-primary mb-2">
<i class="bi bi-plus"></i> Add Group
</button>
<div class="table-responsive">
<table id="groupsTable" class="table table-striped nowrap">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Permissions</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div><!-- end row -->
</div><!-- end user-management tab content -->
</div><!-- end tab-content -->
</div><!-- end card-body -->
</div><!-- end card -->
<!-- ============== MODALS ============== -->
<!-- STREAM PROFILE MODAL -->
<div class="modal fade" id="streamProfileModal" tabindex="-1" aria-labelledby="streamProfileModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="streamProfileForm" enctype="multipart/form-data">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="streamProfileModalLabel">Stream Profile</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" id="streamProfileId" name="id">
<div class="mb-3">
<label for="profileName" class="form-label">Profile Name</label>
<input type="text" class="form-control" id="profileName" name="profile_name" required>
</div>
<div class="mb-3">
<label for="command" class="form-label">Command</label>
<input type="text" class="form-control" id="command" name="command" required>
</div>
<div class="mb-3">
<label for="parameters" class="form-label">Parameters</label>
<textarea class="form-control" id="parameters" name="parameters" required></textarea>
</div>
<div class="mb-3">
<label for="profileActive" class="form-label">Active</label>
<select class="form-select" id="profileActive" name="is_active">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
<div class="mb-3">
<label for="profileUserAgent" class="form-label">User Agent (optional)</label>
<input type="text" class="form-control" id="profileUserAgent" name="user_agent">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Save Profile</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</form>
</div>
</div>
<!-- USER AGENT MODAL -->
<div class="modal fade" id="userAgentModal" tabindex="-1" aria-labelledby="userAgentModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="userAgentForm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userAgentModalLabel">Add User Agent</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="userAgentId" name="id">
<div class="mb-3">
<label for="userAgentNameField" class="form-label">User Agent Name</label>
<input type="text" class="form-control" id="userAgentNameField" name="user_agent_name" required>
</div>
<div class="mb-3">
<label for="userAgentStringField" class="form-label">User Agent String</label>
<input type="text" class="form-control" id="userAgentStringField" name="user_agent" required>
</div>
<div class="mb-3">
<label for="userAgentDescription" class="form-label">Description</label>
<input type="text" class="form-control" id="userAgentDescription" name="description">
</div>
<div class="mb-3">
<label for="userAgentActive" class="form-label">Active</label>
<select class="form-select" id="userAgentActive" name="is_active">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Save User Agent</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</form>
</div>
</div>
<!-- USER MODAL -->
<div class="modal fade" id="userModal" tabindex="-1" aria-labelledby="userModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="userForm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userModalLabel">Add User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="userId" name="id">
<div class="mb-3">
<label for="usernameField" class="form-label">Username</label>
<input type="text" class="form-control" id="usernameField" name="username" required>
</div>
<div class="mb-3">
<label for="emailField" class="form-label">Email</label>
<input type="email" class="form-control" id="emailField" name="email">
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Save User</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</form>
</div>
</div>
<!-- GROUP MODAL -->
<div class="modal fade" id="groupModal" tabindex="-1" aria-labelledby="groupModalLabel" aria-hidden="true">
<div class="modal-dialog">
<form id="groupForm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="groupModalLabel">Add Group</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="groupId" name="id">
<div class="mb-3">
<label for="groupNameField" class="form-label">Group Name</label>
<input type="text" class="form-control" id="groupNameField" name="name" required>
</div>
<!-- If you want to pick permissions:
<div class="mb-3">
<label class="form-label">Permissions</label>
<select class="form-select" id="groupPermissionsField" name="permissions" multiple>
...
</select>
</div>
-->
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Save Group</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_js %}
<!-- DataTables CSS -->
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.4/css/jquery.dataTables.min.css">
<link rel="stylesheet" href="https://cdn.datatables.net/responsive/2.3.0/css/responsive.dataTables.min.css">
<!-- DataTables JS / Dependencies -->
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/responsive/2.3.0/js/dataTables.responsive.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script>
/* CSRF Helper */
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== "") {
const cookies = document.cookie.split(";");
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + "=")) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const csrftoken = getCookie("csrftoken");
document.addEventListener("DOMContentLoaded", function() {
/* STREAM PROFILES TABLE */
const streamProfilesTable = new DataTable("#streamProfilesTable", {
ajax: { url: "/api/core/streamprofiles/", dataSrc: "" },
responsive: true,
columns: [
{ data: "id" },
{ data: "profile_name" },
{ data: "command" },
{ data: "parameters" },
{
data: "is_active",
render: data => (data ? "Yes" : "No")
},
{ data: "user_agent" },
{
data: null,
render: row => `
<button class="btn btn-sm btn-primary edit-stream-profile" data-id="${row.id}">Edit</button>
<button class="btn btn-sm btn-danger delete-stream-profile" data-id="${row.id}">Delete</button>
`
}
]
});
/* ADD STREAM PROFILE */
document.getElementById("addStreamProfileBtn").addEventListener("click", () => {
document.getElementById("streamProfileForm").reset();
document.getElementById("streamProfileId").value = "";
document.getElementById("streamProfileModalLabel").textContent = "Add Stream Profile";
new bootstrap.Modal(document.getElementById("streamProfileModal")).show();
});
/* SAVE STREAM PROFILE */
document.getElementById("streamProfileForm").addEventListener("submit", function(e) {
e.preventDefault();
const idVal = document.getElementById("streamProfileId").value;
const url = idVal ? `/api/core/streamprofiles/${idVal}/` : "/api/core/streamprofiles/";
const method = idVal ? "PUT" : "POST";
const formData = new FormData(this);
fetch(url, {
method: method,
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to save Stream Profile");
return resp.json();
})
.then(() => {
Swal.fire("Success", "Stream Profile saved.", "success");
streamProfilesTable.ajax.reload();
bootstrap.Modal.getInstance(document.getElementById("streamProfileModal")).hide();
})
.catch(err => {
Swal.fire("Error", err.message, "error");
});
});
/* EDIT / DELETE STREAM PROFILE */
$("#streamProfilesTable").on("click", ".edit-stream-profile", function(){
const rowData = streamProfilesTable.row($(this).closest("tr")).data();
document.getElementById("streamProfileModalLabel").textContent = "Edit Stream Profile";
document.getElementById("streamProfileId").value = rowData.id;
document.getElementById("profileName").value = rowData.profile_name;
document.getElementById("command").value = rowData.command;
document.getElementById("parameters").value = rowData.parameters;
document.getElementById("profileActive").value = rowData.is_active ? "true" : "false";
document.getElementById("profileUserAgent").value = rowData.user_agent || "";
new bootstrap.Modal(document.getElementById("streamProfileModal")).show();
});
$("#streamProfilesTable").on("click", ".delete-stream-profile", function(){
const rowData = streamProfilesTable.row($(this).closest("tr")).data();
Swal.fire({
title: "Delete this Stream Profile?",
text: `Are you sure you want to delete "${rowData.profile_name}"?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes, delete it!"
}).then(result => {
if(result.isConfirmed) {
fetch(`/api/core/streamprofiles/${rowData.id}/`, {
method: "DELETE",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to delete profile");
Swal.fire("Deleted!", "Stream Profile was removed.", "success");
streamProfilesTable.ajax.reload();
})
.catch(err => Swal.fire("Error", err.message, "error"));
}
});
});
/* USER AGENTS TABLE */
const userAgentsTable = new DataTable("#userAgentsTable", {
ajax: { url: "/api/core/useragents/", dataSrc: "" },
responsive: true,
columns: [
{ data: "id" },
{ data: "user_agent_name" },
{ data: "user_agent" },
{ data: "description" },
{
data: "is_active",
render: data => (data ? "Yes" : "No")
},
{
data: null,
render: row => `
<button class="btn btn-sm btn-primary edit-user-agent" data-id="${row.id}">Edit</button>
<button class="btn btn-sm btn-danger delete-user-agent" data-id="${row.id}">Delete</button>
`
}
]
});
/* ADD USER AGENT */
document.getElementById("addUserAgentBtn").addEventListener("click", () => {
document.getElementById("userAgentForm").reset();
document.getElementById("userAgentModalLabel").textContent = "Add User Agent";
document.getElementById("userAgentId").value = "";
new bootstrap.Modal(document.getElementById("userAgentModal")).show();
});
/* SAVE USER AGENT */
document.getElementById("userAgentForm").addEventListener("submit", function(e){
e.preventDefault();
const idVal = document.getElementById("userAgentId").value;
const url = idVal ? `/api/core/useragents/${idVal}/` : "/api/core/useragents/";
const method = idVal ? "PUT" : "POST";
const formData = new FormData(this);
fetch(url, {
method,
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to save user agent");
return resp.json();
})
.then(() => {
Swal.fire("Success", "User Agent saved.", "success");
userAgentsTable.ajax.reload();
bootstrap.Modal.getInstance(document.getElementById("userAgentModal")).hide();
})
.catch(err => Swal.fire("Error", err.message, "error"));
});
/* EDIT / DELETE USER AGENT */
$("#userAgentsTable").on("click", ".edit-user-agent", function(){
const rowData = userAgentsTable.row($(this).closest("tr")).data();
document.getElementById("userAgentModalLabel").textContent = "Edit User Agent";
document.getElementById("userAgentId").value = rowData.id;
document.getElementById("userAgentNameField").value = rowData.user_agent_name || "";
document.getElementById("userAgentStringField").value = rowData.user_agent || "";
document.getElementById("userAgentDescription").value = rowData.description || "";
document.getElementById("userAgentActive").value = rowData.is_active ? "true" : "false";
new bootstrap.Modal(document.getElementById("userAgentModal")).show();
});
$("#userAgentsTable").on("click", ".delete-user-agent", function(){
const rowData = userAgentsTable.row($(this).closest("tr")).data();
Swal.fire({
title: "Delete this User Agent?",
text: `Are you sure you want to delete "${rowData.user_agent_name}"?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes, delete it!"
}).then(result => {
if(result.isConfirmed) {
fetch(`/api/core/useragents/${rowData.id}/`, {
method: "DELETE",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to delete user agent");
Swal.fire("Deleted!", "User Agent was removed.", "success");
userAgentsTable.ajax.reload();
})
.catch(err => Swal.fire("Error", err.message, "error"));
}
});
});
/* LOGO CACHING SETTINGS */
document.getElementById("logoCachingForm").addEventListener("submit", function(e){
e.preventDefault();
const formData = new FormData(this);
fetch("/api/core/settings/1/", {
method: "PUT",
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(response => {
if(response.ok) Swal.fire("Success", "Logo caching settings saved.", "success");
else Swal.fire("Error", "Failed to save logo caching settings.", "error");
});
});
/* BACKUP / RESTORE */
document.getElementById("createBackupBtn").addEventListener("click", function(){
fetch("/api/channels/backup/", {
method: "POST",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(resp.ok) Swal.fire("Success", "Backup created.", "success");
else Swal.fire("Error", "Backup failed.", "error");
});
});
document.getElementById("restoreBackupBtn").addEventListener("click", function(){
fetch("/api/channels/restore/", {
method: "POST",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(resp.ok) Swal.fire("Success", "Restore complete.", "success");
else Swal.fire("Error", "Restore failed.", "error");
});
});
/* HDHR Emulation */
document.getElementById("hdhrEmulationForm").addEventListener("submit", function(e){
e.preventDefault();
const formData = new FormData(this);
fetch("/api/core/settings/1/", {
method: "PUT",
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(resp.ok) Swal.fire("Success", "HDHR settings saved.", "success");
else Swal.fire("Error", "Failed to save HDHR settings.", "error");
});
});
/* USERS TABLE */
const usersTable = new DataTable("#usersTable", {
ajax: { url: "/api/accounts/users", dataSrc: "" },
responsive: true,
columns: [
{ data: "id" },
{ data: "username" },
{ data: "email" },
{ data: "groups" },
{
data: null,
render: row => `
<button class="btn btn-sm btn-primary edit-user" data-id="${row.id}">Edit</button>
<button class="btn btn-sm btn-danger delete-user" data-id="${row.id}">Delete</button>
`
}
]
});
/* ADD USER */
document.getElementById("addUserBtn").addEventListener("click", () => {
document.getElementById("userForm").reset();
document.getElementById("userModalLabel").textContent = "Add User";
document.getElementById("userId").value = "";
new bootstrap.Modal(document.getElementById("userModal")).show();
});
/* SAVE USER */
document.getElementById("userForm").addEventListener("submit", function(e){
e.preventDefault();
const idVal = document.getElementById("userId").value;
const url = idVal ? `/api/accounts/users/${idVal}/` : "/api/accounts/users/";
const method = idVal ? "PUT" : "POST";
const formData = new FormData(this);
fetch(url, {
method,
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to save user");
return resp.json();
})
.then(() => {
Swal.fire("Success", "User saved.", "success");
usersTable.ajax.reload();
bootstrap.Modal.getInstance(document.getElementById("userModal")).hide();
})
.catch(err => Swal.fire("Error", err.message, "error"));
});
/* EDIT / DELETE USER */
$("#usersTable").on("click", ".edit-user", function(){
const rowData = usersTable.row($(this).closest("tr")).data();
document.getElementById("userModalLabel").textContent = "Edit User";
document.getElementById("userId").value = rowData.id;
document.getElementById("usernameField").value = rowData.username || "";
document.getElementById("emailField").value = rowData.email || "";
new bootstrap.Modal(document.getElementById("userModal")).show();
});
$("#usersTable").on("click", ".delete-user", function(){
const rowData = usersTable.row($(this).closest("tr")).data();
Swal.fire({
title: "Delete this user?",
text: `Are you sure you want to delete "${rowData.username}"?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes, delete!"
}).then(res => {
if(res.isConfirmed){
fetch(`/api/accounts/users/${rowData.id}/`, {
method: "DELETE",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to delete user");
Swal.fire("Deleted!", "User was removed.", "success");
usersTable.ajax.reload();
})
.catch(err => Swal.fire("Error", err.message, "error"));
}
});
});
/* GROUPS TABLE */
const groupsTable = new DataTable("#groupsTable", {
ajax: { url: "/api/accounts/groups/", dataSrc: "" },
responsive: true,
columns: [
{ data: "id" },
{ data: "name" },
{
data: "permissions",
render: perms => {
if(!perms || !perms.length) return "";
return perms.join(", ");
}
},
{
data: null,
render: row => `
<button class="btn btn-sm btn-primary edit-group" data-id="${row.id}">Edit</button>
<button class="btn btn-sm btn-danger delete-group" data-id="${row.id}">Delete</button>
`
}
]
});
/* ADD GROUP */
document.getElementById("addGroupBtn").addEventListener("click", () => {
document.getElementById("groupForm").reset();
document.getElementById("groupModalLabel").textContent = "Add Group";
document.getElementById("groupId").value = "";
new bootstrap.Modal(document.getElementById("groupModal")).show();
});
/* SAVE GROUP */
document.getElementById("groupForm").addEventListener("submit", function(e){
e.preventDefault();
const idVal = document.getElementById("groupId").value;
const url = idVal ? `/api/accounts/groups/${idVal}/` : "/api/accounts/groups/";
const method = idVal ? "PUT" : "POST";
const formData = new FormData(this);
fetch(url, {
method,
body: formData,
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to save group");
return resp.json();
})
.then(() => {
Swal.fire("Success", "Group saved.", "success");
groupsTable.ajax.reload();
bootstrap.Modal.getInstance(document.getElementById("groupModal")).hide();
})
.catch(err => Swal.fire("Error", err.message, "error"));
});
/* EDIT / DELETE GROUP */
$("#groupsTable").on("click", ".edit-group", function(){
const rowData = groupsTable.row($(this).closest("tr")).data();
document.getElementById("groupModalLabel").textContent = "Edit Group";
document.getElementById("groupId").value = rowData.id;
document.getElementById("groupNameField").value = rowData.name || "";
new bootstrap.Modal(document.getElementById("groupModal")).show();
});
$("#groupsTable").on("click", ".delete-group", function(){
const rowData = groupsTable.row($(this).closest("tr")).data();
Swal.fire({
title: "Delete this group?",
text: `Are you sure you want to delete "${rowData.name}"?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes, delete!"
}).then(res => {
if(res.isConfirmed){
fetch(`/api/accounts/groups/${rowData.id}/`, {
method: "DELETE",
headers: { "X-CSRFToken": csrftoken }
})
.then(resp => {
if(!resp.ok) throw new Error("Failed to delete group");
Swal.fire("Deleted!", "Group was removed.", "success");
groupsTable.ajax.reload();
})
.catch(err => Swal.fire("Error", err.message, "error"));
}
});
});
});
</script>
{% endblock %}