Skip to content

Commit

Permalink
updated inbuild HTML pages to serve ne web UI
Browse files Browse the repository at this point in the history
  • Loading branch information
darklynx committed Mar 25, 2017
1 parent d3f1b99 commit 76dca04
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 105 deletions.
15 changes: 11 additions & 4 deletions handlers.go
Expand Up @@ -18,8 +18,7 @@ import (

var validBasketName = regexp.MustCompile(basketNamePattern)
var defaultResponse = ResponseConfig{Status: 200, IsTemplate: false}
var indexPageTemplate = template.Must(template.New("index").Parse(indexPageContent))
var basketPageTemplate = template.Must(template.New("basket").Parse(basketPageContent))
var basketPageTemplate = template.Must(template.New("basket").Parse(basketPageContentTemplate))

// writeJSON writes JSON content to HTTP response
func writeJSON(w http.ResponseWriter, status int, json []byte, err error) {
Expand Down Expand Up @@ -326,13 +325,21 @@ func ForwardToWeb(w http.ResponseWriter, r *http.Request, ps httprouter.Params)

// WebIndexPage handles HTTP request to render index page
func WebIndexPage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
indexPageTemplate.Execute(w, "")
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(indexPageContent)
}

// WebBasketPage handles HTTP request to render basket details page
func WebBasketPage(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
if name := ps.ByName("basket"); validBasketName.MatchString(name) {
basketPageTemplate.Execute(w, name)
switch name {
case serviceAPIPath:
// admin page to access all baskets
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(basketsPageContent)
default:
basketPageTemplate.Execute(w, name)
}
} else {
http.Error(w, "Basket name does not match pattern: "+validBasketName.String(), http.StatusBadRequest)
}
Expand Down
14 changes: 14 additions & 0 deletions handlers_test.go
Expand Up @@ -856,6 +856,20 @@ func TestWebBasketPage(t *testing.T) {
}
}

func TestWebBasketsPage(t *testing.T) {
r, err := http.NewRequest("GET", "http://localhost:55555/web/"+serviceAPIPath, strings.NewReader(""))
if assert.NoError(t, err) {
w := httptest.NewRecorder()
ps := append(make(httprouter.Params, 0), httprouter.Param{Key: "basket", Value: serviceAPIPath})
WebBasketPage(w, r, ps)

// validate response: 200 - OK
assert.Equal(t, 200, w.Code, "wrong HTTP result code")
assert.Contains(t, w.Body.String(), "<title>Request Baskets - Administration</title>",
"HTML index page with baskets is expected")
}
}

func TestWebBasketPage_InvalidName(t *testing.T) {
basket := ">>>"

Expand Down
6 changes: 3 additions & 3 deletions server.go
Expand Up @@ -54,9 +54,9 @@ func StartServer() {

// web pages
router.GET("/", ForwardToWeb)
//router.GET("/"+serviceUIPath, WebIndexPage)
//router.GET("/"+serviceUIPath+"/:basket", WebBasketPage)
router.ServeFiles("/"+serviceUIPath+"/*filepath", http.Dir("./src/github.com/darklynx/request-baskets/web"))
router.GET("/"+serviceUIPath, WebIndexPage)
router.GET("/"+serviceUIPath+"/:basket", WebBasketPage)
//router.ServeFiles("/"+serviceUIPath+"/*filepath", http.Dir("./src/github.com/darklynx/request-baskets/web"))

// basket requests
router.NotFound = http.HandlerFunc(AcceptBasketRequests)
Expand Down
2 changes: 1 addition & 1 deletion web/baskets.html
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head lang="en">
<title>Request Baskets</title>
<title>Request Baskets - Administration</title>
<!-- Bootstrap -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
Expand Down
16 changes: 10 additions & 6 deletions web_basket.html.go
@@ -1,7 +1,7 @@
package main

const (
basketPageContent = `<!DOCTYPE html>
basketPageContentTemplate = `<!DOCTYPE html>
<html>
<head lang="en">
<title>Request Basket: {{.}}</title>
Expand All @@ -26,7 +26,7 @@ const (
var autoRefreshId;
function getToken() {
var token = sessionStorage.getItem("token_{{.}}");
var token = localStorage.getItem("basket_{{.}}");
if (!token) { // fall back to master token if provided
token = sessionStorage.getItem("master_token");
}
Expand All @@ -35,6 +35,7 @@ const (
function onAjaxError(jqXHR) {
if (jqXHR.status == 401) {
localStorage.removeItem("basket_{{.}}");
enableAutoRefresh(false);
$("#token_dialog").modal({ keyboard : false });
} else {
Expand Down Expand Up @@ -116,8 +117,10 @@ const (
$("#requests_count").html(data.count + " (" + totalCount + ")");
if (data.count > 0) {
$("#empty_basket").addClass("hide");
$("#requests_link").removeClass("hide");
} else {
$("#empty_basket").removeClass("hide");
$("#requests_link").addClass("hide");
}
if (data && data.requests) {
Expand Down Expand Up @@ -348,17 +351,17 @@ const (
"Authorization" : getToken()
}
}).done(function(data) {
sessionStorage.removeItem("token_{{.}}");
localStorage.removeItem("basket_{{.}}");
window.location.href = "/web";
}).fail(onAjaxError);
}
// Initialization
$(document).ready(function() {
$("#basket_uri").html(window.location.protocol + "//" + window.location.host + "/{{.}}");
$(".basket_uri").html(window.location.protocol + "//" + window.location.host + "/{{.}}");
// dialogs
$("#token_dialog").on("hidden.bs.modal", function (event) {
sessionStorage.setItem("token_{{.}}", $("#basket_token").val());
localStorage.setItem("basket_{{.}}", $("#basket_token").val());
fetchRequests();
});
$("#config_form").on("submit", function(event) {
Expand Down Expand Up @@ -594,6 +597,7 @@ const (
<div class="row">
<div class="col-md-8">
<h1>Basket: {{.}}</h1>
<p id="requests_link" class="hide">Requests are collected at <kbd class="basket_uri"></kbd></p>
</div>
<div class="col-md-3 col-md-offset-1">
<h4><abbr title="Current requests count (Total count)">Requests</abbr>: <span id="requests_count"></span></h4>
Expand All @@ -611,7 +615,7 @@ const (
<!-- Empty basket -->
<div class="jumbotron text-center hide" id="empty_basket">
<h1>Empty basket!</h1>
<p>This basket is empty, send requests to <kbd id="basket_uri">/{{.}}</kbd> and they will appear here.</p>
<p>This basket is empty, send requests to <kbd class="basket_uri"></kbd> and they will appear here.</p>
</div>
</div>
Expand Down
171 changes: 171 additions & 0 deletions web_baskets.html.go
@@ -0,0 +1,171 @@
package main

var (
basketsPageContent = []byte(`<!DOCTYPE html>
<html>
<head lang="en">
<title>Request Baskets - Administration</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" integrity="sha384-T8Gy5hrqNKT+hzMclPo118YTQO6cYprQmhrYwIiQ/3axmI1hQomh7Ud2hPOy8SP1" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<style>
html { position: relative; min-height: 100%; }
body { padding-top: 70px; margin-bottom: 60px; }
.footer { position: absolute; bottom: 0; width: 100%; height: 60px; background-color: #f5f5f5; }
.container .text-muted { margin: 20px 0; }
h1 { margin-top: 2px; }
#more { margin-left: 60px; padding-bottom: 10px; }
#all_baskets ul { width: 100%; }
#all_baskets li { padding: 0 0 5px 20px; float: left; display: inline; position: relative; width: 50%; }
#all_baskets li:before { content: "\f291"; font-family: "FontAwesome"; position: absolute; left: 0px; top:0px; }
</style>
<script>
(function($) {
var basketsCount = 0;
function onAjaxError(jqXHR) {
if (jqXHR.status == 401) {
$("#master_token_dialog").modal({ keyboard : false });
} else {
$("#error_message_label").html("HTTP " + jqXHR.status + " - " + jqXHR.statusText);
$("#error_message_text").html(jqXHR.responseText);
$("#error_message").modal();
}
}
function addBaskets(data) {
if (data && data.names) {
var baskets = $("#all_baskets");
var index, name;
for (index = 0; index < data.names.length; ++index) {
name = data.names[index];
baskets.append("<li><a href='/web/" + name + "'>" + name + "</a></li>");
basketsCount++;
}
if (data.has_more) {
$("#more").removeClass("hide");
} else {
$("#more").addClass("hide");
}
}
}
function fetchBaskets() {
$.ajax({
method: "GET",
url: "/baskets?skip=" + basketsCount,
headers: {
"Authorization" : sessionStorage.getItem("master_token")
}
}).done(function(data) {
addBaskets(data);
}).fail(onAjaxError);
}
function saveMasterToken() {
var token = $("#master_token").val();
$("#master_token").val("");
$("#master_token_dialog").modal("hide");
if (token) {
sessionStorage.setItem("master_token", token);
} else {
sessionStorage.removeItem("master_token");
}
fetchBaskets();
}
// Initialization
$(document).ready(function() {
$("#master_token_dialog").on("hidden.bs.modal", function (event) {
saveMasterToken();
});
$("#fetch_more").on("click", function(event) {
fetchBaskets();
});
fetchBaskets();
});
})(jQuery);
</script>
</head>
<body>
<!-- Fixed navbar -->
<nav class="navbar navbar-default navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/web">Request Baskets</a>
</div>
</div>
</nav>
<!-- Error message -->
<div class="modal fade" id="error_message" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content panel-danger">
<div class="modal-header panel-heading">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title" id="error_message_label">HTTP error</h4>
</div>
<div class="modal-body">
<p id="error_message_text"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Master token dialog -->
<div class="modal fade" id="master_token_dialog" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content panel-warning">
<div class="modal-header panel-heading">
<h4 class="modal-title">Master Token</h4>
</div>
<form id="master_token_form">
<div class="modal-body">
<p>By providing the master token you will gain access to all baskets.</p>
<div class="form-group">
<label for="master_token" class="control-label">Token:</label>
<input type="password" class="form-control" id="master_token">
</div>
</div>
<div class="modal-footer">
<a href="/web" class="btn btn-default">Back to list of your baskets</a>
<button type="submit" class="btn btn-success" data-dismiss="modal">Authorize</button>
</div>
</form>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-4">
<h1>All Baskets</h1>
</div>
</div>
<hr/>
<div class="row">
<ul id="all_baskets">
</ul>
<div id="more" class="hide">
<a id="fetch_more" class="btn btn-default btn-s">more...</a>
</div>
</div>
</div>
<footer class="footer">
<div class="container">
<p class="text-muted"><small>Powered by <a href="https://github.com/darklynx/request-baskets">request-baskets</a></small></p>
</div>
</footer>
</body>
</html>`)
)

0 comments on commit 76dca04

Please sign in to comment.