mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Project ui, ws
- [wip] ws support
This commit is contained in:
parent
121567fa03
commit
79a05b389f
@ -6,8 +6,8 @@ create table user (
|
||||
`email` varchar(255) not null comment "Email, unique",
|
||||
`password` varchar(255) not null comment "Password",
|
||||
|
||||
UNIQUE KEY `username` (`username`),
|
||||
UNIQUE KEY `email` (`email`)
|
||||
unique key `username` (`username`),
|
||||
unique key `email` (`email`)
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project (
|
||||
@ -21,6 +21,91 @@ create table project__user (
|
||||
`user_id` varchar (255) not null comment "User ID",
|
||||
`admin` tinyint (1) not null default 0 comment 'Gives user god-like privileges',
|
||||
|
||||
UNIQUE KEY `id` (`project_id`, `user_id`)
|
||||
unique key `id` (`project_id`, `user_id`),
|
||||
foreign key (`project_id`) references project(`id`) on delete cascade,
|
||||
foreign key (`user_id`) references user(`id`) on delete cascade
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table access_key (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`name` varchar(255) not null,
|
||||
`type` varchar(255) not null comment 'aws/do/gcloud',
|
||||
|
||||
`project_id` int(11) null,
|
||||
`key` text null,
|
||||
`secret` text null,
|
||||
|
||||
foreign key (`project_id`) references project(`id`) on delete set null
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project__repository (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`project_id` int(11) not null,
|
||||
`git_url` text not null,
|
||||
`ssh_key_id` int(11) not null,
|
||||
|
||||
foreign key (`project_id`) references project(`id`) on delete cascade,
|
||||
foreign key (`ssh_key_id`) references access_key(`id`)
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project__inventory (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`project_id` int(11) not null,
|
||||
`type` varchar(255) not null comment 'can be static/aws/do/gcloud',
|
||||
`key_id` int(11) not null comment 'references keys to authenticate remote services',
|
||||
`inventory` longtext not null,
|
||||
|
||||
foreign key (`project_id`) references project(`id`) on delete cascade,
|
||||
foreign key (`key_id`) references access_key(`id`)
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project__environment (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`project_id` int(11) not null,
|
||||
`password` varchar(255) null,
|
||||
`json` longtext not null,
|
||||
|
||||
foreign key (`project_id`) references project(`id`) on delete cascade
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project__template (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`ssh_key_id` int(11) not null comment 'for accessing the inventory',
|
||||
`project_id` int(11) not null,
|
||||
`inventory_id` int(11) not null,
|
||||
`repository_id` int(11) not null,
|
||||
`environment_id` int(11) not null,
|
||||
`playbook` varchar(255) not null comment 'playbook name (ansible.yml)',
|
||||
|
||||
foreign key (`project_id`) references project(`id`) on delete cascade,
|
||||
foreign key (`ssh_key_id`) references access_key(`id`),
|
||||
foreign key (`inventory_id`) references project__inventory(`id`),
|
||||
foreign key (`repository_id`) references project__repository(`id`),
|
||||
foreign key (`environment_id`) references project__environment(`id`)
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table project__template_schedule (
|
||||
`template_id` int(11) not null,
|
||||
`cron_format` varchar(255) not null,
|
||||
|
||||
foreign key (`template_id`) references project__template(`id`) on delete cascade
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table task (
|
||||
`id` int(11) not null primary key auto_increment,
|
||||
`template_id` int(11) not null,
|
||||
`status` varchar(255) not null,
|
||||
`playbook` varchar(255) not null comment 'override playbook name (ansible.yml)',
|
||||
`environment` longtext null comment 'override environment',
|
||||
|
||||
foreign key (`template_id`) references project__template(`id`)
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
||||
|
||||
create table task__output (
|
||||
`task_id` int(11) not null,
|
||||
`time` datetime not null default NOW(),
|
||||
`output` longtext not null,
|
||||
|
||||
unique key `id` (`task_id`, `time`),
|
||||
foreign key (`task_id`) references task(`id`) on delete cascade
|
||||
) ENGINE=InnoDB CHARSET=utf8;
|
2
main.go
2
main.go
@ -6,6 +6,7 @@ import (
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/migration"
|
||||
"github.com/ansible-semaphore/semaphore/routes"
|
||||
"github.com/ansible-semaphore/semaphore/routes/sockets"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/bugsnag/bugsnag-go"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -29,6 +30,7 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
go sockets.StartWS()
|
||||
r := gin.New()
|
||||
r.Use(gin.Recovery(), recovery, gin.Logger())
|
||||
|
||||
|
30
models/Project.go
Normal file
30
models/Project.go
Normal file
@ -0,0 +1,30 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Created time.Time `json:"created"`
|
||||
}
|
||||
|
||||
func (project *Project) CreateProject() error {
|
||||
res, err := database.Mysql.Exec("insert into project set name=?", project.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
projectID, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project.ID = int(projectID)
|
||||
project.Created = time.Now()
|
||||
|
||||
return nil
|
||||
}
|
@ -12,7 +12,7 @@ type User struct {
|
||||
Username string `db:"username" json:"username"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Email string `db:"email" json:"email"`
|
||||
Password string `db:"password" json:"password"`
|
||||
Password string `db:"password" json:"-"`
|
||||
}
|
||||
|
||||
func FetchUser(userID int) (*User, error) {
|
||||
|
@ -10,10 +10,13 @@
|
||||
"app",
|
||||
"routes/router",
|
||||
"routes/auth",
|
||||
"routes/project",
|
||||
"routes/identities",
|
||||
"routes/playbooks",
|
||||
"routes/users",
|
||||
|
||||
"factories/project",
|
||||
|
||||
"/vendor/require.min",
|
||||
"../require_config"
|
||||
]
|
@ -6,6 +6,6 @@
|
||||
.col-md-4
|
||||
.panel.panel-default
|
||||
.panel-heading Projects
|
||||
button.btn.btn-default.btn-xs.pull-right: i.fa.fa-fw.fa-plus
|
||||
button.btn.btn-default.btn-xs.pull-right(ng-click="addProject()"): i.fa.fa-fw.fa-plus
|
||||
ul.list-group
|
||||
li.list-group-item(ng-repeat="project in projects") {{ project.name }}
|
||||
li.list-group-item(ng-repeat="project in projects"): a(ui-sref="project.dashboard({ project_id: project.id })") {{ project.name }}
|
@ -12,7 +12,13 @@ html(lang="en" ng-app="semaphore")
|
||||
body
|
||||
.navbar.navbar-default(ng-if="loggedIn"): .container-fluid
|
||||
.navbar-header
|
||||
a.navbar-brand(ui-sref="homepage") SEMAPHORE
|
||||
a.navbar-brand(ui-sref="dashboard") SEMAPHORE
|
||||
|
||||
ul.nav.navbar-nav
|
||||
li(ng-class="{ active: $state.includes('dashboard') }")
|
||||
a(ui-sref="dashboard") Dashboard
|
||||
li(ng-class="{ active: $state.includes('keys') }")
|
||||
a(ui-sref="keys") Key Store
|
||||
|
||||
ul.nav.navbar-nav.navbar-right(style="margin-right: 0;")
|
||||
li(uib-dropdown)
|
||||
|
13
public/html/projects/add.jade
Normal file
13
public/html/projects/add.jade
Normal file
@ -0,0 +1,13 @@
|
||||
.modal-header
|
||||
h4.modal-title Add Project
|
||||
|
||||
.modal-body
|
||||
form.form-horizontal
|
||||
.form-group
|
||||
label.control-label.col-sm-4 Name
|
||||
.col-sm-6
|
||||
input.form-control(type="text" placeholder="Project Name" ng-model="project.name" required)
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default.pull-left(ng-click="$dismiss()") Dismiss
|
||||
button.btn.btn-success(ng-click="$close(project)") Save
|
14
public/html/projects/container.jade
Normal file
14
public/html/projects/container.jade
Normal file
@ -0,0 +1,14 @@
|
||||
.container-fluid: .row
|
||||
.col-sm-4.col-md-3
|
||||
h3.no-top-margin {{ project.name }}
|
||||
ul.list-group
|
||||
li.list-group-item: a(ui-sref="project.dashboard") Dashboard
|
||||
li.list-group-item: a(ui-sref="project.templates") Task Templates
|
||||
li.list-group-item: a(ui-sref="project.inventory") Inventory
|
||||
li.list-group-item: a(ui-sref="project.environment") Environment
|
||||
li.list-group-item: a(ui-sref="project.keys") Key Store
|
||||
li.list-group-item: a(ui-sref="project.repositories") Playbook Repositories
|
||||
li.list-group-item: a(ui-sref="project.schedule") Task Schedule
|
||||
li.list-group-item: a(ui-sref="project.users") Team
|
||||
.col-sm-8.col-md-9
|
||||
ui-view
|
1
public/html/projects/dashboard.jade
Normal file
1
public/html/projects/dashboard.jade
Normal file
@ -0,0 +1 @@
|
||||
h4 Project activity
|
0
public/html/projects/environment.jade
Normal file
0
public/html/projects/environment.jade
Normal file
0
public/html/projects/inventory.jade
Normal file
0
public/html/projects/inventory.jade
Normal file
0
public/html/projects/keys.jade
Normal file
0
public/html/projects/keys.jade
Normal file
0
public/html/projects/repositories.jade
Normal file
0
public/html/projects/repositories.jade
Normal file
0
public/html/projects/schedule.jade
Normal file
0
public/html/projects/schedule.jade
Normal file
0
public/html/projects/templates.jade
Normal file
0
public/html/projects/templates.jade
Normal file
8
public/html/projects/users.jade
Normal file
8
public/html/projects/users.jade
Normal file
@ -0,0 +1,8 @@
|
||||
h4 Users
|
||||
|
||||
table.table
|
||||
tbody: tr(ng-repeat="user in users")
|
||||
td {{ user.name }}
|
||||
td {{ user.username }}
|
||||
td {{ user.email }}
|
||||
td: button.btn.btn-default.pull-right.btn-xs(ng-click="remove(user)") remove
|
@ -11,7 +11,7 @@ app.config(['$httpProvider', function ($httpProvider) {
|
||||
request.url = url = url.replace('/tpl/', '/public/html/');
|
||||
}
|
||||
|
||||
if (!(url.indexOf('/public') !== -1 || url.indexOf('://') !== -1)) {
|
||||
if (!(url.indexOf('/public') !== -1 || url.indexOf('://') !== -1 || url.indexOf('uib/template') !== -1)) {
|
||||
request.url = "/api" + request.url;
|
||||
request.headers['Cache-Control'] = 'no-cache';
|
||||
}
|
||||
@ -50,14 +50,35 @@ app.run(['$rootScope', '$window', '$couchPotato', '$injector', '$state', '$http'
|
||||
$rootScope.user = null;
|
||||
$rootScope.loggedIn = false;
|
||||
|
||||
$rootScope.ws = null;
|
||||
|
||||
$http.get('/user')
|
||||
.then(function (user) {
|
||||
$rootScope.user = user;
|
||||
$rootScope.loggedIn = true;
|
||||
|
||||
$rootScope.startWS();
|
||||
}, function () {
|
||||
$state.go('auth.login');
|
||||
});
|
||||
}
|
||||
|
||||
$rootScope.startWS = function () {
|
||||
var ws_proto = 'ws:';
|
||||
if (document.location.protocol == 'https:') {
|
||||
ws_proto = 'wss:';
|
||||
}
|
||||
|
||||
$rootScope.ws = new WebSocket(ws_proto + '//' + document.location.host + '/api/ws');
|
||||
$rootScope.ws.onclose = function () {
|
||||
console.log('WS closed, retrying');
|
||||
setTimeout($rootScope.startWS, 2000);
|
||||
}
|
||||
|
||||
$rootScope.ws.onmessage = function (e) {
|
||||
console.log('msg', e);
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.refreshUser();
|
||||
}]);
|
@ -1,9 +1,21 @@
|
||||
define(function () {
|
||||
app.registerController('DashboardCtrl', function ($scope, $http) {
|
||||
$scope.projects = [{
|
||||
name: 'Hey there'
|
||||
}, {
|
||||
name: 'Test project'
|
||||
}];
|
||||
})
|
||||
})
|
||||
define(['controllers/projects/edit'], function () {
|
||||
app.registerController('DashboardCtrl', ['$scope', '$http', '$uibModal', function ($scope, $http, $modal) {
|
||||
$scope.projects = [];
|
||||
|
||||
$http.get('/projects').success(function (projects) {
|
||||
$scope.projects = projects;
|
||||
})
|
||||
|
||||
$scope.addProject = function () {
|
||||
$modal.open({
|
||||
templateUrl: '/tpl/projects/add.html'
|
||||
}).result.then(function (project) {
|
||||
$http.post('/projects', project)
|
||||
.success(function () {
|
||||
}).error(function (data, status) {
|
||||
swal('Error', 'Could not create project: ' + status, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/dashboard.js
Normal file
4
public/js/controllers/projects/dashboard.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectDashboardCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/edit.js
Normal file
4
public/js/controllers/projects/edit.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectEditCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/environment.js
Normal file
4
public/js/controllers/projects/environment.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectEnvironmentCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/inventory.js
Normal file
4
public/js/controllers/projects/inventory.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectInventoryCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/keys.js
Normal file
4
public/js/controllers/projects/keys.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectKeysCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/repositories.js
Normal file
4
public/js/controllers/projects/repositories.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectRepositoriesCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/schedule.js
Normal file
4
public/js/controllers/projects/schedule.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectScheduleCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
4
public/js/controllers/projects/templates.js
Normal file
4
public/js/controllers/projects/templates.js
Normal file
@ -0,0 +1,4 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectTemplatesCtrl', ['$scope', '$http', function ($scope, $http) {
|
||||
}]);
|
||||
});
|
19
public/js/controllers/projects/users.js
Normal file
19
public/js/controllers/projects/users.js
Normal file
@ -0,0 +1,19 @@
|
||||
define(function () {
|
||||
app.registerController('ProjectUsersCtrl', ['$scope', '$http', 'Project', function ($scope, $http, Project) {
|
||||
$scope.reload = function () {
|
||||
$http.get(Project.getURL() + '/users').success(function (users) {
|
||||
$scope.users = users;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.remove = function (user) {
|
||||
$http.delete(Project.getURL() + '/users/' + user.id).success(function () {
|
||||
$scope.reload();
|
||||
}).error(function () {
|
||||
swal('error', 'could not delete user..', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
$scope.reload();
|
||||
}]);
|
||||
});
|
12
public/js/factories/project.js
Normal file
12
public/js/factories/project.js
Normal file
@ -0,0 +1,12 @@
|
||||
app.factory('ProjectFactory', ['$http', function ($http) {
|
||||
var Project = function (project) {
|
||||
this.id = project.id;
|
||||
this.name = project.name;
|
||||
}
|
||||
|
||||
Project.prototype.getURL = function () {
|
||||
return '/project/' + this.id;
|
||||
}
|
||||
|
||||
return Project;
|
||||
}]);
|
104
public/js/routes/project.js
Normal file
104
public/js/routes/project.js
Normal file
@ -0,0 +1,104 @@
|
||||
app.config(function ($stateProvider, $couchPotatoProvider) {
|
||||
$stateProvider
|
||||
.state('project', {
|
||||
url: '/project/:project_id',
|
||||
abstract: true,
|
||||
templateUrl: '/tpl/projects/container.html',
|
||||
controller: function ($scope, Project) {
|
||||
$scope.project = Project;
|
||||
},
|
||||
resolve: {
|
||||
Project: ['$http', '$stateParams', '$q', 'ProjectFactory', function ($http, params, $q, ProjectFactory) {
|
||||
var d = $q.defer();
|
||||
|
||||
$http.get('/project/' + params.project_id)
|
||||
.success(function (project) {
|
||||
d.resolve(new ProjectFactory(project));
|
||||
}).error(function () {
|
||||
d.resolve(false);
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}]
|
||||
}
|
||||
})
|
||||
.state('project.dashboard', {
|
||||
url: '',
|
||||
pageTitle: 'Project Dashboard',
|
||||
templateUrl: '/tpl/projects/dashboard.html',
|
||||
controller: 'ProjectDashboardCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/dashboard'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.users', {
|
||||
url: '/users',
|
||||
pageTitle: 'Users',
|
||||
templateUrl: '/tpl/projects/users.html',
|
||||
controller: 'ProjectUsersCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/users'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.templates', {
|
||||
url: '/templates',
|
||||
pageTitle: 'Templates',
|
||||
templateUrl: '/tpl/projects/templates.html',
|
||||
controller: 'ProjectTemplatesCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/templates'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.inventory', {
|
||||
url: '/inventory',
|
||||
pageTitle: 'Inventory',
|
||||
templateUrl: '/tpl/projects/inventory.html',
|
||||
controller: 'ProjectInventoryCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/inventory'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.environment', {
|
||||
url: '/environment',
|
||||
pageTitle: 'Environment',
|
||||
templateUrl: '/tpl/projects/environment.html',
|
||||
controller: 'ProjectEnvironmentCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/environment'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.keys', {
|
||||
url: '/keys',
|
||||
pageTitle: 'Keys',
|
||||
templateUrl: '/tpl/projects/keys.html',
|
||||
controller: 'ProjectKeysCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/keys'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.repositories', {
|
||||
url: '/repositories',
|
||||
pageTitle: 'Repositories',
|
||||
templateUrl: '/tpl/projects/repositories.html',
|
||||
controller: 'ProjectRepositoriesCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/repositories'])
|
||||
}
|
||||
})
|
||||
|
||||
.state('project.schedule', {
|
||||
url: '/schedule',
|
||||
pageTitle: 'Template Schedule',
|
||||
templateUrl: '/tpl/projects/schedule.html',
|
||||
controller: 'ProjectScheduleCtrl',
|
||||
resolve: {
|
||||
$d: $couchPotatoProvider.resolveDependencies(['controllers/projects/schedule'])
|
||||
}
|
||||
});
|
||||
});
|
12
routes/projects/environment.go
Normal file
12
routes/projects/environment.go
Normal file
@ -0,0 +1,12 @@
|
||||
package projects
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func GetProjectEnvironment(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddProjectEnvironment(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RemoveProjectEnvironment(c *gin.Context) {
|
||||
}
|
12
routes/projects/inventory.go
Normal file
12
routes/projects/inventory.go
Normal file
@ -0,0 +1,12 @@
|
||||
package projects
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func GetProjectInventories(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddProjectInventory(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RemoveProjectInventory(c *gin.Context) {
|
||||
}
|
12
routes/projects/keys.go
Normal file
12
routes/projects/keys.go
Normal file
@ -0,0 +1,12 @@
|
||||
package projects
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func GetProjectKeys(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddProjectKey(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RemoveProjectKey(c *gin.Context) {
|
||||
}
|
44
routes/projects/project.go
Normal file
44
routes/projects/project.go
Normal file
@ -0,0 +1,44 @@
|
||||
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 ProjectMiddleware(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
projectID, err := util.GetIntParam("project_id", c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
query, args, _ := squirrel.Select("p.*").
|
||||
From("project as p").
|
||||
Join("project__user as pu on pu.project_id=p.id").
|
||||
Where("p.id=?", projectID).
|
||||
Where("pu.user_id=?", user.ID).
|
||||
ToSql()
|
||||
|
||||
var project models.Project
|
||||
if err := database.Mysql.SelectOne(&project, query, args...); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.AbortWithStatus(404)
|
||||
return
|
||||
}
|
||||
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Set("project", project)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func GetProject(c *gin.Context) {
|
||||
c.JSON(200, c.MustGet("project"))
|
||||
}
|
46
routes/projects/projects.go
Normal file
46
routes/projects/projects.go
Normal file
@ -0,0 +1,46 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/masterminds/squirrel"
|
||||
)
|
||||
|
||||
func GetProjects(c *gin.Context) {
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
query, args, _ := squirrel.Select("p.*").
|
||||
From("project as p").
|
||||
Join("project__user as pu on pu.project_id=p.id").
|
||||
Where("pu.user_id=?", user.ID).
|
||||
OrderBy("p.name").
|
||||
ToSql()
|
||||
|
||||
var projects []models.Project
|
||||
if _, err := database.Mysql.Select(&projects, query, args...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.JSON(200, projects)
|
||||
}
|
||||
|
||||
func AddProject(c *gin.Context) {
|
||||
var body models.Project
|
||||
user := c.MustGet("user").(*models.User)
|
||||
|
||||
if err := c.Bind(&body); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err := body.CreateProject()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if _, err := database.Mysql.Exec("insert into project__user set project_id=?, user_id=?, admin=1", body.ID, user.ID); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.JSON(201, body)
|
||||
}
|
12
routes/projects/repository.go
Normal file
12
routes/projects/repository.go
Normal file
@ -0,0 +1,12 @@
|
||||
package projects
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func GetProjectRepositories(c *gin.Context) {
|
||||
}
|
||||
|
||||
func AddProjectRepository(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RemoveProjectRepository(c *gin.Context) {
|
||||
}
|
31
routes/projects/users.go
Normal file
31
routes/projects/users.go
Normal file
@ -0,0 +1,31 @@
|
||||
package projects
|
||||
|
||||
import (
|
||||
"github.com/ansible-semaphore/semaphore/database"
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/masterminds/squirrel"
|
||||
)
|
||||
|
||||
func GetProjectUsers(c *gin.Context) {
|
||||
project := c.MustGet("project").(models.Project)
|
||||
var users []models.User
|
||||
|
||||
query, args, _ := squirrel.Select("u.*").
|
||||
From("project__user as pu").
|
||||
Join("user as u on pu.user_id=u.id").
|
||||
Where("pu.project_id=?", project.ID).
|
||||
ToSql()
|
||||
|
||||
if _, err := database.Mysql.Select(&users, query, args...); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.JSON(200, users)
|
||||
}
|
||||
|
||||
func AddProjectUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
func RemoveProjectUser(c *gin.Context) {
|
||||
}
|
@ -4,6 +4,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/routes/auth"
|
||||
"github.com/ansible-semaphore/semaphore/routes/projects"
|
||||
"github.com/ansible-semaphore/semaphore/routes/sockets"
|
||||
"github.com/ansible-semaphore/semaphore/util"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@ -28,8 +30,43 @@ func Route(r *gin.Engine) {
|
||||
|
||||
api.Use(MustAuthenticate)
|
||||
|
||||
api.GET("/ws", sockets.Handler)
|
||||
|
||||
api.GET("/user", getUser)
|
||||
// api.PUT("/user", misc.UpdateUser)
|
||||
|
||||
api.GET("/projects", projects.GetProjects)
|
||||
api.POST("/projects", projects.AddProject)
|
||||
|
||||
func(api *gin.RouterGroup) {
|
||||
api.Use(projects.ProjectMiddleware)
|
||||
|
||||
api.GET("", projects.GetProject)
|
||||
|
||||
api.GET("/users", projects.GetProjectUsers)
|
||||
api.POST("/users", projects.AddProjectUser)
|
||||
api.DELETE("/users/:user_id", projects.RemoveProjectUser)
|
||||
|
||||
api.GET("/keys", projects.GetProjectKeys)
|
||||
api.POST("/keys", projects.AddProjectKey)
|
||||
api.DELETE("/keys", projects.RemoveProjectKey)
|
||||
|
||||
api.GET("/repositories", projects.GetProjectRepositories)
|
||||
api.POST("/repositories", projects.AddProjectRepository)
|
||||
api.DELETE("/repositories/:user_id", projects.RemoveProjectRepository)
|
||||
|
||||
api.GET("/inventory", projects.GetProjectInventories)
|
||||
api.POST("/inventory", projects.AddProjectInventory)
|
||||
api.DELETE("/inventory/:user_id", projects.RemoveProjectInventory)
|
||||
|
||||
api.GET("/environment", projects.GetProjectEnvironment)
|
||||
api.POST("/environment", projects.AddProjectEnvironment)
|
||||
api.DELETE("/environment/:user_id", projects.RemoveProjectEnvironment)
|
||||
|
||||
api.GET("/templates", projects.GetProjectUsers)
|
||||
api.POST("/templates", projects.AddProjectUser)
|
||||
api.DELETE("/templates/:user_id", projects.RemoveProjectUser)
|
||||
}(api.Group("/project/:project_id"))
|
||||
}
|
||||
|
||||
func servePublic(c *gin.Context) {
|
||||
|
115
routes/sockets/handler.go
Normal file
115
routes/sockets/handler.go
Normal file
@ -0,0 +1,115 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/ansible-semaphore/semaphore/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
}
|
||||
|
||||
const (
|
||||
// Time allowed to write a message to the peer.
|
||||
writeWait = 10 * time.Second
|
||||
|
||||
// Time allowed to read the next pong message from the peer.
|
||||
pongWait = 60 * time.Second
|
||||
|
||||
// Send pings to peer with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
|
||||
// Maximum message size allowed from peer.
|
||||
maxMessageSize = 512
|
||||
)
|
||||
|
||||
type connection struct {
|
||||
ws *websocket.Conn
|
||||
send chan []byte
|
||||
userID int
|
||||
}
|
||||
|
||||
// readPump pumps messages from the websocket connection to the hub.
|
||||
func (c *connection) readPump() {
|
||||
defer func() {
|
||||
h.unregister <- c
|
||||
c.ws.Close()
|
||||
}()
|
||||
|
||||
c.ws.SetReadLimit(maxMessageSize)
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
c.ws.SetPongHandler(func(string) error {
|
||||
c.ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
return nil
|
||||
})
|
||||
|
||||
for {
|
||||
_, message, err := c.ws.ReadMessage()
|
||||
fmt.Println(string(message))
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
log.Printf("error: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
h.broadcast <- message
|
||||
}
|
||||
}
|
||||
|
||||
// write writes a message with the given message type and payload.
|
||||
func (c *connection) write(mt int, payload []byte) error {
|
||||
c.ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
return c.ws.WriteMessage(mt, payload)
|
||||
}
|
||||
|
||||
// writePump pumps messages from the hub to the websocket connection.
|
||||
func (c *connection) writePump() {
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
|
||||
defer func() {
|
||||
ticker.Stop()
|
||||
c.ws.Close()
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case message, ok := <-c.send:
|
||||
if !ok {
|
||||
c.write(websocket.CloseMessage, []byte{})
|
||||
return
|
||||
}
|
||||
if err := c.write(websocket.TextMessage, message); err != nil {
|
||||
return
|
||||
}
|
||||
case <-ticker.C:
|
||||
if err := c.write(websocket.PingMessage, []byte{}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Handler(context *gin.Context) {
|
||||
user := context.MustGet("user").(*models.User)
|
||||
ws, err := upgrader.Upgrade(context.Writer, context.Request, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c := &connection{
|
||||
send: make(chan []byte, 256),
|
||||
ws: ws,
|
||||
userID: user.ID,
|
||||
}
|
||||
|
||||
h.register <- c
|
||||
|
||||
go c.writePump()
|
||||
c.readPump()
|
||||
}
|
51
routes/sockets/pool.go
Normal file
51
routes/sockets/pool.go
Normal file
@ -0,0 +1,51 @@
|
||||
package sockets
|
||||
|
||||
// hub maintains the set of active connections and broadcasts messages to the
|
||||
// connections.
|
||||
type hub struct {
|
||||
// Registered connections.
|
||||
connections map[*connection]bool
|
||||
|
||||
// Inbound messages from the connections.
|
||||
broadcast chan []byte
|
||||
|
||||
// Register requests from the connections.
|
||||
register chan *connection
|
||||
|
||||
// Unregister requests from connections.
|
||||
unregister chan *connection
|
||||
}
|
||||
|
||||
var h = hub{
|
||||
broadcast: make(chan []byte),
|
||||
register: make(chan *connection),
|
||||
unregister: make(chan *connection),
|
||||
connections: make(map[*connection]bool),
|
||||
}
|
||||
|
||||
func (h *hub) run() {
|
||||
for {
|
||||
select {
|
||||
case c := <-h.register:
|
||||
h.connections[c] = true
|
||||
case c := <-h.unregister:
|
||||
if _, ok := h.connections[c]; ok {
|
||||
delete(h.connections, c)
|
||||
close(c.send)
|
||||
}
|
||||
case m := <-h.broadcast:
|
||||
for c := range h.connections {
|
||||
select {
|
||||
case c.send <- m:
|
||||
default:
|
||||
close(c.send)
|
||||
delete(h.connections, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func StartWS() {
|
||||
h.run()
|
||||
}
|
Loading…
Reference in New Issue
Block a user