mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Events, task history, task details
This commit is contained in:
parent
ea0245b550
commit
1805c32e90
@ -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;
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
23
public/html/projects/createTaskModal.jade
Normal file
23
public/html/projects/createTaskModal.jade
Normal 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!
|
@ -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")
|
||||
time(ng-bind="event.created | date:'short'")
|
||||
span :
|
||||
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'")
|
||||
|
|
||||
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 }}
|
||||
|
8
public/html/projects/taskModal.jade
Normal file
8
public/html/projects/taskModal.jade
Normal 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
|
@ -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
|
@ -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
|
||||
});
|
||||
}
|
||||
}]);
|
||||
});
|
18
public/js/controllers/projects/taskRunner.js
Normal file
18
public/js/controllers/projects/taskRunner.js
Normal 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) {
|
||||
}]);
|
||||
});
|
@ -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();
|
||||
}]);
|
||||
});
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"))
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user