Big update.

This commit is contained in:
Dan Cryer 2013-05-10 12:28:43 +01:00
parent dd59bff838
commit 3647ac1cd8
26 changed files with 3158 additions and 8941 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
vendor/
composer.lock
config.php
.DS_Store

View file

@ -19,42 +19,49 @@ class Builder
public function __construct(Build $build)
{
$this->build = $build;
$this->store = Store\Factory::getStore('Build');
}
public function execute()
{
$this->build->setStatus(1);
$this->build = Store\Factory::getStore('Build')->save($this->build);
$this->build->setStarted(new \DateTime());
$this->build = $this->store->save($this->build);
if($this->setupBuild())
{
$this->executeEvent('prepare');
$this->executePlugins();
$this->executePlugins('setup');
$this->executePlugins('test');
$this->log('');
$this->executeEvent('on_complete');
$this->executePlugins('complete');
if($this->success)
{
$this->executeEvent('on_success');
$this->executePlugins('success');
$this->logSuccess('BUILD SUCCESSFUL!');
$this->build->setStatus(2);
}
else
{
$this->executeEvent('on_failure');
$this->executePlugins('failure');
$this->logFailure('BUILD FAILED!');
$this->build->setStatus(3);
}
$this->log('');
}
else
{
$this->build->setStatus(3);
}
$this->removeBuild();
$this->build->setFinished(new \DateTime());
$this->build->setLog($this->log);
Store\Factory::getStore('Build')->save($this->build);
$this->store->save($this->build);
}
public function executeCommand($command)
@ -94,6 +101,9 @@ class Builder
$this->log .= $message;
print $message;
}
$this->build->setLog($this->log);
$this->build = $this->store->save($this->build);
}
protected function logSuccess($message)
@ -106,52 +116,35 @@ class Builder
$this->log("\033[0;31m" . $message . "\033[0m");
}
protected function executeEvent($event)
{
$this->log('RUNNING '.strtoupper($event).' ACTIONS:');
if(!isset($this->config[$event]))
{
return;
}
if(is_string($this->config[$event]))
{
if(!$this->executeCommand($this->config[$event]))
{
$this->success = false;
}
return;
}
if(is_array($this->config[$event]))
{
foreach($this->config[$event] as $command)
{
if(!$this->executeCommand($command))
{
$this->success = false;
}
}
}
}
protected function setupBuild()
{
$commitId = $this->build->getCommitId();
$url = $this->build->getProject()->getGitUrl();
$key = $this->build->getProject()->getGitKey();
$buildId = 'project' . $this->build->getProject()->getId() . '-build' . $this->build->getId();
$this->ciDir = realpath(dirname(__FILE__) . '/../') . '/';
$this->buildPath = $this->ciDir . 'build/' . $commitId . '/';
$keyFile = $this->ciDir . 'build/' . $commitId . '.key';
$this->buildPath = $this->ciDir . 'build/' . $buildId . '/';
mkdir($this->buildPath, 0777, true);
file_put_contents($keyFile, $key);
chmod($keyFile, 0600);
$this->executeCommand('ssh-agent ssh-add '.$keyFile.' && git clone -b ' .$this->build->getBranch() . ' ' .$url.' '.$this->buildPath.' && ssh-agent -k');
unlink($keyFile);
if(!empty($key))
{
// Do an SSH clone:
$keyFile = $this->ciDir . 'build/' . $buildId . '.key';
file_put_contents($keyFile, $key);
chmod($keyFile, 0600);
$this->executeCommand('ssh-agent ssh-add '.$keyFile.' && git clone -b ' .$this->build->getBranch() . ' ' .$url.' '.$this->buildPath.' && ssh-agent -k');
unlink($keyFile);
}
else
{
// Do an HTTP clone:
$this->executeCommand('git clone -b ' .$this->build->getBranch() . ' ' .$url.' '.$this->buildPath);
}
file_put_contents($this->buildPath . 'phpci.yml', file_get_contents('/www/b8framework/phpci.yml'));
if(!is_file($this->buildPath . 'phpci.yml'))
{
@ -186,15 +179,15 @@ class Builder
shell_exec('rm -Rf ' . $this->buildPath);
}
protected function executePlugins()
protected function executePlugins($stage)
{
foreach($this->config['plugins'] as $plugin => $options)
foreach($this->config[$stage] as $plugin => $options)
{
$this->log('');
$this->log('RUNNING PLUGIN: ' . $plugin);
// Is this plugin allowed to fail?
if(!isset($options['allow_failures']))
if($stage == 'test' && !isset($options['allow_failures']))
{
$options['allow_failures'] = false;
}
@ -207,7 +200,7 @@ class Builder
{
$this->logFailure('Plugin does not exist: ' . $plugin);
if(!$options['allow_failures'])
if($stage == 'test' && !$options['allow_failures'])
{
$this->success = false;
}
@ -221,7 +214,7 @@ class Builder
if(!$plugin->execute())
{
if(!$options['allow_failures'])
if($stage == 'test' && !$options['allow_failures'])
{
$this->success = false;
}
@ -234,7 +227,7 @@ class Builder
{
$this->logFailure('EXCEPTION: ' . $ex->getMessage());
if(!$options['allow_failures'])
if($stage == 'test' && !$options['allow_failures'])
{
$this->success = false;
continue;

View file

@ -0,0 +1,50 @@
<?php
namespace PHPCI\Controller;
use b8,
b8\Store,
PHPCI\Model\Build;
class BitbucketController extends b8\Controller
{
public function init()
{
$this->_buildStore = Store\Factory::getStore('Build');
}
public function webhook($project)
{
$payload = json_decode($this->getParam('payload'), true);
try
{
$build = new Build();
$build->setProjectId($project);
$build->setCommitId($payload['after']);
$build->setStatus(0);
$build->setLog('');
$build->setCreated(new \DateTime());
$build->setBranch(str_replace('refs/heads/', '', $payload['ref']));
}
catch(\Exception $ex)
{
header('HTTP/1.1 400 Bad Request');
header('Ex: ' . $ex->getMessage());
die('FAIL');
}
try
{
$this->_buildStore->save($build);
}
catch(\Exception $ex)
{
header('HTTP/1.1 500 Internal Server Error');
header('Ex: ' . $ex->getMessage());
die('FAIL');
}
die('OK');
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace PHPCI\Controller;
use b8,
PHPCI\Model\Build;
class BuildController extends b8\Controller
{
public function init()
{
$this->_buildStore = b8\Store\Factory::getStore('Build');
}
public function view($buildId)
{
$build = $this->_buildStore->getById($buildId);
$view = new b8\View('Build');
$view->build = $build;
$view->data = $this->getBuildData($buildId);
return $view->render();
}
public function data($buildId)
{
die($this->getBuildData($buildId));
}
protected function getBuildData($buildId)
{
$build = $this->_buildStore->getById($buildId);
$data = array();
$data['status'] = (int)$build->getStatus();
$data['log'] = $this->cleanLog($build->getLog());
$data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null;
$data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null;
$data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null;
return json_encode($data);
}
public function rebuild($buildId)
{
$copy = $this->_buildStore->getById($buildId);
$build = new Build();
$build->setProjectId($copy->getProjectId());
$build->setCommitId($copy->getCommitId());
$build->setStatus(0);
$build->setBranch($copy->getBranch());
$build->setCreated(new \DateTime());
$build = $this->_buildStore->save($build);
header('Location: /build/view/' . $build->getId());
}
public function delete($buildId)
{
$build = $this->_buildStore->getById($buildId);
$this->_buildStore->delete($build);
header('Location: /project/view/' . $build->getProjectId());
}
protected function cleanLog($log)
{
$log = str_replace('[0;32m', '<span style="color: green">', $log);
$log = str_replace('[0;31m', '<span style="color: red">', $log);
$log = str_replace('[0m', '</span>', $log);
return $log;
}
}

View file

@ -24,6 +24,7 @@ class GithubController extends b8\Controller
$build->setCommitId($payload['after']);
$build->setStatus(0);
$build->setLog('');
$build->setCreated(new \DateTime());
$build->setBranch(str_replace('refs/heads/', '', $payload['ref']));
}
catch(\Exception $ex)

View file

@ -13,12 +13,24 @@ class IndexController extends b8\Controller
public function index()
{
$builds = $this->_buildStore->getWhere(array(), 10, 0, array(), array('id' => 'DESC'));
$projects = $this->_projectStore->getWhere(array(), 50, 0, array(), array('title' => 'ASC'));
$projects = $this->_projectStore->getWhere(array(), 50, 0, array(), array('title' => 'ASC'));
$view = new b8\View('Index');
$view->builds = $this->getLatestBuildsHtml();
$view->projects = $projects['items'];
$view = new b8\View('Index');
$view->builds = $builds['items'];
$view->projects = $projects['items'];
return $view->render();
}
public function latest()
{
die($this->getLatestBuildsHtml());
}
protected function getLatestBuildsHtml()
{
$builds = $this->_buildStore->getWhere(array(), 10, 0, array(), array('id' => 'DESC'));
$view = new b8\View('BuildsTable');
$view->builds = $builds['items'];
return $view->render();
}

View file

@ -0,0 +1,195 @@
<?php
namespace PHPCI\Controller;
use b8,
PHPCI\Model\Build,
PHPCI\Model\Project,
b8\Form,
b8\Registry;
class ProjectController extends b8\Controller
{
public function init()
{
$this->_buildStore = b8\Store\Factory::getStore('Build');
$this->_projectStore = b8\Store\Factory::getStore('Project');
}
public function view($projectId)
{
$project = $this->_projectStore->getById($projectId);
$page = $this->getParam('p', 1);
$builds = $this->getLatestBuildsHtml($projectId, (($page - 1) * 10));
$view = new b8\View('Project');
$view->builds = $builds[0];
$view->total = $builds[1];
$view->project = $project;
$view->page = $page;
return $view->render();
}
public function build($projectId)
{
$build = new Build();
$build->setProjectId($projectId);
$build->setCommitId('Manual');
$build->setStatus(0);
$build->setBranch('master');
$build->setCreated(new \DateTime());
$build = $this->_buildStore->save($build);
header('Location: /build/view/' . $build->getId());
}
public function delete($id)
{
$project = $this->_projectStore->getById($id);
$this->_projectStore->delete($project);
header('Location: /');
}
public function builds($projectId)
{
$builds = $this->getLatestBuildsHtml($projectId);
die($builds[0]);
}
protected function getLatestBuildsHtml($projectId, $start = 0)
{
$builds = $this->_buildStore->getWhere(array('project_id' => $projectId), 10, $start, array(), array('id' => 'DESC'));
$view = new b8\View('BuildsTable');
$view->builds = $builds['items'];
return array($view->render(), $builds['count']);
}
public function add()
{
$method = Registry::getInstance()->get('requestMethod');
if($method == 'POST')
{
$values = $this->getParams();
$pub = null;
}
else
{
$id = '/tmp/' . md5(microtime(true));
shell_exec('ssh-keygen -q -t rsa -b 2048 -f '.$id.' -N "" -C "deploy@phpci"');
$pub = file_get_contents($id . '.pub');
$prv = file_get_contents($id);
$values = array('key' => $prv);
}
$form = $this->projectForm($values);
if($method != 'POST' || ($method == 'POST' && !$form->validate()))
{
$view = new b8\View('ProjectForm');
$view->type = 'add';
$view->project = null;
$view->form = $form;
$view->key = $pub;
return $view->render();
}
$values = $form->getValues();
$values['git_key'] = $values['key'];
$project = new Project();
$project->setValues($values);
$project = $this->_projectStore->save($project);
header('Location: /project/view/' . $project->getId());
die;
}
public function edit($id)
{
$method = Registry::getInstance()->get('requestMethod');
$project = $this->_projectStore->getById($id);
if($method == 'POST')
{
$values = $this->getParams();
}
else
{
$values = $project->getDataArray();
$values['key'] = $values['git_key'];
}
$form = $this->projectForm($values, 'edit/' . $id);
if($method != 'POST' || ($method == 'POST' && !$form->validate()))
{
$view = new b8\View('ProjectForm');
$view->type = 'edit';
$view->project = $project;
$view->form = $form;
$view->key = null;
return $view->render();
}
$values = $form->getValues();
$values['git_key'] = $values['key'];
$project->setValues($values);
$project = $this->_projectStore->save($project);
header('Location: /project/view/' . $project->getId());
die;
}
protected function projectForm($values, $type = 'add')
{
$form = new Form();
$form->setMethod('POST');
$form->setAction('/project/' . $type);
$form->addField(new Form\Element\Csrf('csrf'));
$field = new Form\Element\Text('title');
$field->setRequired(true);
$field->setLabel('Project Title');
$field->setClass('span4');
$form->addField($field);
$field = new Form\Element\Select('type');
$field->setRequired(true);
$field->setOptions(array('github' => 'Github', 'bitbucket' => 'Bitbucket'));
$field->setLabel('Where is your project hosted?');
$field->setClass('span4');
$form->addField($field);
$field = new Form\Element\Text('reference');
$field->setRequired(true);
$field->setPattern('[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+');
$field->setLabel('Repository Name on Github / Bitbucket (e.g. block8/phpci)');
$field->setClass('span4');
$form->addField($field);
$field = new Form\Element\TextArea('key');
$field->setRequired(false);
$field->setLabel('Private key to use to access repository (leave blank to use anonymous HTTP repository access)');
$field->setClass('span7');
$field->setRows(6);
$form->addField($field);
$field = new Form\Element\Submit();
$field->setValue('Save Project');
$field->setClass('btn-success');
$form->addField($field);
$form->setValues($values);
return $form;
}
}

View file

@ -22,6 +22,9 @@ class BuildBase extends Model
'status' => null,
'log' => null,
'branch' => null,
'created' => null,
'started' => null,
'finished' => null,
);
protected $_getters = array(
'id' => 'getId',
@ -30,6 +33,9 @@ class BuildBase extends Model
'status' => 'getStatus',
'log' => 'getLog',
'branch' => 'getBranch',
'created' => 'getCreated',
'started' => 'getStarted',
'finished' => 'getFinished',
'Project' => 'getProject',
@ -42,6 +48,9 @@ class BuildBase extends Model
'status' => 'setStatus',
'log' => 'setLog',
'branch' => 'setBranch',
'created' => 'setCreated',
'started' => 'setStarted',
'finished' => 'setFinished',
'Project' => 'setProject',
);
@ -93,6 +102,30 @@ class BuildBase extends Model
),
'created' => array(
'type' => 'datetime',
'length' => '',
'nullable' => true,
),
'started' => array(
'type' => 'datetime',
'length' => '',
'nullable' => true,
),
'finished' => array(
'type' => 'datetime',
'length' => '',
'nullable' => true,
),
);
public $indexes = array(
@ -154,6 +187,48 @@ class BuildBase extends Model
return $rtn;
}
public function getCreated()
{
$rtn = $this->_data['created'];
if(!empty($rtn))
{
$rtn = new \DateTime($rtn);
}
return $rtn;
}
public function getStarted()
{
$rtn = $this->_data['started'];
if(!empty($rtn))
{
$rtn = new \DateTime($rtn);
}
return $rtn;
}
public function getFinished()
{
$rtn = $this->_data['finished'];
if(!empty($rtn))
{
$rtn = new \DateTime($rtn);
}
return $rtn;
}
public function setId($value)
@ -240,6 +315,48 @@ class BuildBase extends Model
$this->_setModified('branch');
}
public function setCreated($value)
{
$this->_validateDate('Created', $value);
if($this->_data['created'] == $value)
{
return;
}
$this->_data['created'] = $value;
$this->_setModified('created');
}
public function setStarted($value)
{
$this->_validateDate('Started', $value);
if($this->_data['started'] == $value)
{
return;
}
$this->_data['started'] = $value;
$this->_setModified('started');
}
public function setFinished($value)
{
$this->_validateDate('Finished', $value);
if($this->_data['finished'] == $value)
{
return;
}
$this->_data['finished'] = $value;
$this->_setModified('finished');
}
/**

View file

@ -18,14 +18,16 @@ class ProjectBase extends Model
protected $_data = array(
'id' => null,
'title' => null,
'git_url' => null,
'reference' => null,
'git_key' => null,
'type' => null,
);
protected $_getters = array(
'id' => 'getId',
'title' => 'getTitle',
'git_url' => 'getGitUrl',
'reference' => 'getReference',
'git_key' => 'getGitKey',
'type' => 'getType',
);
@ -33,8 +35,9 @@ class ProjectBase extends Model
protected $_setters = array(
'id' => 'setId',
'title' => 'setTitle',
'git_url' => 'setGitUrl',
'reference' => 'setReference',
'git_key' => 'setGitKey',
'type' => 'setType',
);
public $columns = array(
@ -54,9 +57,9 @@ class ProjectBase extends Model
),
'git_url' => array(
'reference' => array(
'type' => 'varchar',
'length' => '1024',
'length' => '250',
@ -69,6 +72,14 @@ class ProjectBase extends Model
),
'type' => array(
'type' => 'varchar',
'length' => '50',
),
);
public $indexes = array(
@ -95,9 +106,9 @@ class ProjectBase extends Model
return $rtn;
}
public function getGitUrl()
public function getReference()
{
$rtn = $this->_data['git_url'];
$rtn = $this->_data['reference'];
return $rtn;
@ -111,6 +122,14 @@ class ProjectBase extends Model
return $rtn;
}
public function getType()
{
$rtn = $this->_data['type'];
return $rtn;
}
public function setId($value)
@ -141,18 +160,18 @@ class ProjectBase extends Model
$this->_setModified('title');
}
public function setGitUrl($value)
public function setReference($value)
{
$this->_validateNotNull('GitUrl', $value);
$this->_validateString('GitUrl', $value);
if($this->_data['git_url'] == $value)
$this->_validateNotNull('Reference', $value);
$this->_validateString('Reference', $value);
if($this->_data['reference'] == $value)
{
return;
}
$this->_data['git_url'] = $value;
$this->_data['reference'] = $value;
$this->_setModified('git_url');
$this->_setModified('reference');
}
public function setGitKey($value)
@ -169,6 +188,20 @@ class ProjectBase extends Model
$this->_setModified('git_key');
}
public function setType($value)
{
$this->_validateNotNull('Type', $value);
$this->_validateString('Type', $value);
if($this->_data['type'] == $value)
{
return;
}
$this->_data['type'] = $value;
$this->_setModified('type');
}

View file

@ -16,5 +16,21 @@ use PHPCI\Model\Base\BuildBase;
*/
class Build extends BuildBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
public function getCommitLink()
{
switch($this->getProject()->getType())
{
case 'github':
return 'https://github.com/' . $this->getProject()->getReference() . '/commit/' . $this->getCommitId();
}
}
public function getBranchLink()
{
switch($this->getProject()->getType())
{
case 'github':
return 'https://github.com/' . $this->getProject()->getReference() . '/tree/' . $this->getBranch();
}
}
}

View file

@ -16,5 +16,23 @@ use PHPCI\Model\Base\ProjectBase;
*/
class Project extends ProjectBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
public function getGitUrl()
{
$key = $this->getGitKey();
switch($this->getType() . '.' . (!empty($key) ? 'ssh' : 'http'))
{
case 'github.ssh':
return 'git@github.com:' . $this->getReference() . '.git';
case 'github.http':
return 'https://github.com/' . $this->getReference() . '.git';
case 'bitbucket.ssh':
return 'git@bitbucket.org:' . $this->getReference() . '.git';
case 'bitbucket.http':
return 'https://bitbucket.org/' . $this->getReference() . '.git';
}
}
}

View file

@ -11,7 +11,7 @@ class Composer implements \PHPCI\Plugin
public function __construct(\PHPCI\Builder $phpci, array $options = array())
{
$this->phpci = $phpci;
$this->directory = isset($options['directory']) ? $options['directory'] : $phpci->buildPath;
$this->directory = isset($options['directory']) ? $phpci->buildPath . '/' . $options['directory'] : $phpci->buildPath;
$this->action = isset($options['action']) ? $options['action'] : 'update';
}

27
PHPCI/Plugin/Mysql.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace PHPCI\Plugin;
class Mysql implements \PHPCI\Plugin
{
protected $phpci;
protected $queries = array();
public function __construct(\PHPCI\Builder $phpci, array $options = array())
{
$this->phpci = $phpci;
$this->queries = $options;
}
public function execute()
{
$rtn = true;
foreach($this->queries as $query)
{
$rtn = !$this->phpci->executeCommand('mysql -uroot -e "'.$query.'"') ? false : $rtn;
}
return $rtn;
}
}

135
PHPCI/View/Build.phtml Normal file
View file

@ -0,0 +1,135 @@
<div id="title">
<h1 style="display: inline-block">Build #<?= $build->getId(); ?></h1>
<h3 style="margin-left: 40px; display: inline-block">Branch: <?= $build->getBranch(); ?> - <?= $build->getCommitId() == 'Manual' ? 'Manual Build' : 'Commit: ' . $build->getCommitId(); ?></h3>
</div>
<div class="row">
<div class="span3">
<div class="well" style="padding: 8px 0">
<ul class="nav nav-list">
<li><a href="/"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="/project/view/<?= $build->getProject()->getId(); ?>"><i class="icon-folder-open"></i> <?= $build->getProject()->getTitle(); ?></a></li>
<li class="divider"></li>
<li class="nav-header">Options</li>
<li><a href="/build/rebuild/<?= $build->getId(); ?>"><i class="icon-cog"></i> Rebuild</a></li>
<li><a href="javascript:confirmDelete('/build/delete/<?= $build->getId(); ?>')"><i class="icon-trash"></i> Delete Build</a></li>
</ul>
</div>
</div>
<div class="span9">
<div id="status"></div>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th style="width: 33.3%">Build Created</th>
<th style="width: 33.3%">Build Started</th>
<th style="width: 33.3%">Build Finished</th>
</tr>
</thead>
<tbody>
<tr>
<td id="created"></td>
<td id="started"></td>
<td id="finished"></td>
</tr>
</tbody>
</table>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Build Log</th>
</tr>
</thead>
<tbody>
<tr>
<td><pre style="height: 300px; overflow-y: auto;" id="log"></pre></td>
</tr>
</tbody>
</table>
</div>
</div>
<?php if($build->getStatus() == 0 || $build->getStatus() == 1 || true): ?>
<script>
setInterval(function()
{
$.getJSON('/build/data/<?= $build->getId(); ?>', updateBuildView);
}, 10000);
</script>
<?php endif; ?>
<script>
window.initial = <?= $data; ?>;
function updateBuildView(data)
{
console.log(data);
$('#status').attr('class', 'alert');
var cls;
var msg;
switch(data.status)
{
case 0:
cls = 'alert-info';
msg = 'This build has not yet started.';
break;
case 1:
cls = 'alert-warning';
msg = 'This build is in progress.';
break;
case 2:
cls = 'alert-success';
msg = 'This build was successful!';
break;
case 3:
cls = 'alert-error';
msg = 'This build has failed.';
break;
}
$('#status').addClass(cls).text(msg);
if(data.created)
{
$('#created').text(data.created);
}
else
{
$('#created').text('Not created yet.');
}
if(data.started)
{
$('#started').text(data.started);
}
else
{
$('#started').text('Not started yet.');
}
if(data.finished)
{
$('#finished').text(data.finished);
}
else
{
$('#finished').text('Not finished yet.');
}
$('#log').html(data.log);
}
$(document).ready(function()
{
updateBuildView(window.initial);
});
</script>

View file

@ -0,0 +1,51 @@
<?php if(empty($builds) || !count($builds)): ?>
<tr class="">
<td colspan="6">No builds yet.</td>
</tr>
<?php endif; ?>
<?php foreach($builds as $build): ?>
<?php
switch($build->getStatus())
{
case 0:
$cls = 'info';
$status = 'Pending';
break;
case 1:
$cls = 'warning';
$status = 'Running';
break;
case 2:
$cls = 'success';
$status = 'Success';
break;
case 3:
$cls = 'error';
$status = 'Failed';
break;
}
?>
<tr class="<?= $cls; ?>">
<td><a href="/build/view/<?= $build->getId(); ?>">#<?= str_pad($build->getId(), 6, '0', STR_PAD_LEFT); ?></a></td>
<td><a href="/project/view/<?= $build->getProjectId(); ?>"><?= $build->getProject()->getTitle(); ?></a></td>
<td><a href="<?= $build->getCommitLink(); ?>"><?= $build->getCommitId(); ?></a></td>
<td><a href="<?= $build->getBranchLink(); ?>"><?= $build->getBranch(); ?></a></td>
<td><?= $status; ?></td>
<td>
<div class="btn-group">
<a class="btn" href="/build/view/<?= $build->getId(); ?>">View</a>
<button class="btn dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:confirmDelete('/build/delete/<?= $build->getId(); ?>');">Delete Build</a></li>
</ul>
</div>
</td>
</tr>
<?php endforeach; ?>

View file

@ -1,27 +1,42 @@
<div id="title">
<h1 style="display: inline-block">Dashboard</h1>
</div>
<div class="row">
<div class="span3">
<ul>
<div class="well" style="padding: 8px 0">
<ul class="nav nav-list">
<li><a href="/"><i class="icon-home"></i> Dashboard</a></li>
<li class="divider"></li>
<li class="nav-header">Projects</li>
<?php foreach($projects as $project): ?>
<li><a href="/project/view/<?= $project->getId(); ?>"><?= $project->getTitle(); ?></a></li>
<li><a href="/project/view/<?= $project->getId(); ?>"><i class="icon-folder-open"></i> <?= $project->getTitle(); ?></a></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="span9">
<table width="100%" class="table-striped table-bordered">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Project</th>
<th>Commit</th>
<th>Branch</th>
<th>Status</th>
<th style="width: 1%"></th>
</tr>
<?php foreach($builds as $build): ?>
<tr>
<td><?= $build->getProject()->getTitle(); ?></th>
<td><?= $build->getCommitId(); ?></th>
<td><?= $build->getBranch(); ?></th>
<td><?= $build->getStatus(); ?></th>
</tr>
<?php endforeach; ?>
</thead>
<tbody id="latest-builds">
<?= $builds; ?>
</tbody>
</table>
</div>
</div>
</div>
<script>
setInterval(function()
{
$('#latest-builds').load('/index/latest');
}, 10000);
</script>

View file

@ -1,10 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>PHPCI</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='http://fonts.googleapis.com/css?family=Roboto:400,100,300,500,700,900,300italic' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="/assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="/assets/css/bootstrap-responsive.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="/assets/js/bootstrap.min.js"></script>
@ -12,6 +15,9 @@
body
{
background: #246;
font-family: Roboto, Arial, Sans-Serif;
font-style: normal;
font-weight: 300;
padding-top: 70px;
}
@ -21,13 +27,58 @@
border: 10px solid #369;
padding: 10px;
}
.widget-title, .modal-header, .table th, div.dataTables_wrapper .ui-widget-header, .ui-dialog .ui-dialog-titlebar {
background-color: #efefef;
background-image: -webkit-gradient(linear, 0 0%, 0 100%, from(#fdfdfd), to(#eaeaea));
background-image: -webkit-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%);
background-image: -moz-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%);
background-image: -ms-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%);
background-image: -o-linear-gradient(top, #fdfdfd 0%, #eaeaea 100%);
background-image: -linear-gradient(top, #fdfdfd 0%, #eaeaea 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fdfdfd', endColorstr='#eaeaea',GradientType=0 ); /* IE6-9 */
border-bottom: 1px solid #CDCDCD;
}
#title
{
background: #f8f8f8;
border-bottom: 1px solid #ccc;
margin: -10px -10px 15px -10px;
padding: 10px;
}
#title h1
{
font-size: 2em;
margin: 0;
padding: 0;
}
</style>
<script>
function confirmDelete(url)
{
if(confirm('Are you sure you want to delete this?'))
{
window.location.href = url;
}
else
{
return false;
}
}
</script>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="/">PHPCI</a>
<a class="brand" href="/">PHPCI <span class="badge badge-important" style="position: relative; top: -3px; margin-left: 10px">1.0 Alpha</span></a>
<a class="pull-right btn" href="/project/add">Add Project</a>
</div>
</div>
</div>

85
PHPCI/View/Project.phtml Normal file
View file

@ -0,0 +1,85 @@
<div id="title">
<h1>Project: <?= $project->getTitle(); ?></h1>
</div>
<div class="row">
<div class="span3">
<div class="well" style="padding: 8px 0">
<ul class="nav nav-list">
<li><a href="/"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="/project/view/<?= $project->getId(); ?>"><i class="icon-folder-open"></i> <?= $project->getTitle(); ?></a></li>
<li class="divider"></li>
<li class="nav-header">Options</li>
<li><a href="/project/edit/<?= $project->getId(); ?>"><i class="icon-edit"></i> Edit Project</a></li>
<li><a href="/project/build/<?= $project->getId(); ?>"><i class="icon-cog"></i> Build Now</a></li>
<li><a href="javascript:confirmDelete('/project/delete/<?= $project->getId(); ?>')"><i class="icon-trash"></i> Delete Project</a></li>
</ul>
</div>
<br>
<p class="alert alert-info">To automatically build this project when new commits are pushed, add the URL below
<?php
switch($project->getType())
{
case 'github':
$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/github/webhook/' . $project->getId();
print ' as a "WebHook URL" in the <a href="https://github.com/' . $project->getReference() . '/settings/hooks">Service Hooks</a> section of your Github repository.<br><br><strong style="word-wrap: break-word;">' . $url . '</strong>';
break;
case 'bitbucket':
$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/bitbucket/webhook/' . $project->getId();
print ' as a "POST" service in the <a href="https://bitbucket.org/' . $project->getReference() . '/admin/services">Services</a> section of your Bitbucket repository.<br><br><strong style="word-wrap: break-word;">' . $url . '</strong>';
break;
}
?>
</p>
</div>
<div class="span9">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Project</th>
<th>Commit</th>
<th>Branch</th>
<th>Status</th>
<th style="width: 1%"></th>
</tr>
</thead>
<tbody id="latest-builds">
<?= $builds; ?>
</tbody>
</table>
<?php
print '<div class="pagination"><ul>';
$pages = ceil($total / 10);
$pages = $pages == 0 ? 1 : $pages;
print '<li class="'.($page == 1 ? 'disabled' : '').'"><a href="/project/view/'.$project->getId().'?p='.($page == 1 ? '1' : $page - 1).'">&laquo;</a></li>';
for($i = 1; $i <= $pages; $i++)
{
print '<li><a href="/project/view/' . $project->getId() . '?p=' . $i . '">' . $i . '</a></li>';
}
print '<li class="'.($page == $pages ? 'disabled' : '').'"><a href="/project/view/'.$project->getId().'?p='.($page == $pages ? $pages : $page + 1).'">&raquo;</a></li>';
print '</ul></div>';
?>
</div>
</div>
<?php if($page == 1): ?>
<script>
setInterval(function()
{
$('#latest-builds').load('/project/builds/<?= $project->getId(); ?>');
}, 10000);
</script>
<?php endif; ?>

View file

@ -0,0 +1,22 @@
<div id="title">
<h1><?= $type == 'add' ? 'Add Project' : 'Edit Project' ?></h1>
</div>
<div class="row">
<div class="span4">
<div class="well" style="">
<?php if(!is_null($key)): ?>
<p>To make it easier to get started, we've generated a public / private key pair for you to use for this project. To use it, just add the following public key to the "deploy keys" section of your repository settings on Github / Bitbucket.</p>
<textarea style="width: 90%; height: 150px;"><?= $key ?></textarea>
<?php elseif($type == 'add'): ?>
<p>Fill in the form to the right to add your new project.</p>
<?php else: ?>
<p>Edit your project details using the form to the right.</p>
<?php endif; ?>
</div>
</div>
<div class="span8">
<?= $form; ?>
</div>
</div>

BIN
assets/.DS_Store vendored

Binary file not shown.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

6158
assets/css/bootstrap.css vendored

File diff suppressed because it is too large Load diff

836
assets/css/bootstrap.min.css vendored Normal file → Executable file

File diff suppressed because one or more lines are too long

2914
assets/js/bootstrap.js vendored Normal file → Executable file

File diff suppressed because it is too large Load diff

5
assets/js/bootstrap.min.js vendored Normal file → Executable file

File diff suppressed because one or more lines are too long