- Complete more UI work
- Show events on dashboard & project dashboard
- Show list of tasks
This commit is contained in:
Matej Kramny 2016-04-16 20:42:57 +01:00
parent cd5a978bb9
commit ea0245b550
18 changed files with 313 additions and 21 deletions

View 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;

View File

@ -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
View 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"`
}

View File

@ -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

View File

@ -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 }}

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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({

View File

@ -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;
});
}]);
});

View File

@ -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();
}]);
});

View File

@ -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
View 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)
}

View File

@ -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) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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"