Merge pull request #11 from ansible-semaphore/feature/ui-work

UI Work
This commit is contained in:
Alan Campbell 2015-03-25 09:27:47 -04:00
commit 0cc72360be
19 changed files with 165 additions and 106 deletions

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM iojs:onbuild
ENV NODE_ENV production
ADD . /srv/semaphore
WORKDIR /srv/semaphore
RUN rm -f node_modules/
RUN npm install
CMD ["node", "/srv/semaphore/bin/semaphore"]
EXPOSE 80

100
README.md
View File

@ -1,49 +1,81 @@
semaphore
=========
# semaphore
Open Source Alternative to Ansible Tower
![](public/img/screenshot.png)
![screenshot](public/img/screenshot.png)
Features
--------
## Features
The basics of Ansible Tower, but in addition:
- Fast, Simple interface that doesnt get in the way
- Task output is streamed live via websocket
- Free. MIT Licensed. Do what you want.
- [x] Fast, Simple interface (not having to submit a million forms to get something simple done)
- [x] Task output is streamed live via websocket
- [x] Create inventories per playbook
- [x] Add rsa keys (to authenticate git repositories)
- [x] Run playbooks against specified hosts
- [ ] Multiple Users support
How to run:
-----------
## Docker quickstart
1. Install Vagrant
2. Run `vagrant up`
3. Open [localhost:3000](http://localhost:3000)
### Run redis
Development steps:
```
docker run -d \
--name=redisio \
--restart=always \
-v /var/lib/redisio:/var/lib/redis \
-p 127.0.0.1:6379:6379 \
castawaylabs/redis-docker
```
Install requirements:
- node.js >= 0.11.x
- an isolated environment (e.g. Docker / NodeGear)
- ansible (the tool)
- mongodb & redis
- Sudo access (this might change). To run jobs, this tool writes private keys to /root/.ssh and copies playbook directories to /root/.
### Run mongodb
1. Copy `lib/credentials.default.json` to `lib/credentials.json` and customise, or export relevant environment variables
2. `bower install`
3. `node bin/semaphore`
```
docker run -d \
--name=mongodb \
--restart=always \
-v /var/lib/mongodb:/var/lib/mongodb \
-p 127.0.0.1:27017:27017 \
castawaylabs/mongodb-docker
```
Initial Login
-------------
### Run semaphore
```
docker run -d \
--name=semaphore \
--restart=always \
--link redisio:redis \
--link mongodb:mongo \
-e MONGODB_URL="mongodb://mongo/semaphore" \
-e REDIS_HOST="redis" \
-p 80:80 \
castawaylabs/semaphore
```
## Development
1. Install VirtualBox & Vagrant
2. Run `vagrant plugin install gatling-rsync-auto`
3. Run `vagrant up` to start the vagrant box
4. Run `vagrant gatling-rsync-auto` to synchronise changes from your local machine to vagrant
### Running semaphore inside vagrant
1. `vagrant ssh`, `cd /opt/semaphore`
2. `npm install`
3. `bower install`
4. `npm install -g nodemon`
5. `nodemon bin/semaphore`
## Initial Login
```
Email: 'admin@semaphore.local'
Password: 'CastawayLabs'
```
Environment Variables
---------------------
## Environment Variables
Use these variables to override the config.
@ -58,9 +90,19 @@ Use these variables to override the config.
| SMTP_PASS | Mandrill smtp password | |
| MONGODB_URL | Mongodb URL | `mongodb://127.0.0.1/semaphore` |
Note to Ansible guys
--------------------
## Vision and goals for v1
- Be able to specify environment information per playbook / per task
- Schedule jobs
- Email alerts
- Multiple user support
## Note to Ansible guys
> Thanks very much for making Ansible, and Ansible Tower. It is a great tool!. Your UI is pretty horrible though, and so we'd be happy if you could learn and use parts of this tool in your Tower.
It would be amazing if this could be your `Community Edition` of Ansible Tower.
## License
MIT

View File

@ -13,4 +13,4 @@
"angular-couch-potato": "~0.1.1",
"angular-ui-router": "~0.2.10"
}
}
}

View File

@ -4,6 +4,9 @@ var express = require('express')
exports.unauthorized = function (app, template) {
template([
'tasks',
'jobs',
'hosts',
'view'
], {
prefix: 'playbook'

View File

@ -27,19 +27,12 @@ html
li: a(ui-sref="addPlaybook") Add Playbook
.container-fluid
.row
.col-sm-3.col-lg-2
ul.nav
h2.no-top-margin Playbooks
li(ng-repeat="playbook in playbooks")
a(ui-sref="playbook.view({ playbook_id: playbook._id })") {{ playbook.name }}
.col-sm-9.col-lg-10
block content
ui-view(autoscroll="false")
p.lead.text-center
i.fa.fa-spin.fa-cog
| Loading...
.col-lg-12
block content
ui-view(autoscroll="false")
p.lead.text-center
i.fa.fa-spin.fa-cog
| Loading...
block js
script(src="/vendor/requirejs/require.js" data-main="/js/semaphore.js")

View File

@ -1,7 +1,9 @@
h1 Add Playbook
h1
span(ng-if="!playbook.data._id") Add Playbook
span(ng-if="playbook.data._id.length > 0") Edit Playbook
.row
.col-md-6
div(ng-class="{ 'col-md-6': !playbook.data._id, 'col-md-12': playbook.data._id.length > 0 }")
.panel.panel-default
.panel-body
form.form-horizontal(name="playbookForm")
@ -31,5 +33,6 @@ h1 Add Playbook
button.btn.btn-success(ng-click="add()" ng-disabled="playbookForm.$invalid")
span(ng-if="!playbook.data._id") Add
span(ng-if="playbook.data._id.length > 0") Edit
.col-md-6
a.btn.btn-danger(ng-if="playbook.data._id.length > 0" ng-click="delete()" style="margin-left: 10px;") Delete
.col-md-6(ng-if="!playbook.data._id")
p.lead It is recommended you create an identity before creating playbooks.

View File

@ -1,4 +1,4 @@
h2 Hosts
h1 Hosts
button.btn.btn-default.pull-right(ng-click="add()") Add Group
div(ng-repeat="hostgroup in hostgroups.hostgroups")

View File

@ -1,4 +1,4 @@
h2 Jobs
h1 Jobs
button.btn.btn-default.pull-right(ng-click="add()") Add Job
table.table.table-hover

View File

@ -5,6 +5,6 @@ h1 Playbooks
table.table
tr(ng-repeat="playbook in playbooks")
td: a(ui-sref="playbook.view({ playbook_id: playbook._id })") {{ playbook.name }}
td: a(ui-sref="playbook.tasks({ playbook_id: playbook._id })") {{ playbook.name }}
blockquote Playbooks are ansible playbooks. Each playbook defines a git repository with the playbook code. Semaphore downloads the playbook and runs the task file.

View File

@ -1,7 +1,7 @@
h2 Tasks
h1(ng-if="tasks.tasks.length") Tasks
small(ng-if="status.length > 0" ng-bind="status")
table.table.table-hover
table.table.table-hover(ng-if="tasks.tasks.length")
thead
tr
th Job Name
@ -33,4 +33,8 @@ table.table.table-hover
button(data-dismiss="modal").close: span ×
h4.modal-title Task Output
.modal-body
pre: code {{ openTask.data.output }}
pre: code {{ openTask.data.output }}
h4.text-center.text-muted(ng-if="!tasks.tasks.length" style="margin-top: 15px;") It seems no 
a(ui-sref="playbook.jobs") jobs
|  have been run yet.

View File

@ -1,16 +1,15 @@
h1 {{ playbook.data.name }}
.btn-group.pull-right
button.btn.btn-success(ui-sref="playbook.edit({ playbook_id: playbook.data._id })") Edit
button.btn.btn-danger(ng-click="delete()") Delete
mixin menuItem(state, name)
li(ng-class="{ active: $state.includes('#{state}') }"): a(ui-sref=state+"({ playbook_id: playbook.data._id })")= name
hr
ui-view(name="tasks")
hr
ui-view(name="jobs")
hr
ui-view(name="hosts")
.row
.col-sm-3.col-lg-2
ul.nav
h2.no-top-margin {{ playbook.data.name }}
+menuItem("playbook.tasks", "Tasks")
+menuItem("playbook.jobs", "Jobs")
+menuItem("playbook.hosts", "Hosts")
br
ul.nav
li: a(ui-sref="playbook.edit") Edit
.col-sm-9.col-lg-10
ui-view

View File

@ -22,7 +22,6 @@
- runit
- npm: name={{ item }} global=yes
with_items:
- grunt-cli
- bower
# source is copied using ansible.

View File

@ -28,4 +28,10 @@ h1, h2, h3, h4, h5, h6 {
&.no-top-margin {
margin-top: 0;
}
}
ul.nav {
& > li.active a {
background-color: #eee;
}
}

View File

@ -18,7 +18,7 @@ define([
$scope.playbook.add()
.success(function (data) {
playbooks.getPlaybooks(function () {
$state.transitionTo('playbook.view', {
$state.transitionTo('playbook.tasks', {
playbook_id: data._id
});
});

View File

@ -5,10 +5,10 @@ define([
'factories/hostgroup',
'factories/host'
], function(app, $) {
app.registerController('HostsCtrl', ['$scope', '$state', 'hostgroups', 'Host', function($scope, $state, hostgroups, Host) {
app.registerController('PlaybookHostsCtrl', ['$scope', '$state', 'hostgroups', 'Host', function($scope, $state, hostgroups, Host) {
$scope.hostgroups = hostgroups;
hostgroups.get($scope.playbook, function () {
});

View File

@ -4,9 +4,9 @@ define([
'services/jobs',
'factories/job'
], function(app, $) {
app.registerController('JobsCtrl', ['$scope', 'jobs', function($scope, jobs) {
app.registerController('PlaybookJobsCtrl', ['$scope', 'jobs', function($scope, jobs) {
$scope.jobs = jobs;
jobs.get($scope.playbook, function () {
});

View File

@ -7,9 +7,9 @@ define([
], function(app, $, io) {
var socket = io();
app.registerController('TasksCtrl', ['$scope', 'tasks', 'Task', function($scope, tasks, Task) {
app.registerController('PlaybookTasksCtrl', ['$scope', 'tasks', 'Task', function($scope, tasks, Task) {
$scope.tasks = tasks;
tasks.get($scope.playbook, function () {
});
@ -40,7 +40,7 @@ define([
if (!task.data.output) {
task.data.output = "";
}
task.data.output += data.output;
if (!$scope.$$phase) {

View File

@ -30,10 +30,7 @@ define([
controller: 'PlaybookCtrl',
templateUrl: '/view/playbook/view',
resolve: {
dummy: $couchPotatoProvider.resolve(['controllers/playbook/playbook',
'controllers/host/hosts',
'controllers/job/jobs',
'controllers/task/tasks']),
dummy: $couchPotatoProvider.resolve(['controllers/playbook/playbook']),
playbook: function (Playbook, $stateParams, $q, $state) {
var deferred = $q.defer();
@ -51,36 +48,37 @@ define([
}
})
.state('playbook.view', {
url: '',
views: {
tasks: {
templateUrl: '/view/task/tasks',
controller: 'TasksCtrl'
},
jobs: {
templateUrl: '/view/job/jobs',
controller: 'JobsCtrl'
},
hosts: {
templateUrl: '/view/host/hosts',
controller: 'HostsCtrl'
}
}
})
.state('playbook.edit', {
url: '/edit',
templateUrl: "/view/playbook/add",
controller: 'EditPlaybookCtrl',
resolve: {
dummy: $couchPotatoProvider.resolve(['controllers/playbook/edit'])
},
views: {
tasks: {
templateUrl: '/view/playbook/add',
controller: 'EditPlaybookCtrl'
}
}
})
.state('playbook.tasks', {
url: '/tasks',
templateUrl: "/view/playbook/tasks",
controller: 'PlaybookTasksCtrl',
resolve: {
dummy: $couchPotatoProvider.resolve(['controllers/playbook/tasks'])
}
})
.state('playbook.jobs', {
url: '/jobs',
templateUrl: "/view/playbook/jobs",
controller: 'PlaybookJobsCtrl',
resolve: {
dummy: $couchPotatoProvider.resolve(['controllers/playbook/jobs'])
}
})
.state('playbook.hosts', {
url: '/hosts',
templateUrl: "/view/playbook/hosts",
controller: 'PlaybookHostsCtrl',
resolve: {
dummy: $couchPotatoProvider.resolve(['controllers/playbook/hosts'])
}
})
})

View File

@ -1 +0,0 @@
vagrant ssh -- -R 27017:localhost:27017 -R 6379:localhost:6379 -R 3000:localhost:3000