mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Events
- Complete more UI work - Show events on dashboard & project dashboard - Show list of tasks
This commit is contained in:
parent
cd5a978bb9
commit
ea0245b550
10
database/sql_migrations/v1.4.0.sql
Normal file
10
database/sql_migrations/v1.4.0.sql
Normal file
@ -0,0 +1,10 @@
|
||||
CREATE TABLE `event` (
|
||||
`project_id` int(11) DEFAULT NULL,
|
||||
`object_id` int(11) DEFAULT NULL,
|
||||
`object_type` varchar(20) DEFAULT '',
|
||||
`description` text,
|
||||
`created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY `project_id` (`project_id`),
|
||||
KEY `object_id` (`object_id`),
|
||||
KEY `created` (`created`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
@ -59,5 +59,6 @@ func init() {
|
||||
{Major: 1, Minor: 1},
|
||||
{Major: 1, Minor: 2},
|
||||
{Major: 1, Minor: 3},
|
||||
{Major: 1, Minor: 4},
|
||||
}
|
||||
}
|
||||
|
14
models/Event.go
Normal file
14
models/Event.go
Normal file
@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type Event struct {
|
||||
ProjectID *int `db:"project_id" json:"project_id"`
|
||||
ObjectID *int `db:"object_id" json:"object_id"`
|
||||
ObjectType *string `db:"object_type" json:"object_type"`
|
||||
Description *string `db:"description" json:"description"`
|
||||
Created time.Time `db:"created" json:"created"`
|
||||
|
||||
ObjectName string `db:"-" json:"object_name"`
|
||||
ProjectName *string `db:"project_name" json:"project_name"`
|
||||
}
|
@ -1,8 +1,13 @@
|
||||
.container-fluid
|
||||
.row
|
||||
.col-md-8
|
||||
ul
|
||||
li Scheduled job {x} was successfully executed. Took 30s
|
||||
h4.no-top-margin Events
|
||||
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")
|
||||
span(ng-bind="event.object_name")
|
||||
span - {{ event.description }}
|
||||
.col-md-4
|
||||
.panel.panel-default
|
||||
.panel-heading Projects
|
||||
|
@ -1 +1,13 @@
|
||||
h3 Project activity
|
||||
.row
|
||||
.col-sm-7
|
||||
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")
|
||||
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 }}
|
@ -5,26 +5,28 @@
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Name
|
||||
.col-sm-6
|
||||
input.form-control(type="text" ng-model="inventory.name")
|
||||
input.form-control(type="text" ng-model="inventory.name" placeholder="Inventory Name")
|
||||
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Type
|
||||
.col-sm-6
|
||||
select.form-control(ng-model="inventory.type")
|
||||
select.form-control(ng-model="inventory.type" ng-init="inventory.type = 'static'")
|
||||
option(value="static") Static
|
||||
option(disabled value="aws") AWS
|
||||
option(disabled value="do") DigitalOcean
|
||||
option(disabled value="gcloud") Google Cloud
|
||||
.form-group
|
||||
.form-group(ng-if="inventory.type != 'static'")
|
||||
label.control-label.col-sm-4 Remote inventory key
|
||||
.col-sm-6
|
||||
select.form-control(ng-model="inventory.key_id" ng-options="key.id as key.name for key in remote_keys")
|
||||
option(value="") -- Select Key --
|
||||
|
||||
.form-group
|
||||
label.control-label.col-sm-4 SSH Key
|
||||
.col-sm-6
|
||||
select.form-control(ng-model="inventory.ssh_key_id" ng-options="key.id as key.name for key in sshKeys")
|
||||
option(value="") -- Select SSH Key --
|
||||
p.help-block Used to log into the servers in this inventory
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
|
||||
|
9
public/html/projects/inventory/edit.jade
Normal file
9
public/html/projects/inventory/edit.jade
Normal file
@ -0,0 +1,9 @@
|
||||
.modal-header
|
||||
h3.modal-title Edit Inventory
|
||||
|
||||
.modal-body
|
||||
textarea.form-control(ng-model="inventory" rows="20")
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.pull-left(ng-click="$dismiss()") Cancel
|
||||
button.btn.btn-success(ng-click="$close(inventory)") Save Changes
|
@ -10,5 +10,5 @@ table.table
|
||||
tbody: tr(ng-repeat="inv in inventory")
|
||||
td {{ inv.name }}
|
||||
td {{ inv.type }}
|
||||
td: button.btn.btn-default.btn-xs(ng-click="editInventory(inv)") edit inventory
|
||||
td: button.btn.btn-default.btn-xs(ng-click="edit(inv)") edit inventory
|
||||
td: button.btn.btn-danger.btn-xs.pull-right(ng-click="remove(inv)") delete
|
@ -8,9 +8,11 @@ table.table
|
||||
th Inventory
|
||||
th Environment
|
||||
th Repository
|
||||
th
|
||||
tbody: tr(ng-repeat="tpl in templates")
|
||||
td {{ tpl.playbook }}
|
||||
td {{ sshKeysAssoc[tpl.ssh_key_id].name }}
|
||||
td {{ inventoryAssoc[tpl.inventory_id].name }}
|
||||
td {{ environmentAssoc[tpl.environment_id].name }}
|
||||
td {{ reposAssoc[tpl.repository_id].name }}
|
||||
td {{ reposAssoc[tpl.repository_id].name }}
|
||||
td: button.btn.btn-danger.btn-xs.pull-right(ng-click="remove(tpl)") remove
|
@ -4,7 +4,11 @@ define(['controllers/projects/edit'], function () {
|
||||
|
||||
$http.get('/projects').success(function (projects) {
|
||||
$scope.projects = projects;
|
||||
})
|
||||
});
|
||||
|
||||
$http.get('/events').success(function (events) {
|
||||
$scope.events = events;
|
||||
});
|
||||
|
||||
$scope.addProject = function () {
|
||||
$modal.open({
|
||||
|
@ -1,4 +1,7 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectDashboardCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
app.registerController('ProjectDashboardCtrl', ['$scope', '$http', 'Project', function ($scope, $http, Project) {
|
||||
$http.get(Project.getURL() + '/events').success(function (events) {
|
||||
$scope.events = events;
|
||||
});
|
||||
}]);
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectInventoryCtrl', ['$scope', '$http', '$uibModal', 'Project', function ($scope, $http, $modal, Project) {
|
||||
app.registerController('ProjectInventoryCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', function ($scope, $http, $modal, Project, $rootScope) {
|
||||
$scope.reload = function () {
|
||||
$http.get(Project.getURL() + '/inventory').success(function (inventory) {
|
||||
$scope.inventory = inventory;
|
||||
@ -14,6 +14,43 @@ define(function () {
|
||||
});
|
||||
}
|
||||
|
||||
$scope.add = function () {
|
||||
$http.get(Project.getURL() + '/keys?type=ssh').success(function (keys) {
|
||||
var scope = $rootScope.$new();
|
||||
scope.sshKeys = keys;
|
||||
|
||||
$modal.open({
|
||||
templateUrl: '/tpl/projects/inventory/add.html',
|
||||
scope: scope
|
||||
}).result.then(function (inventory) {
|
||||
$http.post(Project.getURL() + '/inventory', inventory)
|
||||
.success(function () {
|
||||
$scope.reload();
|
||||
}).error(function (_, status) {
|
||||
swal('Erorr', 'Inventory not added: ' + status, 'error');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.edit = function (inventory) {
|
||||
var scope = $rootScope.$new();
|
||||
scope.inventory = inventory.inventory;
|
||||
|
||||
$modal.open({
|
||||
templateUrl: '/tpl/projects/inventory/edit.html',
|
||||
scope: scope
|
||||
}).result.then(function (v) {
|
||||
inventory.inventory = v;
|
||||
$http.put(Project.getURL() + '/inventory/' + inventory.id, inventory)
|
||||
.success(function () {
|
||||
$scope.reload();
|
||||
}).error(function (_, status) {
|
||||
swal('Erorr', 'Inventory not updated: ' + status, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reload();
|
||||
}]);
|
||||
});
|
@ -39,8 +39,8 @@ define(function () {
|
||||
});
|
||||
}
|
||||
|
||||
$scope.remove = function (environment) {
|
||||
$http.delete(Project.getURL() + '/templates/' + environment.id).success(function () {
|
||||
$scope.remove = function (template) {
|
||||
$http.delete(Project.getURL() + '/templates/' + template.id).success(function () {
|
||||
$scope.reload();
|
||||
}).error(function () {
|
||||
swal('error', 'could not delete template..', 'error');
|
||||
|
63
routes/events.go
Normal file
63
routes/events.go
Normal file
@ -0,0 +1,63 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/masterminds/squirrel"
|
||||
)
|
||||
|
||||
func getEvents(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
q := squirrel.Select("event.*, p.name as project_name").
|
||||
From("event").
|
||||
LeftJoin("project as p on event.project_id=p.id").
|
||||
OrderBy("created desc")
|
||||
|
||||
projectObj, exists := c.Get("event")
|
||||
if exists == true {
|
||||
// limit query to project
|
||||
project := projectObj.(models.Project)
|
||||
q = q.Where("project_id=?", project.ID)
|
||||
} else {
|
||||
q = q.LeftJoin("project__user as pu on pu.project_id=p.id").
|
||||
Where("p.id IS NULL or pu.user_id=?", user.ID)
|
||||
}
|
||||
|
||||
var events []models.Event
|
||||
|
||||
query, args, _ := q.ToSql()
|
||||
if _, err := database.Mysql.Select(&events, query, args...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for i, evt := range events {
|
||||
if evt.ObjectID == nil || evt.ObjectType == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var q squirrel.SelectBuilder
|
||||
|
||||
switch *evt.ObjectType {
|
||||
case "task":
|
||||
q = squirrel.Select("tpl.playbook as name").
|
||||
From("task").
|
||||
Join("project__template as tpl on task.template_id=tpl.id")
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
query, args, _ := q.ToSql()
|
||||
name, err := database.Mysql.SelectNullStr(query, args...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if name.Valid == true {
|
||||
events[i].ObjectName = name.String
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, events)
|
||||
}
|
@ -55,7 +55,13 @@ func GetInventory(c *gin.Context) {
|
||||
|
||||
func AddInventory(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
var inventory models.Inventory
|
||||
var inventory struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
KeyID *int `json:"key_id"`
|
||||
SshKeyID int `json:"ssh_key_id"`
|
||||
Type string `json:"type"`
|
||||
Inventory string `json:"inventory"`
|
||||
}
|
||||
|
||||
if err := c.Bind(&inventory); err != nil {
|
||||
return
|
||||
@ -69,7 +75,7 @@ func AddInventory(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("insert into project__inventory set project_id=?, type=?, key_id=?, inventory=?", project.ID, inventory.Type, inventory.KeyID, inventory.Inventory); err != nil {
|
||||
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 {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -77,7 +83,33 @@ func AddInventory(c *gin.Context) {
|
||||
}
|
||||
|
||||
func UpdateInventory(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
oldInventory := c.MustGet("inventory").(models.Inventory)
|
||||
|
||||
var inventory struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
KeyID *int `json:"key_id"`
|
||||
SshKeyID int `json:"ssh_key_id"`
|
||||
Type string `json:"type"`
|
||||
Inventory string `json:"inventory"`
|
||||
}
|
||||
|
||||
if err := c.Bind(&inventory); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch inventory.Type {
|
||||
case "static", "aws", "do", "gcloud":
|
||||
break
|
||||
default:
|
||||
c.AbortWithStatus(400)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("update project__inventory set name=?, type=?, key_id=?, ssh_key_id=?, inventory=? where id=?", inventory.Name, inventory.Type, inventory.KeyID, inventory.SshKeyID, inventory.Inventory, oldInventory.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
||||
func RemoveInventory(c *gin.Context) {
|
||||
|
@ -1,14 +1,34 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/masterminds/squirrel"
|
||||
)
|
||||
|
||||
func TemplatesMiddleware(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
project := c.MustGet("project").(models.Project)
|
||||
templateID, err := util.GetIntParam("template_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var template models.Template
|
||||
if err := database.Mysql.SelectOne(&template, "select * from project__template where project_id=? and id=?", project.ID, templateID); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Set("template", template)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func GetTemplates(c *gin.Context) {
|
||||
@ -29,13 +49,49 @@ func GetTemplates(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddTemplate(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
project := c.MustGet("project").(models.Project)
|
||||
|
||||
var template models.Template
|
||||
if err := c.Bind(&template); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := database.Mysql.Exec("insert into project__template set ssh_key_id=?, project_id=?, inventory_id=?, repository_id=?, environment_id=?, playbook=?, arguments=?, override_args=?", template.SshKeyID, project.ID, template.InventoryID, template.RepositoryID, template.EnvironmentID, template.Playbook, template.Arguments, template.OverrideArguments)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
insertID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
template.ID = int(insertID)
|
||||
|
||||
c.JSON(201, template)
|
||||
}
|
||||
|
||||
func UpdateTemplate(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
oldTemplate := c.MustGet("template").(models.Template)
|
||||
|
||||
var template models.Template
|
||||
if err := c.Bind(&template); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("update project__template set ssh_key_id=?, inventory_id=?, repository_id=?, environment_id=?, playbook=?, arguments=?, override_args=? where id=?", template.SshKeyID, template.InventoryID, template.RepositoryID, template.EnvironmentID, template.Playbook, template.Arguments, template.OverrideArguments, oldTemplate.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
||||
func RemoveTemplate(c *gin.Context) {
|
||||
c.AbortWithStatus(501)
|
||||
tpl := c.MustGet("template").(models.Template)
|
||||
|
||||
if _, err := database.Mysql.Exec("delete from project__template where id=?", tpl.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.AbortWithStatus(204)
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ func Route(r *gin.Engine) {
|
||||
|
||||
api.GET("/projects", projects.GetProjects)
|
||||
api.POST("/projects", projects.AddProject)
|
||||
api.GET("/events", getEvents)
|
||||
|
||||
api.GET("/users", getUsers)
|
||||
api.POST("/users", addUser)
|
||||
@ -54,6 +55,8 @@ func Route(r *gin.Engine) {
|
||||
|
||||
api.GET("", projects.GetProject)
|
||||
|
||||
api.GET("/events", getEvents)
|
||||
|
||||
api.GET("/users", projects.GetUsers)
|
||||
api.POST("/users", projects.AddUser)
|
||||
api.POST("/users/:user_id/admin", projects.UserMiddleware, projects.MakeUserAdmin)
|
||||
|
41
swagger.yml
41
swagger.yml
@ -169,6 +169,17 @@ definitions:
|
||||
type: string
|
||||
override_args:
|
||||
type: boolean
|
||||
Event:
|
||||
type: object
|
||||
properties:
|
||||
project_id:
|
||||
type: integer
|
||||
object_id:
|
||||
type: integer
|
||||
object_type:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
|
||||
# securityDefinitions:
|
||||
# cookie:
|
||||
@ -351,6 +362,17 @@ paths:
|
||||
responses:
|
||||
200:
|
||||
description: Created project
|
||||
/events:
|
||||
get:
|
||||
summary: Get Events related to Semaphore and projects you are part of
|
||||
responses:
|
||||
200:
|
||||
description: Array of events in chronological order
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Event'
|
||||
|
||||
/project/{project_id}:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
@ -364,6 +386,21 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/Project"
|
||||
|
||||
/project/{project_id}/events:
|
||||
parameters:
|
||||
- $ref: '#/parameters/project_id'
|
||||
get:
|
||||
tags:
|
||||
- project
|
||||
summary: Get Events related to this project
|
||||
responses:
|
||||
200:
|
||||
description: Array of events in chronological order
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/Event'
|
||||
|
||||
# User management
|
||||
/project/{project_id}/users:
|
||||
parameters:
|
||||
@ -664,8 +701,10 @@ paths:
|
||||
schema:
|
||||
$ref: "#/definitions/Template"
|
||||
responses:
|
||||
204:
|
||||
201:
|
||||
description: template created
|
||||
schema:
|
||||
$ref: "#/definitions/Template"
|
||||
/project/{project_id}/template/{template_id}:
|
||||
parameters:
|
||||
- $ref: "#/parameters/project_id"
|
||||
|
Loading…
Reference in New Issue
Block a user