define('PERMISSION_SPAMFILTER_DEL', 'sf_del');
/** Can rehash servers */
define('PERMISSION_REHASH', 'rhs');
+/** Can install and uninstall plugins */
+define('PERMISSION_MANAGE_PLUGINS', 'mng_plg');
/**
* PanelUser
* This is the User class for the SQL_Auth plugin
{
$list = [
"Can add/delete/edit Admin Panel users" => PERMISSION_MANAGE_USERS,
+ "Can add/delete/manage plugins" => PERMISSION_MANAGE_PLUGINS,
"Can ban/kill IRC users" => PERMISSION_BAN_USERS,
"Can change properties of a user, i.e. vhost, modes and more" => PERMISSION_EDIT_USER,
"Can change properties of a channel, i.e. topic, modes and more" => PERMISSION_EDIT_CHANNEL,
"Can remove server ban exceptions" => PERMISSION_BAN_EXCEPTION_DEL,
"Can add Spamfilter entries" => PERMISSION_SPAMFILTER_ADD,
"Can remove Spamfilter entries" => PERMISSION_SPAMFILTER_DEL
+
];
Hook::run(HOOKTYPE_USER_PERMISSION_LIST, $list); // so plugin writers can add their own permissions
return $list;
class PluginRepo
{
- public $plugins;
+ public $plugins ;
public $data;
public $err;
function __construct($url = DEFAULT_PLUGINS_DIR)
{
+ global $config;
+ if (!isset($config['third-party-plugins']))
+ {
+ $config['third-party-plugins']['data'] = NULL;
+ $config['third-party-plugins']['timestamp'] = 0;
+ }
+ if (time() - $config['third-party-plugins']['timestamp'] > 200) // Cache for 3.333 minutes lol
+ {
+ // come simba it is taem
+ $curl = curl_init($url);
-
- // Initialize curl
- $curl = curl_init($url);
-
- // Set the options
- curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // Return the response instead of printing it
- curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); // Set the content type to JSON
- curl_setopt($curl, CURLOPT_USERAGENT, "UnrealIRCd Admin Panel");
- // Execute the request
- $response = curl_exec($curl);
-
- // Check for errors
- if ($response === false)
- $this->err = curl_error($curl);
- else
- $this->data = json_decode($response, false);
+ // Set the options
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // Return the response instead of printing it
+ curl_setopt($curl, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); // Set the content type to JSON
+ curl_setopt($curl, CURLOPT_USERAGENT, "UnrealIRCd Admin Panel"); // This is Secret Agent UnrealIRCd Admin Panel reporting for doody
+ // Execute the request
+ $response = curl_exec($curl);
+
+ // Check for errors
+ if ($response === false)
+ $this->err = curl_error($curl);
+ else
+ {
+ $this->data = json_decode($response, false);
+ $config['third-party-plugins']['data'] = $this->data;
+ $config['third-party-plugins']['timestamp'] = time();
+ write_config('third-party-plugins');
+ }
+ }
+ else
+ $this->data = $config['third-party-plugins']['data'];
+
}
{
if ($installed)
{ ?>
- <div class="badge rounded-pill badge-success">Installed ✅</div>
+ <div style="margin-left:40px;" class="badge rounded-pill badge-success">✔ Installed</div>
<?php }
else if (Plugins::plugin_exists($name))
{
?>
- <div class="badge rounded-pill badge-success">Installed ✅</div>
+ <div style="margin-left:40px;" class="badge rounded-pill badge-success">✔ Installed</div>
<?php
}
}
public function do_list()
{
- ?>
-<div class="row">
- <?php
+ global $config;
+ if ($this->err)
+ die("Could not fetch list.\n");
+ global $config;
+ ?>
+ <div class="row">
+ <?php
$counter = 0;
foreach($this->data as $p)
{
- $installed = Plugins::plugin_exists($p->name) ? true : false;
+ $installed = in_array($p->name, $config['plugins']) ? true : false;
if (is_string($p))
continue;
// use a default image if there was none
$p->icon = $p->icon ?? get_config("base_url")."img/no-image-available.jpg";
?>
- <!-- Widget for plugins -->
- <div id="<?php echo $p->name ?>" class="<?php if ($installed) echo "installed " ?>card text-dark bg-light ml-4 mb-3 w-25">
- <div class="card-header">
- <div class="font-weight-bold">
- <div><h5><img class="mr-3" src="<?php echo $p->icon ?>" height="50" width="55">
- <?php echo $p->title; $this->ifInstalledLabel($p->name); ?></h5></div>
+ <!-- Widget for plugins -->
+ <div id="<?php echo $p->name ?>" class="<?php if ($installed) echo "installed" ?> plugin-card card text-dark bg-light ml-4 mb-3 w-25" style="min-width:300px">
+
+ <!-- Card header -->
+ <div class="card-header">
+ <div class="media">
+ <img class="align-self-start mr-3" src="<?php echo $p->icon ?>" height="50" width="55">
+ <div class="media-body">
+ <div style="position:relative;float:inline-end"><?php echo $this->ifInstalledLabel($p->name); ?></div>
+ <h4 class="mb-0 mt-0"><?php echo $p->title ?></h4>
+ <small>By <a href="<?php echo "mailto:$p->email" ?>" target="_blank"><?php echo $p->author ?></a></small>
+ </div>
+ </div>
</div>
- </div>
- <div class="card-body">
- <h5 class="card-title"><?php echo $p->title ?> <small><code>v<?php echo $p->version ?></code></small></h5>
- <p class="card-text"><?php echo $p->description ?> </p>
- </div>
- <div class="card-footer d-flex justify-content-between align-items-center">
- <ul class="list-unstyled">
- <li class="list-item">Author: <a href="<?php echo "mailto:".$p->contact ?? "#" ?>"></li><i><?php echo $p->author ?></i></a>
- </ul>
- <div class="btn-group">
- <div class="btn btn-secondary">More Info</div>
- <div id="<?php echo $p->name ?>install" class="btn btn-primary">Install</div>
- </div></div>
-
- </div>
- <?php
- $counter++;
- if ($counter >= 3) // only do three per row. WARNING: untested due to not having more than 2 plugins atm...
- {
- ?>
+
+ <!-- Card body -->
+ <div class="card-body">
+ <h6 class="card-title"><?php echo $p->title ?> <small><code>v<?php echo $p->version ?></code></small></h6>
+ <p class="card-text"><?php echo $p->description ?> </p>
</div>
- <div class="row"><?php
- $counter = 0;
- }
+
+ <!-- Card footer -->
+ <div class="card-footer d-flex justify-content-between align-items-center">
+ <ul class="list-unstyled">
+ <li class="list-item"><i><a href="<?php echo "mailto:".$p->contact ?? "#" ?>">By <?php echo $p->author ?></i></a></li>
+ </ul>
+ <div>
+ <div id="<?php echo $p->name ?>" class="more-info btn btn-info">More Info</div>
+ <div id="<?php echo $p->name ?>install" class="btn-install-plugin btn btn-primary">Install</div>
+ </div>
+ </div>
+ </div>
+ <?php
+ /** only do three per row.
+ * WARNING: untested due to not having more than 2 plugins atm...
+ */
+ if ($counter >= 3)
+ {
+ ?><!-- New row please mr html -->
+ </div>
+ <div class="row">
+ <?php
+ $counter = 0;
+ }
+ $counter++;
- }
- ?></div>
+ }
+ ?>
+ </div>
+ <i>Want to see your plugin listed here? <a href="https://github.com/unrealircd/unrealircd-webpanel-plugins" target="__blank">Make a pull request to our GitHub Repository</a>!</i>
<?php
}
}
--- /dev/null
+<?php
+
+session_start();
+if (!isset($_SESSION['id']))
+ die("Access denied");
+require_once('common_api.php');
+
+header("Content-type: application/json; charset=utf-8");
+
+if (!current_user_can(PERMISSION_MANAGE_PLUGINS))
+ die(json_encode(['error' => "Access denied"]));
+if (empty($_GET))
+ die(json_encode($config['third-party-plugins']['data']));
+
+elseif(isset($_GET['install']))
+{
+ install_plugin($_GET['install']);
+}
+elseif (isset($_GET['uninstall']))
+{
+ uninstall_plugin($_GET['uninstall']);
+}
+
+function uninstall_plugin($name)
+{
+ global $config;
+ if (!Plugins::plugin_exists($name))
+ die(json_encode(['error' => "Plugin not loaded"]));
+
+ foreach($config['plugins'] as $k => $v)
+ if ($v == $name)
+ unset($config['plugins'][$k]);
+ write_config();
+
+ deleteDirectory(UPATH."/plugins/$name");
+ die(json_encode(["success" => "Plugin was deleted successfully"]));
+}
+
+/**Attempt to install the plugin
+ * @param string $name name of the plugin
+ * @return void
+ */
+function install_plugin($name)
+{
+ global $config;
+ if (in_array($name, $config['plugins']))
+ die(json_encode(["error" => "Plugin already installed"]));
+ $url = get_plugin_install_path_from_name($name);
+ $pluginfile = file_get_contents($url);
+ if (!is_dir(UPATH."/data/tmp"))
+ mkdir(UPATH."/data/tmp");
+
+ $path = UPATH."/data/tmp/";
+ $file = $path.md5(time()).".tmp";
+ if (!file_put_contents($file, $pluginfile))
+ die(json_encode(["error" => "Cannot write to directory: Need write permission"]));
+
+ unset($pluginfile);
+
+ $zip = new ZipArchive;
+ $res = $zip->open($file);
+ if ($res !== true) {
+ unlink($file);
+ die(json_encode(["error" => "Could not open file we just wrote lol"]));
+ }
+
+ // ensure we have no conflicts
+ $extractPath = UPATH."/plugins/$name";
+ // lazy upgrade for now.
+ if (is_dir($extractPath))
+ {
+ deleteDirectory($extractPath);
+ }
+ mkdir($extractPath);
+ $zip->extractTo($extractPath);
+ $zip->close();
+
+ //clear up our temp shit
+ unset($zip);
+ unlink($file);
+ unset($res);
+
+ // load it in the config
+ $config['plugins'][] = $name;
+ write_config();
+
+ // wahey
+ die(json_encode(['success' => "Installation was complete"]));
+}
+
+/**
+ * @param string $name Name of plugin
+ * @return NULL|string Path or NULL
+ */
+function get_plugin_install_path_from_name($name)
+{
+ global $config;
+ foreach($config['third-party-plugins']['data'] as $p)
+ {
+ if (!strcmp($p->name,$name))
+ return $p->download_link;
+ }
+ return NULL;
+}
+
+function deleteDirectory($dir) {
+ if (!file_exists($dir)) {
+ return true;
+ }
+
+ if (!is_dir($dir)) {
+ return unlink($dir);
+ }
+
+ foreach (scandir($dir) as $item) {
+ if ($item == '.' || $item == '..') {
+ continue;
+ }
+
+ if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
+ return false;
+ }
+
+ }
+
+ return rmdir($dir);
+}
\ No newline at end of file
/**
* The version of our webpanel
*/
-define('WEBPANEL_VERSION', "1.0-git");
+define('WEBPANEL_VERSION', "0.9-git");
require_once "../inc/header.php";
require_once "../Classes/class-plugin-git.php";
+if (!current_user_can(PERMISSION_MANAGE_PLUGINS))
+ die("Access denied");
+
$p = new PluginRepo();
?>
?>
-<!-- Remove when page is finished -->
<script>
-const modal = bsModal("Important", "This is a work in progress.<br><br>Please do not expect anything to work properly on this page",
-"<div class=\"btn btn-primary\" onClick=\"$('#'+modal).modal('hide')\">Ok</div>", size = null, static = true, show = true, closebutton = true);
-</script>
\ No newline at end of file
+
+ const ibtns = document.querySelectorAll(".btn-install-plugin");
+ ibtns.forEach((ib) => {
+ ib.addEventListener('click', (e) => {
+ console.log("Button clicked! " +ib.innerHTML);
+ if (ib.innerHTML !== "Install" && ib.innerHTML !== "Uninstall") // some point between, don't do anything
+ {}
+ else if (ib.innerHTML == "Install") // install button pressed!
+ {
+ let req = requestInstall(ib.id.slice(0,-7))
+ if (req == true)
+ {
+ ib.classList.replace("btn-primary", "btn-secondary");
+ ib.innerHTML = "Installing...";
+ }
+ else
+ {
+ let uhoh = new bsModal("Error", "Could not install: "+req, "", null, false, true);
+ }
+ }
+ else if (ib.innerHTML == "Uninstall")
+ {
+ let req = requestInstall(ib.id.slice(0,-7), true); // true = uninstall
+ if (req == true)
+ {
+ ib.classList.replace("btn-outline-danger", "btn-secondary");
+ ib.innerHTML = "Uninstalling...";
+ }
+ else
+ {
+ let uhoh = new bsModal("Error", "Could not uninstall: "+req, "", null, false, true);
+ }
+ }
+ });
+ })
+ const installed = document.querySelectorAll(".installed");
+ installed.forEach((el) => {
+ let btn = document.getElementById(el.id + 'install');
+ btn.classList.replace("btn-primary", "btn-outline-danger");
+ btn.innerHTML = "Uninstall";
+ });
+
+ function requestInstall(name, uninstall = false)
+ {
+ let inst = (uninstall) ? "uninstall" : "install";
+ var xhr = new XMLHttpRequest();
+
+ xhr.onload = function() {
+ if (xhr.status === 200) {
+ var response = JSON.parse(xhr.responseText);
+ console.log(response.success);
+ let install_button = document.getElementById(name+'install');
+ if (response.success !== undefined)
+ {
+ if (install_button)
+ {
+ install_button.innerHTML = (inst == "uninstall") ? "Install" : "Uninstall";
+ install_button.classList.replace('btn-secondary', (inst == "uninstall") ? 'btn-primary' : 'btn-outline-danger');
+ let icomplete = bsModal(((inst == "uninstall") ? "Uninstall" : "Install") + " Plugin", response.success,"<div id=\""+name+"closebtn\" class=\"btn btn-danger\">Close</div>", null, true, true, false);
+ let closebtn = document.getElementById(name+"closebtn");
+ closebtn.addEventListener('click', e => {
+ location.reload();
+ });
+ }
+ }
+ else
+ {
+ if (install_button)
+ {
+ install_button.innerHTML = (inst == "uninstall") ? "Uninstall" : "Install";
+ install_button.classList.replace('btn-secondary', (inst == "uninstall") ? 'btn-outline-danger' : 'btn-primary');
+ let icomplete = bsModal(((inst == "uninstall") ? "Uninstall" : "Install") + " Plugin", response.error,"", null, false, true);
+ let closebtn = document.getElementById(name+"closebtn");
+ closebtn.addEventListener('click', e => {
+ location.reload();
+ });
+ }
+ }
+ }
+ };
+
+ xhr.open('GET', BASE_URL + 'api/plugin.php?'+inst+'=' + name, true);
+ xhr.send();
+ return true;
+ }
+
+ function create_info_modal(modname)
+ {
+ fetch('https://api.dalek.services/plugins.list')
+ .then(response => response.json()) // Parse the response as JSON
+ .then(data => {
+ for (let i = 0; data[i]; i++)
+ {
+ if (data[i].name == modname)
+ {
+ const modal = bsModal(
+ "<i>Information about " + data[i].title + "</i>", // title
+ "<div class=\"" + data[i].name + "_screenshots\"><i class=\"fa fa-spinner\" aria-hidden=\"true\"></i></div><div class=\"" + data[i].name + "_description\"><i class=\"fa fa-spinner\" aria-hidden=\"true\"></i></div>",
+ "<div id=\""+modname+"closebtn\" class=\"btn btn-danger\">Close</div>", null, true, true, false
+ );
+ let modalclose = document.getElementById(modal);
+ modalclose.addEventListener('click', (e) => {
+ $("#"+modal).modal('hide');
+ });
+ console.log(modal + '-body');
+ boobs = document.getElementById(modal + '-body');
+ boobs.innerHTML = data[i].description;
+ }
+ }
+ })
+ .catch(error => {
+ // Handle any errors that occur during the request
+ console.error('Error:', error);
+ });
+ }
+
+ const infoButtons = document.querySelectorAll('.more-info');
+ infoButtons.forEach((el) => {
+ el.addEventListener('click', (event) => {
+ create_info_modal(el.id);
+
+ });
+ });
+</script>