Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions less/__vms-list.less
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
}

.__vms-list {
.vms-list-header {
.vms-list-header,
.list-header {
.make-row(0);
background: @state-info-bg;
.select {
Expand Down Expand Up @@ -139,7 +140,7 @@
text-transform: uppercase;
&.stopped { background-color: @gray-light; color: #fff; }
&.failed { background-color: @brand-danger; color: #fff; }
&.running { background-color: @brand-success; color: #fff; }
&.destroyed { background-color: @gray-dark; color: #fff; }
&.running, &.ready { background-color: @brand-success; color: #fff; }
&.destroyed, &.unknown { background-color: @gray-dark; color: #fff; }
}
}
1 change: 1 addition & 0 deletions less/app.less
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@

@import "vm.less";
@import "vms.less";
@import "volumes.less";

@import "image.less";
@import "settings.less";
Expand Down
5 changes: 3 additions & 2 deletions less/vm.less
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ section.fwrules {
.dropdown-menu .delete { color: red; }

.resource-status {
.running { background: @brand-success; }
.running, .ready { background: @brand-success; }
.failed { background: @brand-danger; }
.stopped { background: @gray-light; }
}
Expand Down Expand Up @@ -123,7 +123,8 @@ section.fwrules {
.server-uuid,
.owner-uuid,
.billing-id,
.image-uuid {
.image-uuid,
.volume-uuid {
.uuid;
.pull-right;
}
Expand Down
27 changes: 27 additions & 0 deletions less/volumes.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

@import "vm";
@import "networks";
@import "__vms-list";

.volumes-list {
.__vms-list;
td.status {width: 90px;}
a.delete {color: @state-danger-text; text-decoration: none; font-weight: bold; visibility: hidden; float: right;}
tr:hover .actions a, .actions:hover a {visibility: visible;}
}

#page-volume {
#page-vm;
}
.networks-list {
#page-networks > .networks-list;
}

#page-volume-form {
.help-block {
margin-bottom: 0;
}
.control-label.required {
font-weight: 600;
}
}
39 changes: 34 additions & 5 deletions lib/adminui.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ function resume(req, res, next) {
next();
}

function checkVolApiConfig(req, res, next) {
if (req.sdc[req.dc].hasOwnProperty('volapi')) {
return next();
}
next(new restify.ServiceUnavailableError('Cannot connect to VOLAPI service. Please verify configuration.'));
}

function setUfds(req, res, next) {
var trySetUfds = function () {
if (req.ufdsMaster.connected) {
Expand Down Expand Up @@ -176,8 +183,6 @@ ADMINUI.prototype.createServer = function () {
});
});



server.use(sdcClients.handler);

server.use((function attachOtherClients(req, res, next) {
Expand Down Expand Up @@ -283,6 +288,32 @@ ADMINUI.prototype.createServer = function () {
auth.requireRole('operators'),
vms.del);

var volumes = require('./volumes');
server.get('/api/volumes',
auth.requireAuth,
auth.requireRole('operators'),
checkVolApiConfig,
volumes.list);

server.get('/api/volumes/:uuid',
auth.requireAuth,
auth.requireRole('operators'),
checkVolApiConfig,
volumes.get);

server.post('/api/volumes',
auth.requireAuth,
auth.requireRole('operators'),
checkVolApiConfig,
resume,
bodyParser,
volumes.create);

server.del('/api/volumes/:uuid',
auth.requireAuth,
auth.requireRole('operators'),
checkVolApiConfig,
volumes.del);

var jobs = require('./jobs');
server.get('/api/jobs', auth.requireAuth, jobs.listJobs);
Expand Down Expand Up @@ -323,8 +354,6 @@ ADMINUI.prototype.createServer = function () {
images.aclAction);




var packages = require('./packages');
server.get('/api/packages', auth.requireAuth, packages.list);
server.get('/api/packages/:uuid', auth.requireAuth, packages.get);
Expand Down Expand Up @@ -909,7 +938,7 @@ ADMINUI.prototype.createServer = function () {
}
var method = req.method;
var body =
!(method === 'POST' && route.name === 'authenticate') &&
!(method === 'POST' && route && route.name === 'authenticate') &&
!(method === 'GET' && Math.floor(res.statusCode/100) === 2);
auditLogger({
log: req.log.child({ component: 'audit', route: route && route.name }, true),
Expand Down
10 changes: 7 additions & 3 deletions lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ var LOCKED_TIME = 4 * 60 * 60;
var userLockTimes = {};
var userLoginAttempts = {};

var MANTA_ENALBED = require('../etc/config.json')['manta'] || false;
var config = require('../etc/config.json');
var MANTA_ENABLED = config.manta || false;
var Auth = module.exports = {};

Auth.optionalAuth = function optionalAuth(req, res, next) {
Expand Down Expand Up @@ -189,10 +190,12 @@ Auth.authenticate = function authenticate(req, res, next) {
return next(new restify.ConflictError('User does not have permission to use Operations Portal.'));
}

var isVolapiEnabled = !!(config.datacenters[req.dc] && config.datacenters[req.dc].volapi);
req.sessions.create({
roles: user.groups(),
user: user.uuid,
manta: MANTA_ENALBED
manta: MANTA_ENABLED,
volapiEnabled: isVolapiEnabled
}, function (err2, token) {
if (err2) {
req.log.info(err2, 'error creating session');
Expand All @@ -203,7 +206,8 @@ Auth.authenticate = function authenticate(req, res, next) {
user: user,
token: token,
adminUuid: req.adminUuid,
manta: MANTA_ENALBED,
manta: MANTA_ENABLED,
volapiEnabled: isVolapiEnabled,
dc: req.dc,
roles: user.groups()
});
Expand Down
4 changes: 4 additions & 0 deletions lib/sdc-clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ module.exports.createClients = function (config) {
clients[dc].imgapi = new IMGAPI(cfg.imgapi);
clients[dc].papi = new SDC.PAPI(cfg.papi);

if (cfg.volapi) {
clients[dc].volapi = restify.createJsonClient(cfg.volapi);
}

cfg.moray.no_count = true;

clients[dc].moray = createMorayClient({
Expand Down
98 changes: 98 additions & 0 deletions lib/volumes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

/*
* Copyright (c) 2016, Joyent, Inc.
*/

var sprintf = require('util').format;

var getVolumeParams = function (params) {
var query = params.query || [];

if (params.type && params.type.length) {
query.push(sprintf('(type=*%s*)', params.type));
delete params.type;
}

if (params.state && params.state.length) {
query.push(sprintf('(state=%s)', params.state));
delete params.state;
}

if (params.tag && params.tag.length) {
query.push(sprintf('(tag=%s)', params.tag));
delete params.tag;
}

if (query.length) {
params.filter = '(&' + query.join('') + ')';
}

return params;
};

function list(req, res, next) {
var params = getVolumeParams(req.params);
var opts = {
path: '/volumes',
query: params
};
req.sdc[req.dc].volapi.get(opts, function (err, _req, _res, volumes) {
if (err) {
req.log.error(err, 'Error retrieving volumes');
return next(err);
}
if (params.uuid && params.uuid.length === 36) {
volumes = volumes.filter(function (volume) {
return volume.uuid === params.uuid;
});
}
res.send(volumes);
return next();
});
}

function get(req, res, next) {
req.sdc[req.dc].volapi.get('/volumes/' + req.params.uuid, function (err, _req, _res, volume) {
if (err) {
req.log.error(err, 'Error retrieving volume');
return next(err);
}
res.send(volume);
return next();
});
}

function create(req, res, next) {
req.log.info(req.body, 'VOLAPI Create Request');
req.sdc[req.dc].volapi.post('/volumes', req.body, function (err, _req, _res, volume) {
if (err) {
req.log.error(err, 'Error creating volume');
return next(err);
}
res.send(volume);
return next();
});
}

function del(req, res, next) {
req.sdc[req.dc].volapi.del('/volumes/' + req.params.uuid, function (err, _req, _res, volume) {
if (err) {
req.log.error(err, 'Error deleting volume');
return next(err);
}
res.send(volume);
return next();
});
}

module.exports = {
list: list,
get: get,
create: create,
del: del
};
28 changes: 15 additions & 13 deletions www/js/components/error-alert.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,32 @@
*/

/*
* Copyright (c) 2014, Joyent, Inc.
* Copyright (c) 2016, Joyent, Inc.
*/

"use strict";
'use strict';
var React = require('react');
var ErrorAlert = React.createClass({
propTypes: {
error: React.PropTypes.object
},
render: function() {
render: function () {
var error = this.props.error;

if (!error || !error.code) {
return <div className="error-alert alert alert-danger" style={ {display: 'none'} }></div>;
if (!error) {
return <div className="error-alert alert alert-danger" style={{display: 'none'}}></div>;
}

var errorMessage = error.message || (error.errors ? null : error);

return (<div className="error-alert alert alert-danger">
{
error.message ? <div><strong>{error.message}</strong></div>: ''
errorMessage ? <div><strong>{errorMessage}</strong></div> : ''
}
{
error.errors && error.errors.length ? <ul>{error.errors.map(function(err) {
return <li><strong>{err.field}</strong> - {err.message}</li>;
}) }</ul> : ''
error.errors && error.errors.length ? <ul>
{
error.errors.map(function (err) {
return <li><strong>{err.field}</strong> - {err.message}</li>;
})
}
</ul> : ''
}
</div>);
}
Expand Down
Loading