Events, task history, task details

This commit is contained in:
Matej Kramny 2016-04-17 01:20:23 +01:00
parent ea0245b550
commit 1805c32e90
17 changed files with 253 additions and 11 deletions

View File

@ -7,4 +7,8 @@ CREATE TABLE `event` (
KEY `project_id` (`project_id`),
KEY `object_id` (`object_id`),
KEY `created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
alter table task add `created` datetime not null default CURRENT_TIMESTAMP,
add `start` datetime null,
add `end` datetime null;

View File

@ -1,6 +1,10 @@
package models
import "time"
import (
"time"
"github.com/ansible-semaphore/semaphore/database"
)
type Event struct {
ProjectID *int `db:"project_id" json:"project_id"`
@ -12,3 +16,9 @@ type Event struct {
ObjectName string `db:"-" json:"object_name"`
ProjectName *string `db:"project_name" json:"project_name"`
}
func (evt Event) Insert() error {
_, err := database.Mysql.Exec("insert into event set project_id=?, object_id=?, object_type=?, description=?, created=NOW()", evt.ProjectID, evt.ObjectID, evt.ObjectType, evt.Description)
return err
}

View File

@ -13,6 +13,10 @@ type Task struct {
// override variables
Playbook string `db:"playbook" json:"playbook"`
Environment string `db:"environment" json:"environment"`
Created time.Time `db:"created" json:"created"`
Start *time.Time `db:"start" json:"start"`
End *time.Time `db:"end" json:"end"`
}
type TaskOutput struct {

View File

@ -4,6 +4,8 @@
h4.no-top-margin Events
ul.list-unstyled
li(ng-repeat="event in events")
time(ng-bind="event.created | date:'short'")
span : 
a(ng-if="event.project_id != null" ui-sref="project.dashboard({ project_id: event.project_id })") {{ event.project_name }}
span(ng-if="event.project_id != null")  
span(ng-bind="event.object_name")

View File

@ -0,0 +1,23 @@
.modal-header
h4.modal-title Create Task
.modal-body
form.form-horizontal
.form-group
.col-sm-6.col-sm-offset-4
p.help-block <i>Optional</i> parameters
.form-group
label.control-label.col-sm-4 Playbook Override
.col-sm-6
input.form-control(type="text" placeholder="Enter playbook name to override template" ng-model="task.playbook")
.form-group
label.control-label.col-sm-4 Environment Override
.col-sm-6
textarea.form-control(rows="10" placeholder="Override playbook environment, *MUST* be valid JSON" ng-model="task.environment")
.form-group
.col-sm-6.col-sm-offset-4: .checkbox: label
input(type="checkbox" ng-model="task.debug")
| Debug (<code>-vvvv</code>)
.modal-footer
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
button.btn.btn-success(ng-click="run(task)") Run!

View File

@ -3,11 +3,21 @@
h3.no-top-margin Project activity
ul.list-unstyled
li(ng-repeat="event in events")
a(ng-if="event.project_id != null" ui-sref="project.dashboard({ project_id: event.project_id })") {{ event.project_name }}
span(ng-if="event.project_id != null") &nbsp;
time(ng-bind="event.created | date:'short'")
span :&nbsp;
span(ng-bind="event.object_name")
span - {{ event.description }}
.col-sm-5
h4.no-top-margin Task history
ul.list-group
li.list-group-item(ng-repeat="task in history"): a(ng-click="openTask()") {{ task.playbook }}
li.list-group-item(ng-repeat="task in tasks"): a(ng-click="openTask()" href="#")
i.fa.fa-fw.fa-clock-o.text-warning(ng-if="task.status == 'waiting'")
i.fa.fa-fw.fa-times.text-danger(ng-if="task.status == 'error'")
i.fa.fa-fw.fa-times.text-success(ng-if="task.status == 'success'")
| &nbsp;
span(ng-if="task.playbook.length == 0") {{ task.tpl_playbook }}
span(ng-if="task.playbook.length > 0") {{ task.playbook }}
span.pull-right {{ task.created | date:'short' }}
br
i Duration
span.pull-right {{ task.duration }}

View File

@ -0,0 +1,8 @@
.modal-header
h4.modal-title Task Log
.modal-body
pre: code Hello!
.modal-footer
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
button.btn.btn-success(ng-click="restart(task)") Re-Run

View File

@ -15,4 +15,6 @@ table.table
td {{ inventoryAssoc[tpl.inventory_id].name }}
td {{ environmentAssoc[tpl.environment_id].name }}
td {{ reposAssoc[tpl.repository_id].name }}
td: button.btn.btn-danger.btn-xs.pull-right(ng-click="remove(tpl)") remove
td
button.btn.btn-danger.btn-xs.pull-right(ng-click="remove(tpl)") remove
button.btn.btn-success.btn-xs.pull-right(ng-click="run(tpl)" style="margin-right: 10px;") run

View File

@ -1,7 +1,31 @@
define(function () {
app.registerController('ProjectDashboardCtrl', ['$scope', '$http', 'Project', function ($scope, $http, Project) {
define(['controllers/projects/taskRunner'], function () {
app.registerController('ProjectDashboardCtrl', ['$scope', '$http', 'Project', '$uibModal', '$rootScope', function ($scope, $http, Project, $modal, $rootScope) {
$http.get(Project.getURL() + '/events').success(function (events) {
$scope.events = events;
});
$http.get(Project.getURL() + '/tasks').success(function (tasks) {
$scope.tasks = tasks;
$scope.tasks.forEach(function (t) {
if (!t.start || !t.end) {
return;
}
t.duration = moment(t.start).from(moment(t.end), true);
})
});
$scope.openTask = function (task) {
var scope = $rootScope.$new();
scope.task = task;
scope.project = Project;
$modal.open({
templateUrl: '/tpl/projects/taskModal.html',
controller: 'TaskCtrl',
scope: scope
});
}
}]);
});

View File

@ -0,0 +1,18 @@
define(function () {
app.registerController('CreateTaskCtrl', ['$scope', '$http', 'Template', 'Project', function ($scope, $http, Template, Project) {
console.log(Template);
$scope.task = {};
$scope.run = function (task) {
task.template_id = Template.id;
$http.post(Project.getURL() + '/tasks', task).success(function (t) {
}).error(function (_, status) {
swal('Error', 'error launching task: HTTP ' + status, 'error');
});
}
}]);
app.registerController('TaskCtrl', ['$scope', '$http', function ($scope, $http) {
}]);
});

View File

@ -1,4 +1,4 @@
define(function () {
define(['controllers/projects/taskRunner'], function () {
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', function ($scope, $http, $modal, Project, $rootScope) {
$http.get(Project.getURL() + '/keys?type=ssh').success(function (keys) {
$scope.sshKeys = keys;
@ -66,6 +66,21 @@ define(function () {
});
}
$scope.run = function (tpl) {
$modal.open({
templateUrl: '/tpl/projects/createTaskModal.html',
controller: 'CreateTaskCtrl',
resolve: {
Project: function () {
return Project;
},
Template: function () {
return tpl;
}
}
})
}
$scope.reload();
}]);
});

View File

@ -76,7 +76,22 @@ func AddEnvironment(c *gin.Context) {
return
}
if _, err := database.Mysql.Exec("insert into project__environment set project_id=?, name=?, json=?, password=?", project.ID, env.Name, env.JSON, env.Password); err != nil {
res, err := database.Mysql.Exec("insert into project__environment set project_id=?, name=?, json=?, password=?", project.ID, env.Name, env.JSON, env.Password)
if err != nil {
panic(err)
}
insertID, _ := res.LastInsertId()
insertIDInt := int(insertID)
objType := "environment"
desc := "Environment " + env.Name + " created"
if err := (models.Event{
ProjectID: &project.ID,
ObjectType: &objType,
ObjectID: &insertIDInt,
Description: &desc,
}.Insert()); err != nil {
panic(err)
}
@ -90,5 +105,13 @@ func RemoveEnvironment(c *gin.Context) {
panic(err)
}
desc := "Environment " + env.Name + " deleted"
if err := (models.Event{
ProjectID: &env.ProjectID,
Description: &desc,
}.Insert()); err != nil {
panic(err)
}
c.AbortWithStatus(204)
}

View File

@ -75,7 +75,22 @@ func AddInventory(c *gin.Context) {
return
}
if _, err := database.Mysql.Exec("insert into project__inventory set project_id=?, name=?, type=?, key_id=?, ssh_key_id=?, inventory=?", project.ID, inventory.Name, inventory.Type, inventory.KeyID, inventory.SshKeyID, inventory.Inventory); err != nil {
res, err := database.Mysql.Exec("insert into project__inventory set project_id=?, name=?, type=?, key_id=?, ssh_key_id=?, inventory=?", project.ID, inventory.Name, inventory.Type, inventory.KeyID, inventory.SshKeyID, inventory.Inventory)
if err != nil {
panic(err)
}
insertID, _ := res.LastInsertId()
insertIDInt := int(insertID)
objType := "inventory"
desc := "Inventory " + inventory.Name + " created"
if err := (models.Event{
ProjectID: &project.ID,
ObjectType: &objType,
ObjectID: &insertIDInt,
Description: &desc,
}.Insert()); err != nil {
panic(err)
}
@ -109,6 +124,17 @@ func UpdateInventory(c *gin.Context) {
panic(err)
}
desc := "Inventory " + inventory.Name + " updated"
objType := "inventory"
if err := (models.Event{
ProjectID: &oldInventory.ProjectID,
Description: &desc,
ObjectID: &oldInventory.ID,
ObjectType: &objType,
}.Insert()); err != nil {
panic(err)
}
c.AbortWithStatus(204)
}
@ -119,5 +145,13 @@ func RemoveInventory(c *gin.Context) {
panic(err)
}
desc := "Inventory " + inventory.Name + " deleted"
if err := (models.Event{
ProjectID: &inventory.ProjectID,
Description: &desc,
}.Insert()); err != nil {
panic(err)
}
c.AbortWithStatus(204)
}

View File

@ -42,5 +42,13 @@ func AddProject(c *gin.Context) {
panic(err)
}
desc := "Project Created"
if err := (models.Event{
ProjectID: &body.ID,
Description: &desc,
}.Insert()); err != nil {
panic(err)
}
c.JSON(201, body)
}

View File

@ -87,6 +87,7 @@ func Route(r *gin.Engine) {
api.PUT("/templates/:template_id", projects.TemplatesMiddleware, projects.UpdateTemplate)
api.DELETE("/templates/:template_id", projects.TemplatesMiddleware, projects.RemoveTemplate)
api.GET("/tasks", tasks.GetAll)
api.POST("/tasks", tasks.AddTask)
}(api.Group("/project/:project_id"))
}

View File

@ -7,6 +7,7 @@ import (
"github.com/ansible-semaphore/semaphore/database"
"github.com/ansible-semaphore/semaphore/models"
"github.com/gin-gonic/gin"
"github.com/masterminds/squirrel"
)
type taskPool struct {
@ -60,6 +61,9 @@ func AddTask(c *gin.Context) {
return
}
taskObj.Created = time.Now()
taskObj.Status = "waiting"
if err := database.Mysql.Insert(&taskObj); err != nil {
panic(err)
}
@ -70,3 +74,25 @@ func AddTask(c *gin.Context) {
c.JSON(201, taskObj)
}
func GetAll(c *gin.Context) {
project := c.MustGet("project").(models.Project)
query, args, _ := squirrel.Select("task.*, tpl.playbook as tpl_playbook").
From("task").
Join("project__template as tpl on task.template_id=tpl.id").
Where("tpl.project_id=?", project.ID).
OrderBy("task.created desc").
ToSql()
var tasks []struct {
models.Task
TemplatePlaybook string `db:"tpl_playbook" json:"tpl_playbook"`
}
if _, err := database.Mysql.Select(&tasks, query, args...); err != nil {
panic(err)
}
c.JSON(200, tasks)
}

View File

@ -24,33 +24,56 @@ type task struct {
environment models.Environment
}
func (t *task) fail() {
t.task.Status = "error"
if _, err := database.Mysql.Exec("update task set status='error' where id=?", t.task.ID); err != nil {
panic(err)
}
}
func (t *task) run() {
pool.running = t
defer func() {
fmt.Println("Stopped running tasks")
pool.running = nil
if _, err := database.Mysql.Exec("update task set end=NOW() where id=?", t.task.ID); err != nil {
fmt.Println("Failed to update task end time")
t.log("Fatal error with database!")
panic(err)
}
}()
if _, err := database.Mysql.Exec("update task set status='running', start=NOW() where id=?", t.task.ID); err != nil {
fmt.Println("Failed to update task start time")
t.log("Fatal error with database!")
panic(err)
}
t.log("Started: " + strconv.Itoa(t.task.ID) + "\n")
if err := t.populateDetails(); err != nil {
t.log("Error: " + err.Error())
t.fail()
return
}
if err := t.installKey(t.repository.SshKey); err != nil {
t.log("Failed installing ssh key for repository access: " + err.Error())
t.fail()
return
}
if err := t.updateRepository(); err != nil {
t.log("Failed updating repository: " + err.Error())
t.fail()
return
}
if err := t.installInventory(); err != nil {
t.log("Failed to install inventory: " + err.Error())
t.fail()
return
}
@ -58,8 +81,14 @@ func (t *task) run() {
if err := t.runPlaybook(); err != nil {
t.log("Running playbook failed: " + err.Error())
t.fail()
return
}
t.task.Status = "success"
if _, err := database.Mysql.Exec("update task set status='success' where id=?", t.task.ID); err != nil {
panic(err)
}
}
func (t *task) fetch(errMsg string, ptr interface{}, query string, args ...interface{}) error {
@ -70,6 +99,7 @@ func (t *task) fetch(errMsg string, ptr interface{}, query string, args ...inter
}
if err != nil {
t.fail()
panic(err)
}