Renames and configs

This commit is contained in:
klizhentas 2015-07-17 17:54:26 -07:00
parent 3ce5afc00b
commit 80e8e1c195
13 changed files with 299 additions and 293 deletions

View file

@ -1,6 +1,12 @@
'use strict';
var EventsPage = React.createClass({
onStartChange: function(e) {
console.log(e.value);
},
onEndChange: function(e) {
console.log(e.value);
},
showEvent: function(e){
this.refs.event.show(e);
},
@ -35,9 +41,9 @@ var EventsPage = React.createClass({
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader icon="fa fa-list" title="Timeline"/>
<div className="wrapper wrapper-content animated fadeInRight">
<div className="wrapper wrapper-content">
<Box colClass="col-lg-8">
<EventsBox events={this.state.entries} onShowEvent={this.showEvent}/>
<EventsBox events={this.state.entries} onShowEvent={this.showEvent} onStartChange={this.onStartChange} onEndChange={this.onEndChange}/>
</Box>
</div>
<PageFooter/>
@ -67,6 +73,8 @@ var EventsContainer = React.createClass({
keyboardNavigation: false,
forceParse: false,
autoclose: true
}).on('changeDate', function(e){
console.log(e)
});
},
render: function() {
@ -88,9 +96,9 @@ var EventsContainer = React.createClass({
<div className="col-lg-4">
<div className="form-group" ref="daterange">
<div className="input-daterange input-group" id="datepicker">
<input type="text" className="input-sm form-control" name="start" value="05/14/2014"/>
<input type="text" className="input-sm form-control" name="start" onChange={this.props.onStartChange}/>
<span className="input-group-addon">to</span>
<input type="text" className="input-sm form-control" name="end" value="05/22/2014"/>
<input type="text" className="input-sm form-control" name="end" onChange={this.props.onEndChange}/>
</div>
</div>
</div>

View file

@ -33,42 +33,31 @@ var LeftNavBar = React.createClass({
}
return "";
},
items: function() {
return grv.nav_sections.concat([
{icon: "fa fa-key", url: grv.path("keys"), title: "Keys", key: "keys"},
{icon: "fa fa-list", url: grv.path("events"), title: "Timeline", key: "events"},
{icon: "fa fa-arrows-h", url: grv.path("webtuns"), title: "Web Tunnels", key: "webtuns"},
{icon: "fa fa-hdd-o", url: grv.path("servers"), title: "Instances", key: "servers"},
{icon: "fa fa-wechat", url: grv.path("sessions"), title: "Sessions", key: "sessions"},
]);
},
render: function(){
var self = this;
var items = this.items().map(function(i, index){
return (<li className={self.className(i.key)}>
<a href={i.url}>
<i className={i.icon}></i>
<span className="nav-label">{i.title}</span>
</a>
</li>);
});
return (
<nav className="navbar-default navbar-static-side" role="navigation">
<div className="sidebar-collapse">
<ul className="nav" id="side-menu">
<UserBarItem/>
<li className={this.className("keys")}>
<a href={grv.path("keys")}>
<i className="fa fa-key"></i>
<span className="nav-label">Keys</span>
</a>
</li>
<li className={this.className("events")}>
<a href={grv.path("events")}>
<i className="fa fa-list"></i>
<span className="nav-label">Timeline</span>
</a>
</li>
<li className={this.className("webtuns")}>
<a href={grv.path("webtuns")}>
<i className="fa fa-arrows-h"></i>
<span className="nav-label">Web Tunnels</span>
</a>
</li>
<li className={this.className("servers")}>
<a href={grv.path("servers")}>
<i className="fa fa-hdd-o"></i>
<span className="nav-label">Servers</span>
</a>
</li>
<li className={this.className("sessions")}>
<a href={grv.path("sessions")}>
<i className="fa fa-wechat"></i>
<span className="nav-label">Sessions</span>
</a>
</li>
{items}
</ul>
</div>
</nav>

View file

@ -1,236 +0,0 @@
'use strict';
var KeysPage = React.createClass({
getInitialState: function(){
return {keys: []};
},
componentDidMount: function() {
this.reload();
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: "json",
success: function(data) {
this.setState({keys: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
openKeyForm: function(){
this.refs.key.open();
},
addKey: function(key){
$.ajax({
url: this.props.url,
dataType: "json",
type: "POST",
data: key,
success: function(data) {
this.refs.key.reset();
this.refs.cert.show(atob(data["value"]));
this.reload();
}.bind(this),
error: function(xhr, status, err) {
this.refs.key.reset();
console.error(this.props.url, status, err.toString());
alert(err.toString());
}.bind(this)
});
},
deleteKey: function(key) {
if (!confirm("Are you sure you want to delete key " + key + " ?")) {
return;
}
$.ajax({
url: this.props.url+"/"+key,
type: "DELETE",
dataType: "json",
success: function(data) {
this.reload();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div id="wrapper">
<LeftNavBar current="keys"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader icon="fa fa-key" title="Keys"/>
<div className="wrapper wrapper-content animated fadeInRight">
<Box>
<KeysBox keys={this.state.keys} onOpenKeyForm={this.openKeyForm} onKeyDelete={this.deleteKey}/>
</Box>
</div>
<PageFooter/>
</div>
<SignForm ref="key" onAddKey={this.addKey}/>
<CertView ref="cert"/>
</div>
);
}
});
var KeysBox = React.createClass({
render: function() {
if (this.props.keys.length == 0) {
return (
<div className="text-center m-t-lg">
<h1>
Public SSH Keys.
</h1>
<small>You have no SSH Keys added. To get an access to cluster,add and sign your public key here.</small><br/><br/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenKeyForm}>
<i className="fa fa-check"></i>&nbsp;Add Key
</BootstrapButton>
</div>);
}
return (
<div>
<KeysTable keys={this.props.keys} onKeyDelete={this.props.onKeyDelete}/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenKeyForm}>
<i className="fa fa-check"></i>&nbsp;Add Key
</BootstrapButton>
</div>
);
}
})
var SignForm = React.createClass({
open: function() {
this.refs.modal.open();
},
close: function() {
this.refs.modal.close();
},
reset: function() {
React.findDOMNode(this.refs.id).value = "";
React.findDOMNode(this.refs.key).value = "";
this.refs.modal.close();
},
confirm: function() {
var id = React.findDOMNode(this.refs.id).value.trim();
var key = React.findDOMNode(this.refs.key).value.trim();
if (!id || !key) {
alert("ID and Key can not be empty");
return;
}
this.props.onAddKey({id: id, value: key});
},
render: function() {
return (
<BootstrapModal
icon="fa-laptop"
dialogClass="modal-lg"
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.reset}
onConfirm={this.confirm}
title="Add and Sign SSH Key">
<div className="form-group">
<label>Key ID</label>
<input placeholder="Unique ID for the Key" className="form-control" ref="id"/>
</div>
<div className="form-group">
<label>Public Key</label>
<textarea placeholder="Paste your public key here" className="form-control" ref="key" rows="8">
</textarea>
<p>Once submitted, public key will be signed by this cluster certificate authority,
and you will get the signed certificate back. Take this certificate and add it
alongside to your key in <strong>user-cert.pub</strong>
</p>
</div>
</BootstrapModal>
);
}
});
var CertView = React.createClass({
show: function(cert) {
React.findDOMNode(this.refs.key).value = cert;
this.refs.modal.open();
},
reset: function() {
React.findDOMNode(this.refs.key).value = "";
this.refs.modal.close();
},
render: function() {
return (
<BootstrapModal
icon="fa-laptop"
dialogClass="modal-lg"
ref="modal"
cancel="Close"
onCancel={this.reset}
title="Signed SSH Certificate">
<p>Congratulations! You can find the certificate below.</p>
<p><strong>Copy this certificate</strong> (click on the textarea and press "Ctrl-A" and "Ctrl-C")
and save it alongside with your public key, for example to the file <strong>username-cert.pub</strong>.
</p>
<p>This will allow your SSH client can use it to authenticate with the cluster.</p>
<div className="form-group">
<label>Signed SSH Certificate</label>
<textarea className="form-control" ref="key" rows="8" id="signed-ssh-cert-val"></textarea>
</div>
</BootstrapModal>
);
}
});
var KeysTable = React.createClass({
render: function() {
var keyDelete = this.props.onKeyDelete
var rows = this.props.keys.map(function (key, index) {
return (
<KeyRow id={key.id} value={key.value} key={index} onKeyDelete={keyDelete}></KeyRow>
);
});
return (
<table className="table table-striped">
<thead>
<tr>
<th>Key ID</th>
<th>Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>);
}
});
var KeyRow = React.createClass({
handleDelete: function(e) {
e.preventDefault();
this.props.onKeyDelete(this.props.id);
},
render: function() {
return (
<tr className="key">
<td>{this.props.id}</td>
<td>{atob(this.props.value).substring(0, 100)}...</td>
<td><a href="#" onClick={this.handleDelete}><i className="fa fa-times text-navy"></i></a></td>
</tr>
);
}
});
React.render(
<KeysPage url={grv.path("api", "keys")}/>,
document.body
);

View file

@ -0,0 +1,229 @@
'use strict';
var KeysPage = React.createClass({
getInitialState: function(){
return {keys: []};
},
componentDidMount: function() {
this.reload();
},
reload: function(){
$.ajax({
url: this.props.url,
dataType: "json",
success: function(data) {
this.setState({keys: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
openKeyForm: function(){
this.refs.key.open();
},
addKey: function(key){
$.ajax({
url: this.props.url,
dataType: "json",
type: "POST",
data: key,
success: function(data) {
this.refs.key.reset();
this.refs.cert.show(atob(data["value"]));
this.reload();
}.bind(this),
error: function(xhr, status, err) {
this.refs.key.reset();
toastr.error(err.toString());
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
deleteKey: function(key) {
if (!confirm("Are you sure you want to delete key " + key + " ?")) {
return;
}
$.ajax({
url: this.props.url+"/"+key,
type: "DELETE",
dataType: "json",
success: function(data) {
this.reload();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
toastr.error(err.toString());
}.bind(this)
});
},
render: function() {
return (
<div id="wrapper">
<LeftNavBar current="keys"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader icon="fa fa-key" title="Keys"/>
<div className="wrapper wrapper-content">
<Box>
<KeysBox keys={this.state.keys} onOpenKeyForm={this.openKeyForm} onKeyDelete={this.deleteKey}/>
</Box>
</div>
<PageFooter/>
</div>
<SignForm ref="key" onAddKey={this.addKey}/>
<CertView ref="cert"/>
</div>
);
}
});
var KeysBox = React.createClass({
render: function() {
if (this.props.keys.length == 0) {
return (
<div className="text-center m-t-lg">
<h1>
Public SSH Keys.
</h1>
<small>You have no SSH Keys added. To get an access to cluster,add and sign your public key here.</small><br/><br/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenKeyForm}>
<i className="fa fa-check"></i>&nbsp;Add Key
</BootstrapButton>
</div>);
}
return (
<div>
<KeysTable keys={this.props.keys} onKeyDelete={this.props.onKeyDelete}/>
<BootstrapButton className="btn-primary" onClick={this.props.onOpenKeyForm}>
<i className="fa fa-check"></i>&nbsp;Add Key
</BootstrapButton>
</div>
);
}
})
var SignForm = React.createClass({
open: function() {
this.refs.modal.open();
},
close: function() {
this.refs.modal.close();
},
reset: function() {
React.findDOMNode(this.refs.id).value = "";
React.findDOMNode(this.refs.key).value = "";
this.refs.modal.close();
},
confirm: function() {
var id = React.findDOMNode(this.refs.id).value.trim();
var key = React.findDOMNode(this.refs.key).value.trim();
if (!id || !key) {
alert("ID and Key can not be empty");
return;
}
this.props.onAddKey({id: id, value: key});
},
render: function() {
return (
<BootstrapModal icon="fa fa-key"
dialogClass="modal-lg"
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.reset}
onConfirm={this.confirm}
title="Add SSH Key">
<div className="form-group">
<label>Key ID</label>
<input placeholder="Unique ID for the Key" className="form-control" ref="id"/>
</div>
<div className="form-group">
<label>Public Key</label>
<textarea placeholder="Paste your public key here" className="form-control" ref="key" rows="8">
</textarea>
<p>Once submitted, public key will be signed by this cluster certificate authority,
and you will get the signed certificate back. Take this certificate and add it
alongside to your key in <strong>user-cert.pub</strong>
</p>
</div>
</BootstrapModal>);
}
});
var CertView = React.createClass({
show: function(cert) {
React.findDOMNode(this.refs.key).value = cert;
this.refs.modal.open();
},
reset: function() {
React.findDOMNode(this.refs.key).value = "";
this.refs.modal.close();
},
render: function() {
return (<BootstrapModal icon="fa fa-key"
dialogClass="modal-lg"
ref="modal"
cancel="Close"
onCancel={this.reset}
title="Signed SSH Certificate">
<p>Congratulations! You can find the certificate below.</p>
<p><strong>Copy this certificate</strong> (click on the textarea and press "Ctrl-A" and "Ctrl-C")
and save it alongside with your public key, for example to the file <strong>username-cert.pub</strong>.
</p>
<p>This will allow your SSH client can use it to authenticate with the cluster.</p>
<div className="form-group">
<label>Signed SSH Certificate</label>
<textarea className="form-control" ref="key" rows="8" id="signed-ssh-cert-val"></textarea>
</div>
</BootstrapModal>
);
}
});
var KeysTable = React.createClass({
render: function() {
var keyDelete = this.props.onKeyDelete
var rows = this.props.keys.map(function (key, index) {
return (
<KeyRow id={key.id} value={key.value} key={index} onKeyDelete={keyDelete}></KeyRow>
);
});
return (
<table className="table table-striped">
<thead>
<tr>
<th>Key ID</th>
<th>Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>);
}
});
var KeyRow = React.createClass({
handleDelete: function(e) {
e.preventDefault();
this.props.onKeyDelete(this.props.id);
},
render: function() {
return (
<tr className="key">
<td>{this.props.id}</td>
<td>{atob(this.props.value).substring(0, 100)}...</td>
<td><a href="#" onClick={this.handleDelete}><i className="fa fa-times text-navy"></i></a></td>
</tr>
);
}
});
React.render(
<KeysPage url={grv.path("api", "keys")}/>,
document.body
);

View file

@ -36,7 +36,7 @@ var ServersPage = React.createClass({
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="SSH Servers" icon="fa fa-hdd-o"/>
<div className="wrapper wrapper-content animated fadeInRight">
<div className="wrapper wrapper-content">
<Box>
<ServersTable servers={this.state.servers} onConnect={this.connect}/>
</Box>

View file

@ -52,7 +52,7 @@ var SessionPage = React.createClass({
<LeftNavBar current="sessions"/>
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<div className="wrapper wrapper-content animated fadeInRight">
<div className="wrapper wrapper-content">
<div className="row">
<div className="col-lg-9" style={{width: '920px'}}>
<ConsoleBox session={this.state.session}

View file

@ -30,7 +30,7 @@ var SessionsPage = React.createClass({
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Active Sessions" icon="fa fa-wechat"/>
<div className="wrapper wrapper-content animated fadeInRight">
<div className="wrapper wrapper-content">
<Box>
<SessionsTable sessions={this.state.sessions}/>
</Box>

View file

@ -62,7 +62,7 @@ var WebTunsPage = React.createClass({
<div id="page-wrapper" className="gray-bg">
<TopNavBar/>
<PageHeader title="Web Tunnels" icon="fa fa-arrows-h"/>
<div className="wrapper wrapper-content animated fadeInRight">
<div className="wrapper wrapper-content">
<Box>
<WebTunsBox tuns={this.state.tuns} onOpenTunForm={this.openTunForm} onTunDelete={this.deleteTun}/>
</Box>

View file

@ -48,14 +48,15 @@
<!-- Gravity stuff -->
<script type="text/javascript">
grv = {
prefix: "{{.Prefix}}",
prefix: "{{.Cfg.URLPrefix}}",
path: function() {
var path = ["{{.Prefix}}" != ""? "{{.Prefix}}":""];
var path = ["{{.Cfg.URLPrefix}}" != ""? "{{.Cfg.URLPrefix}}":""];
for(var i = 0; i < arguments.length; i++) {
path.push(arguments[i]);
}
return path.join("/");
}
},
nav_sections: {{.Cfg.NavSections}}
};
</script>
<script src="{{Path "/static/js/grv/lib.js"}}"></script>

View file

@ -9,5 +9,5 @@
{{ end }}
{{ define "script" }}
<script type="text/jsx" src="{{Path "/static/js/grv/keys.js"}}"></script>
<script type="text/jsx" src="{{Path "/static/js/grv/keys.jsx"}}"></script>
{{ end }}

View file

@ -29,17 +29,32 @@ import (
// CPHandler implements methods for control panel
type CPHandler struct {
httprouter.Router
auth AuthHandler
prefix string
cfg HandlerConfig
}
func NewCPHandler(auth AuthHandler, assetsDir, prefix string) *CPHandler {
type HandlerConfig struct {
Auth AuthHandler
AssetsDir string
NavSections []NavSection
URLPrefix string
}
type NavSection struct {
Title string `json:"title"`
URL string `json:"url"`
Icon string `json:"icon"`
}
func NewCPHandler(cfg HandlerConfig) *CPHandler {
if len(cfg.NavSections) == 0 {
// to avoid panics during iterations
cfg.NavSections = []NavSection{}
}
h := &CPHandler{
auth: auth,
prefix: prefix,
cfg: cfg,
}
h.initTemplates(assetsDir)
h.initTemplates(cfg.AssetsDir)
h.GET("/login", h.login)
h.GET("/logout", h.logout)
h.POST("/auth", h.authForm)
@ -83,7 +98,7 @@ func NewCPHandler(auth AuthHandler, assetsDir, prefix string) *CPHandler {
// Static assets
h.Handler("GET", "/static/*filepath",
http.FileServer(http.Dir(filepath.Join(assetsDir, "assets"))))
http.FileServer(http.Dir(filepath.Join(cfg.AssetsDir, "assets"))))
// Operations with sessions
h.GET("/api/sessions", h.needsAuth(h.getSessions))
@ -109,7 +124,7 @@ func (s *CPHandler) needsAuth(fn RequestHandler) httprouter.Handle {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
ctx, err := s.auth.ValidateSession(d.User, d.SID)
ctx, err := s.cfg.Auth.ValidateSession(d.User, d.SID)
if err != nil {
log.Warningf("failed to validate session: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
@ -238,7 +253,7 @@ func (s *CPHandler) downloadFiles(w http.ResponseWriter, r *http.Request, p http
}
ck := &http.Cookie{
Domain: fmt.Sprintf(".%v", s.auth.GetHost()),
Domain: fmt.Sprintf(".%v", s.cfg.Auth.GetHost()),
Name: "fileDownload",
Value: "true",
Path: "/",
@ -624,7 +639,7 @@ func (s *CPHandler) login(w http.ResponseWriter, r *http.Request, _ httprouter.P
}
func (s *CPHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
s.auth.ClearSession(w)
s.cfg.Auth.ClearSession(w)
http.Redirect(w, r, s.Path("login"), http.StatusFound)
}
@ -639,13 +654,13 @@ func (s *CPHandler) authForm(w http.ResponseWriter, r *http.Request, p httproute
replyErr(w, http.StatusBadRequest, err)
return
}
sid, err := s.auth.Auth(user, pass)
sid, err := s.cfg.Auth.Auth(user, pass)
if err != nil {
log.Warningf("auth error: %v", err)
http.Redirect(w, r, "/login", http.StatusFound)
return
}
if err := s.auth.SetSession(w, user, sid); err != nil {
if err := s.cfg.Auth.SetSession(w, user, sid); err != nil {
replyErr(w, http.StatusInternalServerError, err)
return
}
@ -656,7 +671,7 @@ func (s *CPHandler) executeTemplate(w http.ResponseWriter, name string, data map
if data == nil {
data = map[string]interface{}{}
}
data["Prefix"] = s.prefix
data["Cfg"] = s.cfg
tpl, ok := templates[name]
if !ok {
replyErr(w, http.StatusInternalServerError, fmt.Errorf("template '%v' not found", name))

View file

@ -30,7 +30,7 @@ func NewServer(cfg Config) (*CPServer, error) {
if err != nil {
return nil, err
}
cp := NewCPHandler(auth, cfg.AssetsDir, "")
cp := NewCPHandler(HandlerConfig{Auth: auth, AssetsDir: cfg.AssetsDir})
proxy := newProxyHandler(cp, cfg.AuthSrv, cfg.Host)
return &CPServer{
cfg: cfg,

View file

@ -8,7 +8,7 @@ import (
var templates map[string]*template.Template
func (s *CPHandler) Path(params ...string) string {
out := append([]string{"/", s.prefix}, params...)
out := append([]string{"/", s.cfg.URLPrefix}, params...)
return filepath.Join(out...)
}