mirror of
https://github.com/pi-hole/web.git
synced 2024-12-06 19:36:21 +01:00
Merge pull request #1276 from pi-hole/fix/teleporter
Improvements for Teleporter
This commit is contained in:
@@ -6,134 +6,10 @@
|
||||
|
||||
switch ($_REQUEST['action'])
|
||||
{
|
||||
case 'get': echo json_encode(echoCustomDNSEntries()); break;
|
||||
case 'add': echo json_encode(addCustomDNSEntry()); break;
|
||||
case 'delete': echo json_encode(deleteCustomDNSEntry()); break;
|
||||
case 'get': echo json_encode(echoCustomDNSEntries()); break;
|
||||
case 'add': echo json_encode(addCustomDNSEntry()); break;
|
||||
case 'delete': echo json_encode(deleteCustomDNSEntry()); break;
|
||||
default:
|
||||
die("Wrong action");
|
||||
}
|
||||
|
||||
function echoCustomDNSEntries()
|
||||
{
|
||||
$entries = getCustomDNSEntries();
|
||||
|
||||
$data = [];
|
||||
foreach ($entries as $entry)
|
||||
$data[] = [ $entry->domain, $entry->ip ];
|
||||
|
||||
return [ "data" => $data ];
|
||||
}
|
||||
|
||||
function getCustomDNSEntries()
|
||||
{
|
||||
global $customDNSFile;
|
||||
|
||||
$entries = [];
|
||||
|
||||
$handle = fopen($customDNSFile, "r");
|
||||
if ($handle)
|
||||
{
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = str_replace("\r","", $line);
|
||||
$line = str_replace("\n","", $line);
|
||||
$explodedLine = explode (" ", $line);
|
||||
|
||||
if (count($explodedLine) != 2)
|
||||
continue;
|
||||
|
||||
$data = new \stdClass();
|
||||
$data->ip = $explodedLine[0];
|
||||
$data->domain = $explodedLine[1];
|
||||
$entries[] = $data;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
function addCustomDNSEntry()
|
||||
{
|
||||
try
|
||||
{
|
||||
$ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
|
||||
$domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
|
||||
|
||||
if (empty($ip))
|
||||
return errorJsonResponse("IP must be set");
|
||||
|
||||
$ipType = get_ip_type($ip);
|
||||
|
||||
if (!$ipType)
|
||||
return errorJsonResponse("IP must be valid");
|
||||
|
||||
if (empty($domain))
|
||||
return errorJsonResponse("Domain must be set");
|
||||
|
||||
if (!is_valid_domain_name($domain))
|
||||
return errorJsonResponse("Domain must be valid");
|
||||
|
||||
$existingEntries = getCustomDNSEntries();
|
||||
|
||||
foreach ($existingEntries as $entry)
|
||||
if ($entry->domain == $domain)
|
||||
if (get_ip_type($entry->ip) == $ipType)
|
||||
return errorJsonResponse("This domain already has a custom DNS entry for an IPv" . $ipType);
|
||||
|
||||
pihole_execute("-a addcustomdns ".$ip." ".$domain);
|
||||
|
||||
return successJsonResponse();
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return errorJsonResponse($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCustomDNSEntry()
|
||||
{
|
||||
try
|
||||
{
|
||||
$ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
|
||||
$domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
|
||||
|
||||
if (empty($ip))
|
||||
return errorJsonResponse("IP must be set");
|
||||
|
||||
if (empty($domain))
|
||||
return errorJsonResponse("Domain must be set");
|
||||
|
||||
$existingEntries = getCustomDNSEntries();
|
||||
|
||||
$found = false;
|
||||
foreach ($existingEntries as $entry)
|
||||
if ($entry->domain == $domain)
|
||||
if ($entry->ip == $ip) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$found)
|
||||
return errorJsonResponse("This domain/ip association does not exist");
|
||||
|
||||
pihole_execute("-a removecustomdns ".$ip." ".$domain);
|
||||
|
||||
return successJsonResponse();
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return errorJsonResponse($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function successJsonResponse($message = "")
|
||||
{
|
||||
return [ "success" => true, "message" => $message ];
|
||||
}
|
||||
|
||||
function errorJsonResponse($message = "")
|
||||
{
|
||||
return [ "success" => false, "message" => $message ];
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -81,4 +81,177 @@ function pihole_execute($argument_string, $error_on_failure = true) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
function echoCustomDNSEntries()
|
||||
{
|
||||
$entries = getCustomDNSEntries();
|
||||
|
||||
$data = [];
|
||||
foreach ($entries as $entry)
|
||||
$data[] = [ $entry->domain, $entry->ip ];
|
||||
|
||||
return [ "data" => $data ];
|
||||
}
|
||||
|
||||
function getCustomDNSEntries()
|
||||
{
|
||||
global $customDNSFile;
|
||||
|
||||
$entries = [];
|
||||
|
||||
$handle = fopen($customDNSFile, "r");
|
||||
if ($handle)
|
||||
{
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = str_replace("\r","", $line);
|
||||
$line = str_replace("\n","", $line);
|
||||
$explodedLine = explode (" ", $line);
|
||||
|
||||
if (count($explodedLine) != 2)
|
||||
continue;
|
||||
|
||||
$data = new \stdClass();
|
||||
$data->ip = $explodedLine[0];
|
||||
$data->domain = $explodedLine[1];
|
||||
$entries[] = $data;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return $entries;
|
||||
}
|
||||
|
||||
function addCustomDNSEntry($ip="", $domain="", $json_reply=true)
|
||||
{
|
||||
function error($msg)
|
||||
{
|
||||
global $json_reply;
|
||||
if($json_reply)
|
||||
return errorJsonResponse($msg);
|
||||
else {
|
||||
echo $msg."<br>";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(isset($_REQUEST['ip']))
|
||||
$ip = $_REQUEST['ip'];
|
||||
|
||||
if(isset($_REQUEST['domain']))
|
||||
$domain = $_REQUEST['domain'];
|
||||
|
||||
if (empty($ip))
|
||||
return error("IP must be set");
|
||||
|
||||
$ipType = get_ip_type($ip);
|
||||
|
||||
if (!$ipType)
|
||||
return error("IP must be valid");
|
||||
|
||||
if (empty($domain))
|
||||
return error("Domain must be set");
|
||||
|
||||
if (!is_valid_domain_name($domain))
|
||||
return error("Domain must be valid");
|
||||
|
||||
// Only check for duplicates if adding new records from the web UI (not through Teleporter)
|
||||
if(isset($_REQUEST['ip']) || isset($_REQUEST['domain']))
|
||||
{
|
||||
$existingEntries = getCustomDNSEntries();
|
||||
foreach ($existingEntries as $entry)
|
||||
if ($entry->domain == $domain && get_ip_type($entry->ip) == $ipType)
|
||||
return error("This domain already has a custom DNS entry for an IPv" . $ipType);
|
||||
}
|
||||
|
||||
// Add record
|
||||
pihole_execute("-a addcustomdns ".$ip." ".$domain);
|
||||
|
||||
return $json_reply ? successJsonResponse() : true;
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return error($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function deleteCustomDNSEntry()
|
||||
{
|
||||
try
|
||||
{
|
||||
$ip = !empty($_REQUEST['ip']) ? $_REQUEST['ip']: "";
|
||||
$domain = !empty($_REQUEST['domain']) ? $_REQUEST['domain']: "";
|
||||
|
||||
if (empty($ip))
|
||||
return errorJsonResponse("IP must be set");
|
||||
|
||||
if (empty($domain))
|
||||
return errorJsonResponse("Domain must be set");
|
||||
|
||||
$existingEntries = getCustomDNSEntries();
|
||||
|
||||
$found = false;
|
||||
foreach ($existingEntries as $entry)
|
||||
if ($entry->domain == $domain)
|
||||
if ($entry->ip == $ip) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$found)
|
||||
return errorJsonResponse("This domain/ip association does not exist");
|
||||
|
||||
pihole_execute("-a removecustomdns ".$ip." ".$domain);
|
||||
|
||||
return successJsonResponse();
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return errorJsonResponse($ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
function deleteAllCustomDNSEntries()
|
||||
{
|
||||
$handle = fopen($customDNSFile, "r");
|
||||
if ($handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = str_replace("\r","", $line);
|
||||
$line = str_replace("\n","", $line);
|
||||
$explodedLine = explode (" ", $line);
|
||||
|
||||
if (count($explodedLine) != 2)
|
||||
continue;
|
||||
|
||||
$ip = $explodedLine[0];
|
||||
$domain = $explodedLine[1];
|
||||
|
||||
pihole_execute("-a removecustomdns ".$ip." ".$domain);
|
||||
}
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return errorJsonResponse($ex->getMessage());
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
|
||||
return successJsonResponse();
|
||||
}
|
||||
|
||||
function successJsonResponse($message = "")
|
||||
{
|
||||
return [ "success" => true, "message" => $message ];
|
||||
}
|
||||
|
||||
function errorJsonResponse($message = "")
|
||||
{
|
||||
return [ "success" => false, "message" => $message ];
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@@ -40,11 +40,11 @@ function archive_add_table($name, $table, $type=-1)
|
||||
|
||||
if($type > -1)
|
||||
{
|
||||
$querystr = "SELECT * FROM $table WHERE type = $type;";
|
||||
$querystr = "SELECT * FROM \"$table\" WHERE type = $type;";
|
||||
}
|
||||
else
|
||||
{
|
||||
$querystr = "SELECT * FROM $table;";
|
||||
$querystr = "SELECT * FROM \"$table\";";
|
||||
}
|
||||
$results = $db->query($querystr);
|
||||
|
||||
@@ -87,31 +87,58 @@ function archive_restore_table($file, $table, $flush=false)
|
||||
// Flush table if requested, only flush each table once
|
||||
if($flush && !in_array($table, $flushed_tables))
|
||||
{
|
||||
$db->exec("DELETE FROM ".$table);
|
||||
$db->exec("DELETE FROM \"".$table."\"");
|
||||
array_push($flushed_tables, $table);
|
||||
}
|
||||
|
||||
// Prepare field name for domain/address depending on the table we restore to
|
||||
// Prepare fields depending on the table we restore to
|
||||
if($table === "adlist")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO adlist";
|
||||
$sql .= " (id,address,enabled,date_added,comment)";
|
||||
$sql .= " VALUES (:id,:address,:enabled,:date_added,:comment);";
|
||||
$field = "address";
|
||||
}
|
||||
elseif($table === "domain_audit")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO domain_audit";
|
||||
$sql .= " (id,domain,date_added)";
|
||||
$sql .= " VALUES (:id,:domain,:date_added);";
|
||||
$field = "domain";
|
||||
}
|
||||
elseif($table === "domainlist")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO domainlist";
|
||||
$sql .= " (id,domain,enabled,date_added,comment,type)";
|
||||
$sql .= " VALUES (:id,:domain,:enabled,:date_added,:comment,:type);";
|
||||
$field = "domain";
|
||||
}
|
||||
elseif($table === "group")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO \"group\"";
|
||||
$sql .= " (id,name,date_added,description)";
|
||||
$sql .= " VALUES (:id,:name,:date_added,:description);";
|
||||
}
|
||||
elseif($table === "client")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO client";
|
||||
$sql .= " (id,ip,date_added,comment)";
|
||||
$sql .= " VALUES (:id,:ip,:date_added,:comment);";
|
||||
}
|
||||
elseif($table === "domainlist_by_group")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO domainlist_by_group";
|
||||
$sql .= " (domainlist_id,group_id)";
|
||||
$sql .= " VALUES (:domainlist_id,:group_id);";
|
||||
}
|
||||
elseif($table === "client_by_group")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO client_by_group";
|
||||
$sql .= " (client_id,group_id)";
|
||||
$sql .= " VALUES (:client_id,:group_id);";
|
||||
}
|
||||
elseif($table === "adlist_by_group")
|
||||
{
|
||||
$sql = "INSERT OR IGNORE INTO adlist_by_group";
|
||||
$sql .= " (adlist_id,group_id)";
|
||||
$sql .= " VALUES (:adlist_id,:group_id);";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -149,23 +176,27 @@ function archive_restore_table($file, $table, $flush=false)
|
||||
if(strlen($row[$field]) > 253)
|
||||
continue;
|
||||
|
||||
$stmt->bindValue(":id", $row["id"], SQLITE3_INTEGER);
|
||||
$stmt->bindValue(":date_added", $row["date_added"], SQLITE3_INTEGER);
|
||||
$stmt->bindValue(":".$field, $row[$field], SQLITE3_TEXT);
|
||||
|
||||
if($table !== "domain_audit")
|
||||
{
|
||||
$stmt->bindValue(":enabled", $row["enabled"], SQLITE3_INTEGER);
|
||||
if(is_null($row["comment"]))
|
||||
$type = SQLITE3_NULL;
|
||||
else
|
||||
$type = SQLITE3_TEXT;
|
||||
$stmt->bindValue(":comment", $row["comment"], $type);
|
||||
}
|
||||
|
||||
if($table === "domainlist")
|
||||
{
|
||||
$stmt->bindValue(":type", $row["type"], SQLITE3_INTEGER);
|
||||
// Bind properties from JSON data
|
||||
// Note that only defined above are actually used
|
||||
// so even maliciously modified Teleporter files
|
||||
// cannot be dangerous in any way
|
||||
foreach($row as $key => $value) {
|
||||
$type = gettype($value);
|
||||
$sqltype=NULL;
|
||||
switch($type) {
|
||||
case "integer":
|
||||
$sqltype = SQLITE3_INTEGER;
|
||||
break;
|
||||
case "string":
|
||||
$sqltype = SQLITE3_TEXT;
|
||||
break;
|
||||
case "NULL":
|
||||
$sqltype = SQLITE3_NULL;
|
||||
break;
|
||||
default:
|
||||
$sqltype = "UNK";
|
||||
}
|
||||
$stmt->bindValue(":".$key, $value, $sqltype);
|
||||
}
|
||||
|
||||
if($stmt->execute() && $stmt->reset() && $stmt->clear())
|
||||
@@ -248,10 +279,10 @@ function flush_table($table, $type=null)
|
||||
if(!in_array($table, $flushed_tables))
|
||||
{
|
||||
if($type !== null) {
|
||||
$sql = "DELETE FROM ".$table." WHERE type = ".$type;
|
||||
$sql = "DELETE FROM \"".$table."\" WHERE type = ".$type;
|
||||
array_push($flushed_tables, $table.$type);
|
||||
} else {
|
||||
$sql = "DELETE FROM ".$table;
|
||||
$sql = "DELETE FROM \"".$table."\"";
|
||||
array_push($flushed_tables, $table);
|
||||
}
|
||||
$db->exec($sql);
|
||||
@@ -411,6 +442,43 @@ if(isset($_POST["action"]))
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["group"]) && $file->getFilename() === "group.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "group", $flushtables);
|
||||
echo "Processed group (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["client"]) && $file->getFilename() === "client.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "client", $flushtables);
|
||||
echo "Processed client (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["client"]) && $file->getFilename() === "client_by_group.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "client_by_group", $flushtables);
|
||||
echo "Processed client group assignments (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if((isset($_POST["whitelist"]) || isset($_POST["regex_whitelist"]) ||
|
||||
isset($_POST["blacklist"]) || isset($_POST["regex_blacklist"])) &&
|
||||
$file->getFilename() === "domainlist_by_group.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "domainlist_by_group", $flushtables);
|
||||
echo "Processed black-/whitelist group assignments (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["adlist"]) && $file->getFilename() === "adlist_by_group.json")
|
||||
{
|
||||
$num = archive_restore_table($file, "adlist_by_group", $flushtables);
|
||||
echo "Processed adlist group assignments (".$num." entries)<br>\n";
|
||||
$importedsomething = true;
|
||||
}
|
||||
|
||||
if(isset($_POST["staticdhcpleases"]) && $file->getFilename() === "04-pihole-static-dhcp.conf")
|
||||
{
|
||||
if($flushtables) {
|
||||
@@ -435,6 +503,26 @@ if(isset($_POST["action"]))
|
||||
$importedsomething = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_POST["localdnsrecords"]) && $file->getFilename() === "custom.list")
|
||||
{
|
||||
if($flushtables) {
|
||||
// Defined in func.php included via auth.php
|
||||
deleteAllCustomDNSEntries();
|
||||
}
|
||||
$num = 0;
|
||||
$localdnsrecords = process_file(file_get_contents($file));
|
||||
foreach($localdnsrecords as $record) {
|
||||
list($ip,$domain) = explode(" ",$record);
|
||||
if(addCustomDNSEntry($ip, $domain, false))
|
||||
$num++;
|
||||
}
|
||||
|
||||
echo "Processed local DNS records (".$num." entries)<br>\n";
|
||||
if($num > 0) {
|
||||
$importedsomething = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($importedsomething)
|
||||
@@ -467,8 +555,18 @@ else
|
||||
archive_add_table("blacklist.regex.json", "domainlist", ListType::regex_blacklist);
|
||||
archive_add_table("adlist.json", "adlist");
|
||||
archive_add_table("domain_audit.json", "domain_audit");
|
||||
archive_add_table("group.json", "group");
|
||||
archive_add_table("client.json", "client");
|
||||
|
||||
// Group linking tables
|
||||
archive_add_table("domainlist_by_group.json", "domainlist_by_group");
|
||||
archive_add_table("adlist_by_group.json", "adlist_by_group");
|
||||
archive_add_table("client_by_group.json", "client_by_group");
|
||||
|
||||
archive_add_file("/etc/pihole/","setupVars.conf");
|
||||
archive_add_file("/etc/pihole/","dhcp.leases");
|
||||
archive_add_file("/etc/pihole/","custom.list");
|
||||
archive_add_file("/etc/pihole/","pihole-FTL.conf");
|
||||
archive_add_file("/etc/","hosts","etc/");
|
||||
archive_add_directory("/etc/dnsmasq.d/","dnsmasq.d/");
|
||||
|
||||
|
||||
+37
-12
@@ -1073,13 +1073,13 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<div class="box box-warning">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Teleporter Export</h3>
|
||||
<h3 class="box-title">Backup</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<p>Export your Pi-hole configuration (settings & lists) as a downloadable archive</p>
|
||||
<button type="submit" class="btn btn-default">Export</button>
|
||||
<p>Backup your Pi-hole configuration (settings & lists) as a downloadable archive</p>
|
||||
<button type="submit" class="btn btn-default">Backup</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1088,12 +1088,16 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<div class="box box-warning">
|
||||
<div class="box-header with-border">
|
||||
<h3 class="box-title">Teleporter Import</h3>
|
||||
<h3 class="box-title">Restore</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12">
|
||||
<label>Import...</label>
|
||||
<div class="col-lg-12">
|
||||
<label>Restore...</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="whitelist" value="true"
|
||||
@@ -1118,27 +1122,48 @@ if (isset($_GET['tab']) && in_array($_GET['tab'], array("sysadmin", "blocklists"
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="adlist" value="true"
|
||||
checked>
|
||||
Blocklists</label>
|
||||
Adlists</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="client" value="true"
|
||||
checked>
|
||||
Clients</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="group" value="true"
|
||||
checked>
|
||||
Groups</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="auditlog" value="true"
|
||||
checked>
|
||||
checked>
|
||||
Audit log</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="staticdhcpleases" value="true"
|
||||
checked>
|
||||
checked>
|
||||
Static DHCP Leases</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="localdnsrecords" value="true"
|
||||
checked>
|
||||
Local DNS Records</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 col-md-12">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="form-group">
|
||||
<label for="zip_file">File input</label>
|
||||
<input type="file" name="zip_file" id="zip_file">
|
||||
<p class="help-block">Upload only Pi-hole backup files.</p>
|
||||
<button type="submit" class="btn btn-default" name="action"
|
||||
value="in">Import
|
||||
value="in">Restore
|
||||
</button>
|
||||
<div class="checkbox">
|
||||
<label><input type="checkbox" name="flushtables" value="true"
|
||||
|
||||
Reference in New Issue
Block a user