mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 23:39:56 +01:00
Merge branch 'develop' into create_tmp_dir
This commit is contained in:
commit
a2c207c6d9
@ -55,6 +55,8 @@ definitions:
|
||||
format: date-time
|
||||
alert:
|
||||
type: boolean
|
||||
admin:
|
||||
type: boolean
|
||||
APIToken:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/ansible-semaphore/semaphore/db"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/castawaylabs/mulekick"
|
||||
@ -118,6 +119,13 @@ func GetTaskOutput(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func RemoveTask(w http.ResponseWriter, r *http.Request) {
|
||||
task := context.Get(r, "task").(db.Task)
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
|
||||
if editor.Admin != true {
|
||||
log.Warn(editor.Username + " is not permitted to delete task logs")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
statements := []string{
|
||||
"delete from task__output where task_id=?",
|
||||
|
@ -64,16 +64,38 @@ func (t *task) updateStatus() {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *task) logPipe(scanner *bufio.Scanner) {
|
||||
for scanner.Scan() {
|
||||
t.log(scanner.Text())
|
||||
func Readln(r *bufio.Reader) (string, error) {
|
||||
var (
|
||||
isPrefix bool = true
|
||||
err error = nil
|
||||
line, ln []byte
|
||||
)
|
||||
for isPrefix && err == nil {
|
||||
line, isPrefix, err = r.ReadLine()
|
||||
ln = append(ln, line...)
|
||||
}
|
||||
return string(ln), err
|
||||
}
|
||||
|
||||
func (t *task) logPipe(reader *bufio.Reader) {
|
||||
|
||||
line, err := Readln(reader)
|
||||
for err == nil {
|
||||
t.log(line)
|
||||
line, err = Readln(reader)
|
||||
}
|
||||
|
||||
if err != nil && err.Error() != "EOF" {
|
||||
//don't panic on this errors, sometimes it throw not dangerous "read |0: file already closed" error
|
||||
fmt.Printf("Failed to read task output: %s\n", err.Error())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *task) logCmd(cmd *exec.Cmd) {
|
||||
stderr, _ := cmd.StderrPipe()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
go t.logPipe(bufio.NewScanner(stderr))
|
||||
go t.logPipe(bufio.NewScanner(stdout))
|
||||
go t.logPipe(bufio.NewReader(stderr))
|
||||
go t.logPipe(bufio.NewReader(stdout))
|
||||
}
|
||||
|
@ -105,6 +105,12 @@ func (t *task) prepareRun() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.runGalaxy(); err != nil {
|
||||
t.log("Running galaxy failed: " + err.Error())
|
||||
t.fail()
|
||||
return
|
||||
}
|
||||
|
||||
// todo: write environment
|
||||
|
||||
if err := t.listPlaybookHosts(); err != nil {
|
||||
@ -161,12 +167,6 @@ func (t *task) run() {
|
||||
t.log("Started: " + strconv.Itoa(t.task.ID))
|
||||
t.log("Run task with template: " + t.template.Alias + "\n")
|
||||
|
||||
if err := t.runGalaxy(); err != nil {
|
||||
t.log("Running galaxy failed: " + err.Error())
|
||||
t.fail()
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.runPlaybook(); err != nil {
|
||||
t.log("Running playbook failed: " + err.Error())
|
||||
t.fail()
|
||||
@ -379,6 +379,7 @@ func (t *task) runPlaybook() error {
|
||||
cmd.Env = t.envVars(util.Config.TmpPath, cmd.Dir, nil)
|
||||
|
||||
t.logCmd(cmd)
|
||||
cmd.Stdin = strings.NewReader("")
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
|
45
api/users.go
45
api/users.go
@ -28,6 +28,13 @@ func addUser(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
if editor.Admin != true {
|
||||
log.Warn(editor.Username + " is not permitted to create users")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
user.Created = time.Now()
|
||||
|
||||
if err := db.Mysql.Insert(&user); err != nil {
|
||||
@ -53,23 +60,44 @@ func getUserMiddleware(w http.ResponseWriter, r *http.Request) {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
if editor.Admin != true && editor.ID != user.ID {
|
||||
log.Warn(editor.Username + " is not permitted to edit users")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
context.Set(r, "_user", user)
|
||||
}
|
||||
|
||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
oldUser := context.Get(r, "_user").(db.User)
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
|
||||
var user db.User
|
||||
if err := mulekick.Bind(w, r, &user); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if editor.Admin != true && editor.ID != oldUser.ID {
|
||||
log.Warn(editor.Username + " is not permitted to edit users")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if editor.ID == oldUser.ID && oldUser.Admin != user.Admin {
|
||||
log.Warn("User can't edit his own role")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if oldUser.External == true && oldUser.Username != user.Username {
|
||||
log.Warn("Username is not editable for external LDAP users")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=?, alert=? where id=?", user.Name, user.Username, user.Email, user.Alert, oldUser.ID); err != nil {
|
||||
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=?, alert=?, admin=? where id=?", user.Name, user.Username, user.Email, user.Alert, user.Admin, oldUser.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@ -78,10 +106,18 @@ func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func updateUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||
user := context.Get(r, "_user").(db.User)
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
|
||||
var pwd struct {
|
||||
Pwd string `json:"password"`
|
||||
}
|
||||
|
||||
if editor.Admin != true && editor.ID != user.ID {
|
||||
log.Warn(editor.Username + " is not permitted to edit users")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if user.External == true {
|
||||
log.Warn("Password is not editable for external LDAP users")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
@ -102,6 +138,13 @@ func updateUserPassword(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func deleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
user := context.Get(r, "_user").(db.User)
|
||||
editor := context.Get(r, "user").(*db.User)
|
||||
|
||||
if editor.Admin != true && editor.ID != user.ID {
|
||||
log.Warn(editor.Username + " is not permitted to delete users")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := db.Mysql.Exec("delete from project__user where user_id=?", user.ID); err != nil {
|
||||
panic(err)
|
||||
|
@ -143,7 +143,7 @@ func doSetup() int {
|
||||
user.Password = readNewline(" > Password: ", stdin)
|
||||
pwdHash, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 11)
|
||||
|
||||
if _, err := db.Mysql.Exec("insert into user set name=?, username=?, email=?, password=?, created=UTC_TIMESTAMP()", user.Name, user.Username, user.Email, pwdHash); err != nil {
|
||||
if _, err := db.Mysql.Exec("insert into user set name=?, username=?, email=?, password=?, admin=1, created=UTC_TIMESTAMP()", user.Name, user.Username, user.Email, pwdHash); err != nil {
|
||||
fmt.Printf(" Inserting user failed. If you already have a user, you can disregard this error.\n %v\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ type User struct {
|
||||
Name string `db:"name" json:"name" binding:"required"`
|
||||
Email string `db:"email" json:"email" binding:"required"`
|
||||
Password string `db:"password" json:"-"`
|
||||
Admin bool `db:"admin" json:"admin"`
|
||||
External bool `db:"external" json:"external"`
|
||||
Alert bool `db:"alert" json:"alert"`
|
||||
}
|
||||
|
1
db/migrations/v2.5.0.sql
Normal file
1
db/migrations/v2.5.0.sql
Normal file
@ -0,0 +1 @@
|
||||
ALTER TABLE user ADD admin BOOLEAN NOT NULL DEFAULT 1 AFTER password;
|
@ -71,5 +71,6 @@ func init() {
|
||||
{Major: 2, Minor: 3, Patch: 1},
|
||||
{Major: 2, Minor: 3, Patch: 2},
|
||||
{Major: 2, Minor: 4},
|
||||
{Major: 2, Minor: 5},
|
||||
}
|
||||
}
|
||||
|
@ -12,10 +12,13 @@
|
||||
dd {{ task.endFormatted }}
|
||||
dt Created
|
||||
dd {{ task.createdFormatted }}
|
||||
dt Permalink
|
||||
dd
|
||||
a(href="{{ task.URL }}") Output
|
||||
dt Raw output
|
||||
dd: input(type="checkbox" ng-model="raw" title="show logs unbesmirched")
|
||||
|
||||
textarea.scroll(disabled, scroll-glue) {{ output_formatted }}
|
||||
textarea.scroll(readonly, scroll-glue) {{ output_formatted }}
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
|
||||
|
@ -1,5 +1,7 @@
|
||||
h3 Task Templates
|
||||
button.btn.btn-success.btn-xs.pull-right(ng-click="add()") New Template
|
||||
button.btn.btn-success.btn-xs.pull-right(ng-click="add()" style="margin-left: 5px;") New Template
|
||||
button.btn.btn-default.btn-xs.pull-right(ng-if="allShown && hasHiddenTemplates()" ng-click="hideHidden()") Hide Hidden
|
||||
button.btn.btn-default.btn-xs.pull-right(ng-if="!allShown && hasHiddenTemplates()" ng-click="showAll()") Show Hidden
|
||||
|
||||
table.table.table-hover
|
||||
thead: tr
|
||||
@ -10,7 +12,7 @@ table.table.table-hover
|
||||
th Environment
|
||||
th Repository
|
||||
th
|
||||
tbody: tr(ng-repeat="tpl in templates" ng-click="update(tpl)" style="cursor: pointer;")
|
||||
tbody: tr(ng-repeat="tpl in templates" ng-click="update(tpl)" style="cursor: pointer;" ng-if="!tpl.hidden || allShown")
|
||||
td {{ tpl.alias }}
|
||||
td {{ tpl.playbook }}
|
||||
td {{ sshKeysAssoc[tpl.ssh_key_id].name }}
|
||||
@ -18,5 +20,7 @@ table.table.table-hover
|
||||
td {{ environmentAssoc[tpl.environment_id].name }}
|
||||
td {{ reposAssoc[tpl.repository_id].name }}
|
||||
td: .pull-right
|
||||
button.btn.btn-info.btn-xs(ng-click="copy(tpl); $event.stopPropagation();") copy
|
||||
button.btn.btn-success.btn-xs(ng-click="run(tpl); $event.stopPropagation();", style="margin-left: 5px;") run
|
||||
button.btn.btn-default.btn-xs(ng-if="!tpl.hidden" ng-click="hideTemplate(tpl); $event.stopPropagation();") hide
|
||||
button.btn.btn-default.btn-xs(ng-if="tpl.hidden" ng-click="showTemplate(tpl); $event.stopPropagation();") show
|
||||
button.btn.btn-info.btn-xs(ng-click="copy(tpl); $event.stopPropagation();" style="margin-left: 5px;") copy
|
||||
button.btn.btn-success.btn-xs(ng-click="run(tpl); $event.stopPropagation();" style="margin-left: 5px;") run
|
||||
|
@ -20,6 +20,15 @@
|
||||
.col-sm-6
|
||||
input#password.form-control(type="password" placeholder="User Password" ng-model="user.password" required)
|
||||
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4: .checkbox: label
|
||||
input#admin(type="checkbox" title="User have admin privileges" ng-model="user.admin")
|
||||
| Admin user
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4: .checkbox: label
|
||||
input#alert(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")
|
||||
| Send alerts
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
|
||||
button.btn.btn-success(ng-click="$close(user)") Create User
|
||||
|
@ -1,15 +1,21 @@
|
||||
.container-fluid: .row: .col-sm-12
|
||||
h3.no-top-margin Users
|
||||
button.btn.btn-primary.pull-right(ng-click="addUser()") New User
|
||||
button.btn.btn-primary.pull-right(ng-click="addUser()" ng-if="user.admin == true") New User
|
||||
|
||||
table.table.table-hover
|
||||
thead: tr
|
||||
th Name
|
||||
th Username
|
||||
th Email
|
||||
th Alert
|
||||
th Admin
|
||||
th External
|
||||
tr(ng-repeat="u in users" ng-class="{ info: u.id == user.id }" ui-sref="users.user({ user_id: u.id })" style="cursor: pointer;")
|
||||
td {{ u.name }}
|
||||
td {{ u.username }}
|
||||
td {{ u.email }}
|
||||
td {{ u.alert }}
|
||||
td {{ u.admin }}
|
||||
td {{ u.external }}
|
||||
|
||||
p(ng-show="users.length == 0") No Users
|
||||
|
@ -15,6 +15,10 @@
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Password
|
||||
.col-sm-8: input.form-control(type="password" placeholder="Enter new password" ng-readonly="user.external == true" ng-model="user.password")
|
||||
.form-group(ng-if="!is_self")
|
||||
.col-sm-8.col-sm-offset-4: .checkbox: label
|
||||
input(type="checkbox" title="User has admin privileges" ng-model="user.admin")
|
||||
| Admin user
|
||||
.form-group
|
||||
.col-sm-8.col-sm-offset-4: .checkbox: label
|
||||
input(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")
|
||||
|
@ -44,7 +44,7 @@ define(['controllers/projects/taskRunner'], function() {
|
||||
if (!t.start || !t.end) {
|
||||
return;
|
||||
}
|
||||
|
||||
t.URL = '/api' + $scope.project.getURL() + '/tasks/' + t.id + '/output'
|
||||
t.duration = moment(t.end).diff(moment(t.start), 'minutes');
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
define(['controllers/projects/taskRunner'], function () {
|
||||
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', function ($scope, $http, $modal, Project, $rootScope) {
|
||||
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', '$uibModal', 'Project', '$rootScope', '$window', function ($scope, $http, $modal, Project, $rootScope, $window) {
|
||||
$http.get(Project.getURL() + '/keys?type=ssh').success(function (keys) {
|
||||
$scope.sshKeys = keys;
|
||||
|
||||
@ -39,8 +39,31 @@ define(['controllers/projects/taskRunner'], function () {
|
||||
});
|
||||
});
|
||||
|
||||
function getHiddenTemplates() {
|
||||
try {
|
||||
return JSON.parse($window.localStorage.getItem('hidden-templates') || '[]');
|
||||
} catch(e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function setHiddenTemplates(hiddenTemplates) {
|
||||
$window.localStorage.setItem('hidden-templates', JSON.stringify(hiddenTemplates));
|
||||
}
|
||||
|
||||
$scope.hasHiddenTemplates = function() {
|
||||
return getHiddenTemplates().length > 0;
|
||||
}
|
||||
|
||||
$scope.reload = function () {
|
||||
$http.get(Project.getURL() + '/templates?sort=alias&order=asc').success(function (templates) {
|
||||
var hiddenTemplates = getHiddenTemplates();
|
||||
for (var i = 0; i < templates.length; i++) {
|
||||
var template = templates[i];
|
||||
if (hiddenTemplates.indexOf(template.id) !== -1) {
|
||||
template.hidden = true;
|
||||
}
|
||||
}
|
||||
$scope.templates = templates;
|
||||
});
|
||||
}
|
||||
@ -96,7 +119,7 @@ define(['controllers/projects/taskRunner'], function () {
|
||||
swal('error', 'could not add template:' + status, 'error');
|
||||
});
|
||||
}).closed.then(function () {
|
||||
$scope.reload();
|
||||
$scope.reload();
|
||||
});
|
||||
}
|
||||
|
||||
@ -126,6 +149,33 @@ define(['controllers/projects/taskRunner'], function () {
|
||||
})
|
||||
}
|
||||
|
||||
$scope.showAll = function() {
|
||||
$scope.allShown = true;
|
||||
}
|
||||
|
||||
$scope.hideHidden = function() {
|
||||
$scope.allShown = false;
|
||||
}
|
||||
|
||||
$scope.hideTemplate = function(template) {
|
||||
var hiddenTemplates = getHiddenTemplates();
|
||||
if (hiddenTemplates.indexOf(template.id) === -1) {
|
||||
hiddenTemplates.push(template.id);
|
||||
}
|
||||
setHiddenTemplates(hiddenTemplates);
|
||||
template.hidden = true;
|
||||
}
|
||||
|
||||
$scope.showTemplate = function(template) {
|
||||
var hiddenTemplates = getHiddenTemplates();
|
||||
var i = hiddenTemplates.indexOf(template.id);
|
||||
if (i !== -1) {
|
||||
hiddenTemplates.splice(i, 1);
|
||||
}
|
||||
setHiddenTemplates(hiddenTemplates);
|
||||
delete template.hidden;
|
||||
}
|
||||
|
||||
$scope.copy = function (template) {
|
||||
var tpl = angular.copy(template);
|
||||
tpl.id = null;
|
||||
@ -152,4 +202,4 @@ define(['controllers/projects/taskRunner'], function () {
|
||||
|
||||
$scope.reload();
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
152982
util/bindata.go
152982
util/bindata.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user