Skip to content

Commit

Permalink
refine User installed OS packages part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
darylc committed Jan 4, 2025
1 parent 2b4f0cc commit 85dd7bd
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 104 deletions.
7 changes: 4 additions & 3 deletions www/api/controllers/system.php
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,10 @@ function GetOSPackageInfo() {

// Fetch package information using apt-cache show
$output = shell_exec("apt-cache show " . escapeshellarg($packageName) . " 2>&1");

if (!$output) {
return ['error' => "Package '$packageName' not found or no information available."];
if (!$output || strpos($output, 'E:') === 0) {
// Return error if apt-cache output is empty or contains an error
error_log("Package '$packageName' not found or invalid: $output");
return json_encode(['error' => "Package '$packageName' not found or no information available."]);
}

// Check installation status using dpkg-query
Expand Down
198 changes: 97 additions & 101 deletions www/packages.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en">

<head>
<?php
if (isset($_REQUEST['action'])) {
$skipJSsettings = 1;
}
require_once('config.php');
require_once('common.php');
DisableOutputBuffering();
include('common/menuHead.inc');

$userPackagesFile = $settings['configDirectory'] . '/userpackages.json';
$userPackages = [];
Expand All @@ -15,56 +16,6 @@
}
}

// Handle backend actions
$action = $_POST['action'] ?? $_GET['action'] ?? null;
$packageName = $_POST['package'] ?? $_GET['package'] ?? null;

if ($action) {
if ($action === 'install' && !empty($packageName)) {
$packageName = escapeshellarg($packageName);
header('Content-Type: text/plain');

$process = popen("sudo apt-get install -y $packageName 2>&1", 'r');
if (is_resource($process)) {
while (!feof($process)) {
echo fread($process, 1024);
flush();
}
pclose($process);
}

// Add package to user-installed packages if not already added
if (!in_array(trim($packageName, "'"), $userPackages)) {
$userPackages[] = trim($packageName, "'");
file_put_contents($userPackagesFile, json_encode($userPackages, JSON_PRETTY_PRINT));
}

exit;
}

if ($action === 'uninstall' && !empty($packageName)) {
$packageName = escapeshellarg($packageName);
header('Content-Type: text/plain');

$process = popen("sudo apt-get remove -y $packageName 2>&1", 'r');
if (is_resource($process)) {
while (!feof($process)) {
echo fread($process, 1024);
flush();
}
pclose($process);
}

// Remove package from user-installed packages
$userPackages = array_filter($userPackages, function($pkg) use ($packageName) {
return $pkg !== trim($packageName, "'");
});
file_put_contents($userPackagesFile, json_encode($userPackages, JSON_PRETTY_PRINT));

exit;
}
}
include 'common/menuHead.inc';
writeFPPVersionJavascriptFunctions();
?>
<style>
Expand Down Expand Up @@ -106,9 +57,7 @@ function GetSystemPackages() {
alert('Error: Unable to retrieve package list.');
return;
}
console.log('Raw Data Received:', data);
systemPackages = data;
console.log('Parsed Packages:', systemPackages);
InitializeAutocomplete();
HideLoadingIndicator();
},
Expand All @@ -119,30 +68,102 @@ function GetSystemPackages() {
});
}

function InitializeAutocomplete() {
if (!systemPackages.length) {
console.warn('System packages list is empty.');
return;
}

$("#packageInput").autocomplete({
source: systemPackages,
select: function (event, ui) {
const selectedPackage = ui.item.value;
$(this).val(selectedPackage);
return false;
}
});
}

function UpdateUserPackagesList() {
if (!userInstalledPackages.length) {
$('#userPackagesList').html('<p>No user-installed packages found.</p>');
return;
}

let userPackagesListHTML = '';
let pendingRequests = userInstalledPackages.length;

userInstalledPackages.forEach(pkg => {
$.ajax({
url: `/api/system/packages/info/${encodeURIComponent(pkg)}`,
type: 'GET',
dataType: 'json',
success: function (data) {
const isInstalled = data.Installed === 'Yes';
userPackagesListHTML += `<li>${pkg}
${isInstalled ? `
<button class='btn btn-sm btn-outline-danger' onClick='UninstallPackage("${pkg}")'>
Uninstall
</button>`
: `
<button class='btn btn-sm btn-outline-warning' onClick='InstallPackage("${pkg}")'>
Reinstall Required
</button>
<button class='btn btn-sm btn-outline-danger ms-2' onClick='UninstallPackage("${pkg}")'>
Remove
</button>`}
</li>`;
},
error: function () {
console.error(`Error checking installation status for package: ${pkg}`);
userPackagesListHTML += `<li>${pkg}
<button class='btn btn-sm btn-outline-warning' onClick='InstallPackage("${pkg}")'>
Reinstall Required
</button>
</li>`;
},
complete: function () {
pendingRequests--;
if (pendingRequests === 0) {
$('#userPackagesList').html(userPackagesListHTML);
}
}
});
});
}

function GetPackageInfo(packageName) {
selectedPackageName = packageName;
if (!packageName.trim()) {
alert("Please enter a valid package name.");
return;
}

selectedPackageName = packageName.trim();
$.ajax({
url: `/api/system/packages/info/${encodeURIComponent(packageName)}`,
url: `/api/system/packages/info/${encodeURIComponent(selectedPackageName)}`,
type: 'GET',
dataType: 'json',
success: function (data) {
if (data.error) {
alert(`Error: ${data.error}`);
$('#packageInfo').html(`<strong>Error:</strong> ${data.error}`);
return;
}

const description = data.Description || 'No description available.';
const dependencies = data.Depends
? data.Depends.replace(/\([^)]*\)/g, '').trim()
: 'No dependencies.';
const installed = data.Installed === "Yes" ? "(Already Installed)" : "";

$('#packageInfo').html(`
<strong>Selected Package:</strong> ${packageName} ${installed}<br>
${data.Installed !== "Yes" ?`
<strong>Description:</strong> ${description}<br>
<strong>Will also install these packages:</strong> ${dependencies}<br>
<div class="buttons btn-lg btn-rounded btn-outline-success mt-2" onClick='InstallPackage();'>
<i class="fas fa-download"></i> Install Package
</div>` : ""}
<strong>Selected Package:</strong> ${selectedPackageName} ${installed}<br>
${data.Installed !== "Yes"
? `<strong>Description:</strong> ${description}<br>
<strong>Will also install these packages:</strong> ${dependencies}<br>
<div class="buttons btn-lg btn-rounded btn-outline-success mt-2" onClick="InstallPackage('${selectedPackageName}');">
<i class="fas fa-download"></i> Install Package
</div>`
: ""}
`);
},
error: function () {
Expand All @@ -151,31 +172,15 @@ function GetPackageInfo(packageName) {
});
}

function InitializeAutocomplete() {
if (!systemPackages.length) {
console.warn('System packages list is empty.');
return;
}

$("#packageInput").autocomplete({
source: systemPackages,
select: function (event, ui) {
const selectedPackage = ui.item.value;
$(this).val(selectedPackage);
return false;
}
});
}

function InstallPackage() {
if (!selectedPackageName) {
alert('Please select a package and retrieve its info before installing.');
function InstallPackage(packageName) {
if (!packageName) {
alert('Invalid package name.');
return;
}

const url = `packages.php?action=install&package=${encodeURIComponent(selectedPackageName)}`;
const url = `packagesHelper.php?action=install&package=${encodeURIComponent(packageName)}`;
$('#packageProgressPopupCloseButton').text('Please Wait').prop("disabled", true);
DisplayProgressDialog("packageProgressPopup", `Installing Package: ${selectedPackageName}`);
DisplayProgressDialog("packageProgressPopup", `Installing Package: ${packageName}`);
StreamURL(
url,
'packageProgressPopupText',
Expand All @@ -185,7 +190,7 @@ function InstallPackage() {
}

function UninstallPackage(packageName) {
const url = `packages.php?action=uninstall&package=${encodeURIComponent(packageName)}`;
const url = `packagesHelper.php?action=uninstall&package=${encodeURIComponent(packageName)}`;
$('#packageProgressPopupCloseButton').text('Please Wait').prop("disabled", true);
DisplayProgressDialog("packageProgressPopup", `Uninstalling Package: ${packageName}`);
StreamURL(
Expand All @@ -208,16 +213,7 @@ function EnableCloseButtonAfterOperation(id) {

$(document).ready(function () {
GetSystemPackages();

const userPackagesList = userInstalledPackages.map(pkg => `<li>${pkg} <button class='btn btn-sm btn-outline-danger' onClick='UninstallPackage("${pkg}");'>Uninstall</button></li>`).join('');
$('#userPackagesList').html(userPackagesList || '<p>No user-installed packages found.</p>');

$('#packageInput').on('input', function () {
const packageName = $(this).val().trim();
if (packageName) {
$('#packageStatus').text('');
}
});
UpdateUserPackagesList();
});
</script>
<title>Package Manager</title>
Expand All @@ -233,9 +229,9 @@ function EnableCloseButtonAfterOperation(id) {
<div class="pageContent">
<div id="packages" class="settings">


<h2>Please Note:</h2>
Installing additional packages can break your FPP installation requiring complete reinstallation of FPP. Continue at your own risk.
Installing additional packages can break your FPP installation requiring complete reinstallation of FPP. Continue at your own risk.
<p>
<h2>Installed User Packages</h2>
<ul id="userPackagesList"></ul>
Expand All @@ -244,7 +240,7 @@ function EnableCloseButtonAfterOperation(id) {
<p>Loading package list, please wait...</p>
</div>

<div id="packageInputContainer" style="display: none;">
<div id="packageInputContainer">
<div class="row">
<div class="col">
<input type="text" id="packageInput" class="form-control form-control-lg form-control-rounded has-shadow" placeholder="Enter package name" />
Expand Down
71 changes: 71 additions & 0 deletions www/packagesHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php
$skipJSsettings = 1;
require_once "config.php";
require_once "common.php";
DisableOutputBuffering();

$userPackagesFile = $settings['configDirectory'] . '/userpackages.json';
$userPackages = [];
if (file_exists($userPackagesFile)) {
$userPackages = json_decode(file_get_contents($userPackagesFile), true);
if (!is_array($userPackages)) {
$userPackages = array_values((array)$userPackages); // Convert objects to array of values if needed
}
}

// Handle backend actions
$action = $_POST['action'] ?? $_GET['action'] ?? null;
$packageName = $_POST['package'] ?? $_GET['package'] ?? null;

if ($action) {
if ($action === 'install' && !empty($packageName)) {
$packageName = escapeshellarg($packageName);
header('Content-Type: text/plain');

$process = popen("sudo apt-get update;sudo apt-get install -y $packageName 2>&1", 'r');
if (is_resource($process)) {
while (!feof($process)) {
echo fread($process, 1024);
flush();
}
pclose($process);
}

// Add package to user-installed packages if not already added
$packageName = trim($packageName, "'");
if (!in_array($packageName, $userPackages)) {
$userPackages[] = $packageName;
file_put_contents($userPackagesFile, json_encode($userPackages, JSON_PRETTY_PRINT));
}

exit;
}

if ($action === 'uninstall' && !empty($packageName)) {
$packageName = escapeshellarg($packageName);
header('Content-Type: text/plain');

$process = popen("sudo apt-get remove -y $packageName 2>&1", 'r');
if (is_resource($process)) {
while (!feof($process)) {
echo fread($process, 1024);
flush();
}
pclose($process);
}

// Remove package from user-installed packages
$packageName = trim($packageName, "'");
$userPackages = array_filter($userPackages, function ($pkg) use ($packageName) {
return $pkg !== $packageName;
});
file_put_contents($userPackagesFile, json_encode(array_values($userPackages), JSON_PRETTY_PRINT));

exit;
}
}

header('Content-Type: application/json');
echo json_encode($userPackages);


0 comments on commit 85dd7bd

Please sign in to comment.