New plugins screen

This commit is contained in:
Dan Cryer 2013-10-08 16:28:46 +01:00
parent edc4fc1b1b
commit 160097182f
4 changed files with 358 additions and 2 deletions

View file

@ -0,0 +1,161 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use PHPCI\Model\Build;
/**
* Plugin Controller - Provides support for installing Composer packages.
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Web
*/
class PluginController extends \PHPCI\Controller
{
protected $required = array(
'block8/b8framework',
'ircmaxell/password-compat',
'swiftmailer/swiftmailer',
'symfony/yaml',
'symfony/console'
);
protected $canInstall;
protected $composerPath;
public function init()
{
parent::init();
$this->canInstall = function_exists('shell_exec');
if ($this->canInstall) {
$this->composerPath = $this->findBinary(array('composer', 'composer.phar'));
if (!$this->composerPath) {
$this->canInstall = false;
}
}
}
public function index()
{
$this->view->canWrite = is_writable(APPLICATION_PATH . 'composer.json');
$this->view->canInstall = $this->canInstall;
$this->view->required = $this->required;
$json = $this->getComposerJson();
$this->view->installed = $json['require'];
$this->view->suggested = $json['suggest'];
return $this->view->render();
}
public function remove()
{
$package = $this->getParam('package', null);
$json = $this->getComposerJson();
if (!in_array($package, $this->required)) {
unset($json['require'][$package]);
$this->setComposerJson($json);
if ($this->canInstall) {
$res = shell_exec($this->composerPath . ' update --working-dir=' . APPLICATION_PATH . ' > /dev/null 2>&1 &');
}
header('Location: ' . PHPCI_URL . 'plugin?r=' . $package);
die;
}
header('Location: ' . PHPCI_URL);
die;
}
public function install()
{
$package = $this->getParam('package', null);
$version = $this->getParam('version', '*');
$json = $this->getComposerJson();
$json['require'][$package] = $version;
$this->setComposerJson($json);
if ($this->canInstall) {
$res = shell_exec($this->composerPath . ' update --working-dir=' . APPLICATION_PATH . ' > /dev/null 2>&1 &');
header('Location: ' . PHPCI_URL . 'plugin?i=' . $package);
die;
}
header('Location: ' . PHPCI_URL . 'plugin?w=' . $package);
die;
}
protected function getComposerJson()
{
$json = file_get_contents(APPLICATION_PATH . 'composer.json');
return json_decode($json, true);
}
protected function setComposerJson($array)
{
$json = json_encode($array);
file_put_contents(APPLICATION_PATH . 'composer.json', $json);
}
protected function findBinary($binary)
{
if (is_string($binary)) {
$binary = array($binary);
}
foreach ($binary as $bin) {
// Check project root directory:
if (is_file(APPLICATION_PATH . $bin)) {
return APPLICATION_PATH . $bin;
}
// Check Composer bin dir:
if (is_file(APPLICATION_PATH . 'vendor/bin/' . $bin)) {
return APPLICATION_PATH . 'vendor/bin/' . $bin;
}
// Use "which"
$which = trim(shell_exec('which ' . $bin));
if (!empty($which)) {
return $which;
}
}
return null;
}
public function packagistSearch()
{
$q = $this->getParam('q', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+http://www.phptesting.org)'));
$res = $http->get('https://packagist.org/search.json', array('q' => $q));
die(json_encode($res['body']));
}
public function packagistVersions()
{
$name = $this->getParam('p', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+http://www.phptesting.org)'));
$res = $http->get('https://packagist.org/packages/'.$name.'.json');
die(json_encode($res['body']));
}
}

View file

@ -5,7 +5,8 @@
<div class="col-lg-3">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="<?= PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="<?= PHPCI_URL ?>user"><i class="icon-user"></i> Users</a></li>
<li><a href="<?= PHPCI_URL ?>user"><i class="icon-user"></i> Users</a></li>
<li><a href="<?= PHPCI_URL ?>plugin"><i class="icon-user"></i> Manage Plugins</a></li>
</ul>
<?php if (count($projects)): ?>

View file

@ -0,0 +1,180 @@
<h1 id="title">Plugins</h1>
<?php if (!$canInstall): ?>
<p class="alert alert-danger">PHPCI cannot automatically install/remove plugins for you, as the <strong>shell_exec()</strong>
function is disabled. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.</p>
<?php endif; ?>
<?php if (!$canWrite): ?>
<p class="alert alert-danger">PHPCI cannot update composer.json for you as it is not writable.</p>
<?php endif; ?>
<?php if (isset($_GET['r'])): ?>
<p class="alert alert-success"><strong><?= $_GET['r']; ?></strong> has been removed.</p>
<?php endif; ?>
<?php if (isset($_GET['i'])): ?>
<p class="alert alert-success"><strong><?= $_GET['i']; ?></strong> has been installed.</p>
<?php endif; ?>
<?php if (isset($_GET['w'])): ?>
<p class="alert alert-success"><strong><?= $_GET['w']; ?></strong> has been added to composer.json and will be installed next time you run composer update.</p>
<?php endif; ?>
<div class="box">
<h3 class="title">Installed Plugins</h3>
<table class="table-striped table-bordered table">
<thead>
<tr>
<th>Title</th>
<th>Version</th>
<th width="1"></th>
</tr>
</thead>
<tbody>
<?php foreach ($installed as $package => $version): ?>
<tr>
<td><?= $package; ?></td>
<td><?= $version; ?></td>
<td>
<?php if (!in_array($package, $required) && $canWrite): ?>
<a class="btn btn-danger btn-small" href="<?= PHPCI_URL ?>plugin/remove?package=<?= $package; ?>">Remove &raquo;</a>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="box">
<h3 class="title">Suggested Plugins</h3>
<table class="table-striped table-bordered table">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th width="1"></th>
</tr>
</thead>
<tbody>
<?php foreach ($suggested as $package => $version): ?>
<?php if (in_array($package, array_keys($installed))) { continue; } ?>
<tr>
<td><?= $package; ?></td>
<td><?= $version; ?></td>
<td>
<?php if ($canWrite): ?>
<button data-name="<?= $package; ?>" class="install-package btn btn-success btn-small">Install &raquo;</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="box">
<h3 class="title">Search Packagist for More Plugins</h3>
<div class="input-group">
<input id="search-query" type="text" class="form-control">
<span class="input-group-btn">
<button id="search-button" class="btn btn-success" type="button">Search</button>
</span>
</div>
<div id="results" style="margin-top: 15px; display: none;">
<table class="table-striped table-bordered table">
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th width="1"></th>
</tr>
</thead>
<tbody id="search-results">
</tbody>
</table>
</div>
</div>
<script>
var canWrite = <?php print $canWrite ? 'true' : 'false'; ?>;
$('#search-button').on('click', function() {
$('#loading').show();
$.getJSON('<?php print PHPCI_URL; ?>plugin/packagist-search', {q: $('#search-query').val()}, function(data) {
$('#loading').hide();
$('#results').show();
var results = $('#search-results').empty();
for (var i in data.results) {
var thisRes = data.results[i];
var thisRow = $('<tr></tr>');
thisRow.append($('<td></td>').text(thisRes.name));
thisRow.append($('<td></td>').text(thisRes.description));
var inst = $('<button></button>').data('name', thisRes.name).addClass('btn btn-small btn-success').html('Install &raquo;');
inst.on('click', versionChooser);
thisRow.append($('<td></td>').append(inst));
results.append(thisRow);
}
});
});
$('.install-package').on('click', versionChooser);
function versionChooser()
{
$('#loading').show();
var thisName = $(this).data('name');
$.getJSON('<?php print PHPCI_URL; ?>plugin/packagist-versions', {p: thisName}, function(data) {
var versions = data.package.versions;
var vkeys = Object.keys(versions).sort().reverse();
$('#version-list ul').empty();
for (var i in vkeys) {
var url = '<?php print PHPCI_URL; ?>plugin/install?package=' + thisName + '&version=' + vkeys[i];
var li = $('<li></li>');
var a = $('<a></a>').attr('href', url).text(vkeys[i]);
li.append(a);
$('#version-list ul').append(li);
}
$('#version-list').modal('show');
$('#loading').hide();
});
}
</script>
<div class="modal fade" id="version-list">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Choose a version...</h4>
</div>
<div class="modal-body">
<ul></ul>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div id="loading">Loading...</div>

View file

@ -127,4 +127,18 @@ h3
.ui-sortable-placeholder * { visibility: hidden; }
.ui-plugin { padding-top: 15px; }
.ui-plugin { padding-top: 15px; }
#loading {
font-family: Roboto, Arial, Sans-Serif;
background: #369;
color: #fff;
display: none;
position: fixed;
bottom: 20px;
font-size: 2em;
right: 20px;
padding: 15px 50px;
}