diff --git a/groups-domains.php b/groups-domains.php new file mode 100644 index 00000000..27f3046f --- /dev/null +++ b/groups-domains.php @@ -0,0 +1,105 @@ + + + +
+
+
| Domain | +Type | +Status | +Comment | +Group assignment | +Action | +
|---|
- +
"+data["ip"]+"" );
@@ -96,26 +96,22 @@ $(document).ready(function() {
let button = "";
- if(data["id"] !== 0)
- {
- button +=
+ "" +
" " +
"";
- }
$('td:eq(3)', row).html( button );
},
"lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
"stateSave": true,
stateSaveCallback: function(settings, data) {
// Store current state in client's local storage area
- localStorage.setItem("groups-table", JSON.stringify(data));
+ localStorage.setItem("groups-clients-table", JSON.stringify(data));
},
stateLoadCallback: function(settings) {
// Receive previous state from client's local storage area
- var data = localStorage.getItem("groups-table");
+ var data = localStorage.getItem("groups-clients-table");
// Return if not available
if(data === null){ return null; }
data = JSON.parse(data);
diff --git a/scripts/pi-hole/js/groups-domains.js b/scripts/pi-hole/js/groups-domains.js
new file mode 100644
index 00000000..082af2dc
--- /dev/null
+++ b/scripts/pi-hole/js/groups-domains.js
@@ -0,0 +1,214 @@
+var table, groups;
+
+function showAlert(type, message)
+{
+ var alertElement = null;
+ var messageElement = null;
+
+ switch (type)
+ {
+ case 'info': alertElement = $('#alInfo'); break;
+ case 'success': alertElement = $('#alSuccess'); break;
+ case 'warning': alertElement = $('#alWarning'); messageElement = $('#warn'); break;
+ case 'error': alertElement = $('#alFailure'); messageElement = $('#err'); break;
+ default: return;
+ }
+
+ if (messageElement != null)
+ messageElement.html(message);
+
+ alertElement.fadeIn(200);
+ alertElement.delay(8000).fadeOut(2000);
+}
+
+function get_groups()
+{
+ $.get("scripts/pi-hole/php/groups.php", { 'action': 'get_groups' },
+ function(data) {
+ groups = data.data;
+ }, "json");
+}
+
+$.fn.redraw = function(){
+ return $(this).each(function(){
+ var redraw = this.offsetHeight;
+ });
+};
+
+$(document).ready(function() {
+
+ $('#btnAdd').on('click', addDomain);
+
+ get_groups();
+
+ $('#select').on('change', function() {
+ $("#ip-custom").val("");
+ $("#ip-custom").prop( "disabled" , $( "#select option:selected" ).val() !== "custom");
+ });
+
+ table = $("#domainsTable").DataTable( {
+ "ajax": "scripts/pi-hole/php/groups.php?action=get_domains",
+ order: [[ 1, 'asc' ]],
+ columns: [
+ { data: null },
+ { data: null, "orderable": false },
+ { data: null, "orderable": false },
+ { data: null, "orderable": false },
+ { data: null, "orderable": false },
+ { data: null, width: "60px", "orderable": false }
+ ],
+ "drawCallback": function( settings ) {
+ $('.editDomain').on('click', editDomain);
+ $('.deleteDomain').on('click', deleteDomain);
+ },
+ "rowCallback": function( row, data ) {
+ $('td:eq(0)', row).html( ''+data["domain"]+'' );
+
+ $('td:eq(1)', row).html( '' );
+
+ const disabled = data["enabled"] === 0;
+ $('td:eq(2)', row).html( '' );
+
+ $('td:eq(3)', row).html( '' );
+ $('#comment', row).val(data["comment"]);
+
+ $('td:eq(4)', row).empty();
+ $('td:eq(4)', row).append( '' );
+ var sel = $('#multiselect', row);
+ // Add all known groups
+ for (var i = 0; i < groups.length; i++) {
+ var extra = 'ID ' + groups[i].id;
+ if(!groups[i].enabled)
+ {
+ extra += ', disabled';
+ }
+ sel.append($('').val(groups[i].id).text(groups[i].name + ' (' + extra + ')'));
+ sel.redraw();
+ }
+ // Select assigned groups
+ sel.val(data.groups);
+ // Initialize multiselect
+ sel.multiselect({ includeSelectAllOption: true });
+
+ let button = "" +
+ " " +
+ "";
+ $('td:eq(5)', row).html( button );
+ },
+ "lengthMenu": [[10, 25, 50, 100, -1], [10, 25, 50, 100, "All"]],
+ "stateSave": true,
+ stateSaveCallback: function(settings, data) {
+ // Store current state in client's local storage area
+ localStorage.setItem("groups-domains-table", JSON.stringify(data));
+ },
+ stateLoadCallback: function(settings) {
+ // Receive previous state from client's local storage area
+ var data = localStorage.getItem("groups-domains-table");
+ // Return if not available
+ if(data === null){ return null; }
+ data = JSON.parse(data);
+ // Always start on the first page to show most recent queries
+ data["start"] = 0;
+ // Always start with empty search field
+ data["search"]["search"] = "";
+ // Apply loaded state to table
+ return data;
+ }
+ });
+});
+
+function addDomain()
+{
+ var domain = $("#domain").val();
+ var type = $("#type").val();
+ var comment = $("#comment").val();
+
+ showAlert('info');
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: 'json',
+ data: {"action": "add_domain", "domain": domain, "type": type, "comment": comment},
+ success: function(response) {
+ if (response.success) {
+ showAlert('success');
+ $("#domain").empty();
+ $("#comment").empty();
+ table.ajax.reload();
+ }
+ else
+ showAlert('error', response.message);
+ },
+ error: function(jqXHR, exception) {
+ showAlert('error', "Error while adding new group");
+ console.log(exception);
+ }
+ });
+}
+
+function editDomain()
+{
+ var tr = $(this).closest("tr");
+ var id = tr.find("#id").val();
+ var type = tr.find("#type").val();
+ var status = tr.find("#status").val();
+ var comment = tr.find("#comment").val();
+ var groups = tr.find("#multiselect").val();
+
+ showAlert('info');
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: 'json',
+ data: {"action": "edit_domain", "id": id, "type": type, "comment": comment, "status": status, "groups": groups},
+ success: function(response) {
+ if (response.success) {
+ showAlert('success');
+ table.ajax.reload();
+ }
+ else
+ showAlert('error', response.message);
+ },
+ error: function(jqXHR, exception) {
+ showAlert('error', "Error while editing domain with ID "+id);
+ console.log(exception);
+ }
+ });
+}
+
+function deleteDomain()
+{
+ var id = $(this).attr("data-id");
+
+ showAlert('info');
+ $.ajax({
+ url: "scripts/pi-hole/php/groups.php",
+ method: "post",
+ dataType: 'json',
+ data: {"action": "delete_domain", "id": id},
+ success: function(response) {
+ if (response.success) {
+ showAlert('success');
+ table.ajax.reload();
+ }
+ else
+ showAlert('error', response.message);
+ },
+ error: function(jqXHR, exception) {
+ showAlert('error', "Error while deleting domain with ID "+id);
+ console.log(exception);
+ }
+ });
+}
diff --git a/scripts/pi-hole/js/groups.js b/scripts/pi-hole/js/groups.js
index 0ed63982..4f9d3daa 100644
--- a/scripts/pi-hole/js/groups.js
+++ b/scripts/pi-hole/js/groups.js
@@ -29,11 +29,11 @@ $(document).ready(function() {
"ajax": "scripts/pi-hole/php/groups.php?action=get_groups",
order: [[ 1, 'asc' ]],
columns: [
- { data: "id", width: "10px" },
+ { data: "id", width: "60px" },
{ data: "enabled" },
{ data: "name" },
{ data: "description" },
- { data: null, width: "20px" }
+ { data: null, width: "60px", "orderable": false }
],
"drawCallback": function( settings ) {
$('.deleteGroup').on('click', deleteGroup);
@@ -103,6 +103,8 @@ function addGroup()
success: function(response) {
if (response.success) {
showAlert('success');
+ $("#name").empty();
+ $("#desc").empty();
table.ajax.reload();
}
else
diff --git a/scripts/pi-hole/php/groups.php b/scripts/pi-hole/php/groups.php
index cba75390..33d93a20 100644
--- a/scripts/pi-hole/php/groups.php
+++ b/scripts/pi-hole/php/groups.php
@@ -358,12 +358,160 @@ elseif($_REQUEST['action'] == "delete_client")
// Delete client identified by ID
try
{
+ $stmt = $db->prepare('DELETE FROM client_by_group WHERE client_id=:id');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing client_by_group statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id to client_by_group statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing client_by_group statement: ".$db->lastErrorMsg());
+ }
+
$stmt = $db->prepare('DELETE FROM client WHERE id=:id');
if(!$stmt)
+ {
+ throw new Exception("While preparing client statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id to client statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing client statement: ".$db->lastErrorMsg());
+ }
+
+ $reload = true;
+ return JSON_success();
+ }
+ catch (\Exception $ex)
+ {
+ return JSON_error($ex->getMessage());
+ }
+}
+elseif($_REQUEST['action'] == "get_domains")
+{
+ // List all available groups
+ try
+ {
+ $query = $db->query("SELECT * FROM domainlist;");
+ if(!$query)
+ {
+ throw new Exception("Error while querying gravity's domainlist table: ".$db->lastErrorMsg());
+ }
+
+ $data = array();
+ while($res = $query->fetchArray(SQLITE3_ASSOC))
+ {
+ $group_query = $db->query("SELECT group_id FROM domainlist_by_group WHERE domainlist_id = ".$res["id"].";");
+ if(!$group_query)
+ {
+ throw new Exception("Error while querying gravity's domainlist_by_group table: ".$db->lastErrorMsg());
+ }
+
+ $groups = array();
+ while($gres = $group_query->fetchArray(SQLITE3_ASSOC))
+ {
+ array_push($groups,$gres["group_id"]);
+ }
+ $res["groups"] = $groups;
+ array_push($data,$res);
+ }
+
+
+ echo json_encode(array("data" => $data));
+ }
+ catch (\Exception $ex)
+ {
+ return JSON_error($ex->getMessage());
+ }
+}
+elseif($_REQUEST['action'] == "add_domain")
+{
+ // Add new domain
+ try
+ {
+ $stmt = $db->prepare('INSERT INTO domainlist (domain,type,comment) VALUES (:domain,:type,:comment)');
+ if(!$stmt)
{
throw new Exception("While preparing statement: ".$db->lastErrorMsg());
}
+ if(!$stmt->bindValue(':domain', $_POST["domain"], SQLITE3_TEXT))
+ {
+ throw new Exception("While binding domain: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':type', intval($_POST["type"]), SQLITE3_TEXT))
+ {
+ throw new Exception("While binding type: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':comment', $_POST["comment"], SQLITE3_TEXT))
+ {
+ throw new Exception("While binding comment: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing: ".$db->lastErrorMsg());
+ }
+
+ $reload = true;
+ return JSON_success();
+ }
+ catch (\Exception $ex)
+ {
+ return JSON_error($ex->getMessage());
+ }
+}
+elseif($_REQUEST['action'] == "edit_domain")
+{
+ // Edit domain identified by ID
+ try
+ {
+ $stmt = $db->prepare('UPDATE domainlist SET enabled=:enabled, comment=:comment, type=:type WHERE id = :id');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing statement: ".$db->lastErrorMsg());
+ }
+
+ $status = intval($_POST["status"]);
+ if($status !== 0)
+ {
+ $status = 1;
+ }
+
+ if(!$stmt->bindValue(':enabled', $status, SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding enabled: ".$db->lastErrorMsg());
+ }
+
+ $comment = $_POST["comment"];
+ if(strlen($comment) == 0)
+ {
+ // Store NULL in database for empty comments
+ $comment = null;
+ }
+ if(!$stmt->bindValue(':comment', $comment, SQLITE3_TEXT))
+ {
+ throw new Exception("While binding comment: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':type', intval($_POST["type"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding type: ".$db->lastErrorMsg());
+ }
+
if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
{
throw new Exception("While binding id: ".$db->lastErrorMsg());
@@ -374,6 +522,93 @@ elseif($_REQUEST['action'] == "delete_client")
throw new Exception("While executing: ".$db->lastErrorMsg());
}
+ $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id = :id');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing DELETE statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing DELETE statement: ".$db->lastErrorMsg());
+ }
+
+ $db->query("BEGIN TRANSACTION;");
+ foreach ($_POST["groups"] as $gid)
+ {
+ $stmt = $db->prepare('INSERT INTO domainlist_by_group (domainlist_id,group_id) VALUES(:id,:gid);');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing INSERT INTO statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':gid', intval($gid), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding gid: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing INSERT INTO statement: ".$db->lastErrorMsg());
+ }
+ }
+ $db->query("COMMIT;");
+
+ $reload = true;
+ return JSON_success();
+ }
+ catch (\Exception $ex)
+ {
+ return JSON_error($ex->getMessage());
+ }
+}
+elseif($_REQUEST['action'] == "delete_domain")
+{
+ // Delete domain identified by ID
+ try
+ {
+ $stmt = $db->prepare('DELETE FROM domainlist_by_group WHERE domainlist_id=:id');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing domainlist_by_group statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id to domainlist_by_group statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing domainlist_by_group statement: ".$db->lastErrorMsg());
+ }
+
+ $stmt = $db->prepare('DELETE FROM domainlist WHERE id=:id');
+ if(!$stmt)
+ {
+ throw new Exception("While preparing domainlist statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->bindValue(':id', intval($_POST["id"]), SQLITE3_INTEGER))
+ {
+ throw new Exception("While binding id to domainlist statement: ".$db->lastErrorMsg());
+ }
+
+ if(!$stmt->execute())
+ {
+ throw new Exception("While executing domainlist statement: ".$db->lastErrorMsg());
+ }
+
$reload = true;
return JSON_success();
}