Remove WebUI (now contained in a separate package)

It now lives at https://github.com/pilosa/webui
This commit is contained in:
Cody Soyland 2018-06-06 11:44:16 -05:00
parent f147630051
commit 34cea87683
24 changed files with 30 additions and 1224 deletions

View file

@ -13,7 +13,7 @@ env:
- GOARCH=amd64
- GOARCH=amd64 ENTERPRISE=1
install:
- make install-dep install-statik vendor generate-statik
- make install-dep vendor
script:
- make test
before_deploy:

View file

@ -78,7 +78,7 @@ Pilosa includes a Makefile that automates several tasks:
make install
```
- Install build dependencies (dep, statik, and protoc):
- Install build dependencies (dep and protoc):
```sh
make install-build-deps
@ -114,12 +114,6 @@ Pilosa includes a Makefile that automates several tasks:
make release
```
- Generate static assets for the WebUI:
```sh
make generate-statik
```
- Regenerate protocol buffer files in `internal/`:
```sh

View file

@ -3,7 +3,7 @@ FROM golang:1.10.2 as builder
COPY . /go/src/github.com/pilosa/pilosa/
RUN cd /go/src/github.com/pilosa/pilosa \
&& CGO_ENABLED=0 make install-dep install-statik install FLAGS="-a"
&& CGO_ENABLED=0 make install-dep install FLAGS="-a"
FROM scratch

View file

@ -1,4 +1,4 @@
.PHONY: build check-clean clean cover cover-viz default docker docker-build docker-test generate generate-protoc generate-statik install install-build-deps install-dep install-protoc install-protoc-gen-gofast install-statik prerelease prerelease-build prerelease-upload release release-build require-dep require-protoc require-protoc-gen-gofast require-statik test
.PHONY: build check-clean clean cover cover-viz default docker docker-build docker-test generate generate-protoc install install-build-deps install-dep install-protoc install-protoc-gen-gofast prerelease prerelease-build prerelease-upload release release-build require-dep require-protoc require-protoc-gen-gofast test
CLONE_URL=github.com/pilosa/pilosa
VERSION := $(shell git describe --tags 2> /dev/null || echo unknown)
@ -88,16 +88,12 @@ install: vendor
generate-protoc: require-protoc require-protoc-gen-gofast
go generate github.com/pilosa/pilosa/internal
# `go generate` statik assets (WebUI)
generate-statik: require-statik
go generate github.com/pilosa/pilosa/statik
# `go generate` stringers
generate-stringer:
go generate github.com/pilosa/pilosa
# `go generate` all needed packages
generate: generate-protoc generate-statik generate-stringer
generate: generate-protoc generate-stringer
# Create Docker image from Dockerfile
docker:
@ -126,23 +122,17 @@ endef
require-dep:
$(call require,dep)
require-statik:
$(call require,statik)
require-protoc-gen-gofast:
$(call require,protoc-gen-gofast)
require-protoc:
$(call require,protoc)
install-build-deps: install-dep install-statik install-protoc-gen-gofast install-protoc install-stringer
install-build-deps: install-dep install-protoc-gen-gofast install-protoc install-stringer
install-dep:
go get -u github.com/golang/dep/cmd/dep
install-statik:
go get -u github.com/rakyll/statik
install-stringer:
go get -u golang.org/x/tools/cmd/stringer

View file

@ -112,10 +112,6 @@ Note that both the user IDs and the repository IDs were remapped to sequential i
#### Make Some Queries
<div class="note">
<p>Note the Pilosa server comes with a <a href="../webui/">WebUI</a> for constructing queries in a browser. In local development, it is available at <a href="http://localhost:10101">localhost:10101</a>.</p>
</div>
Which repositories did user 14 star:
``` request
curl localhost:10101/index/repository/query \

View file

@ -142,11 +142,10 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
git clone https://github.com/pilosa/pilosa.git
```
3. Build the Pilosa repo (the `make generate-statik` line isn't necessary but builds a nice [webUI](../webui/) into Pilosa):
3. Build the Pilosa repo:
```
cd $GOPATH/src/github.com/pilosa/pilosa
make install-build-deps
make generate-statik
make install
```
@ -294,11 +293,10 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
git clone https://github.com/pilosa/pilosa.git
```
3. Build the Pilosa repo (the `make generate-statik` line isn't necessary but builds a nice [webUI](../webui/) into Pilosa):
3. Build the Pilosa repo:
```
cd $GOPATH/src/github.com/pilosa/pilosa
make install-build-deps
make generate-statik
make install
```

View file

@ -9,13 +9,27 @@ nav = [
## WebUI
The Pilosa server comes packaged with in-browser WebUI. When you run a local Pilosa server on the default host, you can access it at [localhost:10101](http://localhost:10101).
A web-based app called Pilosa WebUI is available in a separate package. This can be used for constructing queries and viewing the cluster status.
This can be used for constructing queries and viewing the cluster status.
### Installation
Releases are [available on Github](https://github.com/pilosa/webui/releases) as well as on [Homebrew](https://brew.sh/) for Mac.
Installing on a Mac with Homebrew is simple; just run:
```
brew install pilosa-webui
```
You may also build from source by checking out the [repo on Github](https://github.com/pilosa/webui) and running:
```
make install
```
### Console
The [Console view](http://localhost:10101/#console) allows you to enter [PQL](../query-language/) queries and run them against your locally running server. First you must select an Index with the Select index dropdown.
The Console view allows you to enter [PQL](../query-language/) queries and run them against your locally running server. First you must select an Index with the Select index dropdown.
Each query's result will be displayed in the Output section along with the query time.
@ -39,4 +53,4 @@ Frame creation also supports options like `timeQuantum`. When creating a new fra
### Cluster Admin
Use the [Cluster Admin tab](http://localhost:10101/#admin) to view the current status of your cluster. This contains information on each node in the cluster, plus the list of Indexes and Frames.
Use the Cluster Admin tab to view the current status of your cluster. This contains information on each node in the cluster, plus the list of Indexes and Frames.

View file

@ -1,42 +0,0 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pilosa
import (
"fmt"
"net/http"
)
// Ensure nopFileSystem implements interface.
var _ FileSystem = &nopFileSystem{}
// FileSystem represents an interface for a WebUI file system.
type FileSystem interface {
New() (http.FileSystem, error)
}
func init() {
NopFileSystem = &nopFileSystem{}
}
// NopFileSystem represents a FileSystem that returns an error if called.
var NopFileSystem FileSystem
type nopFileSystem struct{}
// New is a no-op implementation of FileSystem New method.
func (n *nopFileSystem) New() (http.FileSystem, error) {
return nil, fmt.Errorf("file system not implemented")
}

View file

@ -42,8 +42,6 @@ import (
type Handler struct {
Handler http.Handler
FileSystem FileSystem
Logger Logger
// Keeps the query argument validators for each handler
@ -87,8 +85,7 @@ func OptHandlerAllowedOrigins(origins []string) HandlerOption {
// NewHandler returns a new instance of Handler with a default logger.
func NewHandler(opts ...HandlerOption) (*Handler, error) {
handler := &Handler{
FileSystem: NopFileSystem,
Logger: NopLogger,
Logger: NopLogger,
}
handler.Handler = NewRouter(handler)
handler.populateValidators()
@ -137,8 +134,7 @@ func (h *Handler) queryArgValidator(next http.Handler) http.Handler {
// NewRouter creates a new mux http router.
func NewRouter(handler *Handler) *mux.Router {
router := mux.NewRouter()
router.HandleFunc("/", handler.handleWebUI).Methods("GET")
router.HandleFunc("/assets/{file}", handler.handleWebUI).Methods("GET")
router.HandleFunc("/", handler.handleHome).Methods("GET")
router.HandleFunc("/cluster/message", handler.handlePostClusterMessage).Methods("POST")
router.HandleFunc("/cluster/resize/set-coordinator", handler.handlePostClusterResizeSetCoordinator).Methods("POST")
router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux).Methods("GET")
@ -226,19 +222,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (h *Handler) handleWebUI(w http.ResponseWriter, r *http.Request) {
// If user is using curl, don't chuck HTML at them
if strings.HasPrefix(r.UserAgent(), "curl") {
http.Error(w, "Welcome. Pilosa is running. Visit https://www.pilosa.com/docs/ for more information or try the WebUI by visiting this URL in your browser.", http.StatusNotFound)
return
}
filesystem, err := h.FileSystem.New()
if err != nil {
_ = h.writeQueryResponse(w, r, &QueryResponse{Err: err})
h.Logger.Printf("Pilosa WebUI is not available. Please run `make generate-statik` before building Pilosa with `make install`.")
return
}
http.FileServer(filesystem).ServeHTTP(w, r)
func (h *Handler) handleHome(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Welcome. Pilosa is running. Visit https://www.pilosa.com/docs/ for more information.", http.StatusNotFound)
}
// handleGetSchema handles GET /schema requests.

View file

@ -31,7 +31,6 @@ import (
"github.com/pilosa/pilosa"
"github.com/pilosa/pilosa/internal"
"github.com/pilosa/pilosa/pql"
"github.com/pilosa/pilosa/statik"
"github.com/pilosa/pilosa/test"
)
@ -915,34 +914,6 @@ func TestHandler_RecalculateCaches(t *testing.T) {
}
func TestHandler_WebUI(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()
h := test.MustNewHandler()
h.API.Holder = hldr.Holder
h.API.Cluster = test.NewCluster(1)
h.FileSystem = &statik.FileSystem{}
w := httptest.NewRecorder()
h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/", nil))
if w.Code != http.StatusOK {
t.Fatalf("unexpected status code: %d", w.Code)
}
if !strings.Contains(w.Body.String(), "<title>Pilosa WebUI</title>") {
t.Fatalf("WebUI is not being served correctly.")
}
// If curl is the client, the response should be different
w = httptest.NewRecorder()
req := test.MustNewHTTPRequest("GET", "/", nil)
req.Header.Add("User-Agent", "curl/7.54.0")
h.ServeHTTP(w, req)
if !strings.Contains(w.Body.String(), "try the WebUI") {
t.Fatalf("WebUI is not being served correctly.")
}
}
func TestHandler_CORS(t *testing.T) {
hldr := test.MustOpenHolder()
defer hldr.Close()

View file

@ -39,7 +39,6 @@ import (
"github.com/pilosa/pilosa/gcnotify"
"github.com/pilosa/pilosa/gopsutil"
"github.com/pilosa/pilosa/gossip"
"github.com/pilosa/pilosa/statik"
"github.com/pilosa/pilosa/statsd"
"github.com/pkg/errors"
)
@ -170,7 +169,6 @@ func (m *Command) SetupServer() error {
return errors.Wrap(err, "wrapping handler")
}
handler.Logger = m.logger
handler.FileSystem = &statik.FileSystem{}
handler.API = pilosa.NewAPI()
handler.API.Logger = m.logger

1
statik/.gitignore vendored
View file

@ -1 +0,0 @@
/statik.go

View file

@ -1,37 +0,0 @@
// Copyright 2017 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//go:generate statik -src=../webui -dest=..
//
// Package statik contains static assets for the Web UI. `go generate` or
// `make generate-statik` will produce statik.go, which is ignored by git.
package statik
import (
"net/http"
"github.com/pilosa/pilosa"
"github.com/rakyll/statik/fs"
)
// Ensure nopFileSystem implements interface.
var _ pilosa.FileSystem = &FileSystem{}
// FileSystem represents a static FileSystem.
type FileSystem struct{}
// New is a statik implementation of FileSystem New method.
func (s *FileSystem) New() (http.FileSystem, error) {
return fs.New()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -1,594 +0,0 @@
class REPL {
constructor(input, output, button, completer) {
this.input = input
this.output = output
this.button = button
this.completer = completer
this.history = []
this.history_index = 0
this.history_buffer = ''
this.result_number = 0
}
bind_events() {
var repl = this
var keys = {
TAB: 9,
ENTER: 13,
UP_ARROW: 38,
DOWN_ARROW: 40
}
this.input.addEventListener("keydown", function(e) {
if (e.keyCode == keys.UP_ARROW) {
e.preventDefault()
if (repl.input.value.substring(0, repl.input.selectionStart).indexOf('\n') == '-1') {
if (repl.history_index == 0) {
return
} else {
if (repl.history_index == repl.history.length) {
repl.history_buffer = repl.input.value
}
repl.history_index--
repl.input.value = repl.history[repl.history_index]
repl.input.setSelectionRange(repl.input.value.length, repl.input.value.length)
}
}
}
if (e.keyCode == keys.DOWN_ARROW) {
e.preventDefault()
if (repl.input.value.substring(repl.input.selectionEnd, repl.input.length).indexOf('\n') == '-1') {
if (repl.history_index == repl.history.length) {
return
} else {
repl.history_index++
if (repl.history_index == repl.history.length) {
repl.input.value = repl.history_buffer
} else {
repl.input.value = repl.history[repl.history_index]
}
repl.input.setSelectionRange(repl.input.value.length, repl.input.value.length)
}
}
}
if (e.keyCode == keys.ENTER && !e.shiftKey) {
e.preventDefault()
repl.submit();
}
if (e.keyCode == keys.TAB) {
e.preventDefault()
repl.completer.complete()
}
})
repl.button.onclick = function() {
repl.submit();
};
}
submit() {
this.history_buffer = ''
this.history_index = this.history.length
this.history[this.history_index] = this.input.value
this.history_index++
this.process_query(this.input.value)
this.input.value = ""
}
process_query(query) {
var xhr = new XMLHttpRequest();
var url, data, request, command_name;
var e = document.getElementById("index-dropdown");
var indexname = e.options[e.selectedIndex].text;
var repl = this;
if (query.startsWith(":")) {
var parsed_query = parse_query(query, indexname);
if (Object.keys(parsed_query).length === 0) {
repl.create_single_output({
"input": query,
"output": "invalid query",
"status": 400,
"indexname": indexname,
});
return;
} else {
// set selectedIndex from dropdown list
if (parsed_query.command === "use") {
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].text === parsed_query.command_name) {
e.selectedIndex = i;
break;
}
}
return;
}
url = parsed_query.url;
data = parsed_query.data;
request = parsed_query.request;
command_name = parsed_query.command_name;
}
}
else {
url = '/index/' + indexname + '/query'
request = "POST"
data = query
}
xhr.open(request, url);
xhr.setRequestHeader('Content-Type', 'application/text');
var start_time = new Date().getTime();
xhr.onload = function () {
var end_time = new Date().getTime();
repl.result_number++
repl.create_single_output({
"input": query,
"output": xhr.responseText,
"status": xhr.status,
"indexname": indexname,
"querytime_ms": end_time - start_time,
});
};
xhr.send(data);
// Remove index from dropdown with delete index command
if (request === 'DELETE' && url === '/index/' + command_name){
for (var i = 0; i < e.options.length; i++) {
if (e.options[i].text === command_name) {
e.remove(i);
break;
}
}
}
}
create_single_output(res) {
var node = document.createElement("div");
node.classList.add('output');
var output_string = res['output']
var result_class = "result-output"
var getting_started_errors = [
'index not found',
'frame not found',
]
var output_json;
if (isJSON(output_string)) {
output_json = JSON.parse(output_string)
}
// handle output formatting
if (res["status"] != 200) {
result_class = "result-error";
if (output_json) {
if ("error" in output_json) {
if (getting_started_errors.indexOf(output_json['error']) >= 0) {
output_string += `<br />
<br />
Just getting started? Try this:<br />
:create index test<br />
:use test<br />
:create frame foo<br />
SetBit(rowID=0, columnID=0, frame=foo) # Use PQL to set a bit
`
}
}
}
}
var markup =`
<div class="panes">
<div class="pane active">
<div class="result-io">
<div class="result-io-header">
<h5>Input</h5>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<em>Source: ${res.indexname}</em>
</div>
<div class="result-input">
${res.input}
</div>
</div>
<div class="result-io">
<div class="result-io-header">
<h5>output</h5>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<em>${res.querytime_ms} ms</em>
</div>
<div class="${result_class}">
${output_string}
</div>
<a href="#" class="expand"><h5>Expand</h5></a>
</div>
</div>
<div class="pane">
<div class="raw">
<a href="${res.url}">Export raw .csv</a>
</div>
</div>
</div>
`
node.innerHTML = markup;
this.output.insertBefore(node, this.output.firstChild);
// Expand when overflow
var element = this.output.firstChild.getElementsByClassName(result_class)[0];
var expand = this.output.firstChild.getElementsByClassName("expand")[0];
if (element.clientHeight < element.scrollHeight) {
expand.style.display = 'block';
} else {
expand.style.display = 'none';
}
expand.onclick = function () {
element.style.height = element.scrollHeight + "px";
expand.style.display = 'none';
return false;
};
}
populate_index_dropdown() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/schema')
var select = document.getElementById('index-dropdown')
xhr.onload = function() {
var schema = JSON.parse(xhr.responseText)
for(var i=0; i<schema['indexes'].length; i++) {
var opt = document.createElement('option')
opt.value = i+1
opt.innerHTML = schema['indexes'][i]['name']
select.appendChild(opt)
}
// set the active option to one of the populated options, instead of invalid "Index"
if(i > 0) {
select.value = 1;
}
}
xhr.send(null)
}
}
function populate_version() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/version')
var node = document.getElementById('server-version')
xhr.onload = function() {
var version = JSON.parse(xhr.responseText)['version']
var version_major_minor = /v?(\d+\.\d+).*/.exec(version)[1]
var doc_link = document.getElementById('nav-documentation')
doc_link.onclick = function() {
window.open('https://www.pilosa.com/docs/v' + version_major_minor + '/introduction/')
}
node.innerHTML = "Pilosa v" + version
}
xhr.send(null)
}
function handle_nav_click(e) {
// e.id = "nav-xxx"
name = e.id.substring(4)
set_active_pane_by_name(name)
window.location.hash = name
}
function set_active_pane_by_name(name) {
// toggle the nav buttons
document.getElementsByClassName("nav-active")[0].classList.remove("nav-active")
document.getElementById("nav-" + name).classList.add("nav-active")
// toggle the main interface content divs
document.getElementsByClassName("interface-active")[0].classList.remove("interface-active")
document.getElementById('interface-' + name).classList.add("interface-active")
// hack hack
switch(name) {
case "cluster":
update_cluster_status()
break
case "documentation":
open_external_docs()
break
}
}
function update_cluster_status() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/status')
xhr.onload = function() {
var status = JSON.parse(xhr.responseText)
render_status(status)
}
xhr.send(null)
var xhrSchema = new XMLHttpRequest();
xhrSchema.open('GET', '/schema')
xhrSchema.onload = function() {
var schema = JSON.parse(xhrSchema.responseText)
render_schema(schema)
}
xhrSchema.send(null)
}
function render_status(status) {
// render node table
var nodes_div = document.getElementById("status-nodes")
while (nodes_div.firstChild) {
nodes_div.removeChild(nodes_div.firstChild);
}
var nodes = status["nodes"]
table = document.createElement("table")
tbody = document.createElement("tbody")
table.appendChild(tbody)
var caption = document.createElement("caption")
caption.innerHTML = "(" + nodes.length + ")"
table.appendChild(caption)
var header = document.createElement('tr')
markup = `<th>Host</th>
<th>ID</th>
<th>Coordinator</th>`
header.innerHTML = markup
tbody.appendChild(header)
for(var n=0; n<nodes.length; n++) {
var row = document.createElement("tr")
markup = `<td>${nodes[n]["uri"]["host"]}:${nodes[n]["uri"]["port"]}</td>
<td>${nodes[n]["id"]}</td>
<td>${nodes[n]["isCoordinator"]}</td>`
row.innerHTML = markup
tbody.appendChild(row)
}
nodes_div.appendChild(table)
}
function render_schema(schema) {
// render index tables
var indexes_div = document.getElementById("status-indexes")
while (indexes_div.firstChild) {
indexes_div.removeChild(indexes_div.firstChild);
}
var indexes = schema["indexes"] // TODO currently comes from only node 0
for(var n=0; n<indexes.length; n++) {
table = document.createElement("table")
tbody = document.createElement("tbody")
table.appendChild(tbody)
var caption = document.createElement("caption")
caption.innerHTML = indexes[n]["name"]
table.appendChild(caption)
var header = document.createElement('tr')
markup = `<th>Name</th>
<th>Cache Type</th>
<th>Cache Size</th>`
header.innerHTML = markup
tbody.appendChild(header)
var frames = indexes[n]["frames"]
if(frames) {
for(var m=0; m<frames.length; m++) {
var row = document.createElement("tr")
row.innerHTML = `<td>${frames[m]["name"]}</td>
<td>${frames[m]["options"]["cacheType"]}</td>
<td>${frames[m]["options"]["cacheSize"]}</td>`
tbody.appendChild(row)
}
}
indexes_div.appendChild(table)
}
// render slice tables
// TODO enable when Slices element is present in status response
/*
var slices_div = document.getElementById("status-slices")
data = ""
for(var n=0; n<nodes.length; n++) {
var indexes = nodes[n]["Indexes"]
for(var m=0; m<indexes.length; m++) {
data += nodes[n]["Host"] + "/" + indexes[m]["Name"] + ": " + indexes[m]["Slices"] + "<br />"
}
}
slices_div.innerHTML = data
*/
}
function open_external_docs() {
window.open("https://www.pilosa.com/docs");
}
function check_anchor_uri() {
var pane_names = {"console": 0, "cluster": 0, "documentation": 0}
var anchor = window.location.hash.substr(1);
if(anchor in pane_names) {
set_active_pane_by_name(anchor)
}
}
Date.prototype.today = function () {
return this.getFullYear() +"/"+ (((this.getMonth()+1) < 10)?"0":"") + (this.getMonth()+1) +"/"+ ((this.getDate() < 10)?"0":"") + this.getDate();
}
Date.prototype.timeNow = function () {
return ((this.getHours() < 10)?"0":"") + this.getHours() +":"+ ((this.getMinutes() < 10)?"0":"") + this.getMinutes() +":"+ ((this.getSeconds() < 10)?"0":"") + this.getSeconds();
}
populate_version()
class Autocompleter {
constructor(input, output) {
this.input = input
this.output = output
this.keyword_map = this.static_keywords
this.init_dynamic_keywords()
}
get static_keywords() {
return {
// keyword: length of substring that comes after cursor
"SetBit()": 1,
"ClearBit()": 1,
"SetRowAttrs()": 1,
"SetColumnAttrs()": 1,
"Bitmap()": 1,
"Union()": 1,
"Intersect()": 1,
"Difference()": 1,
"Count()": 1,
"Range()": 1,
"TopN()": 1,
"frame=": 0,
}
}
complete() {
var completer = this
// extract word fragment ending at cursor. a word fragment:
// - starts with last nonalpha character before cursor (or beginning of string)
// - ends at cursor
var word_start = completer.input.selectionEnd-1
while(word_start>0) {
var c = completer.input.value.charCodeAt(word_start)
if(!((c>64 && c<91) || (c>96 && c<123))) {
word_start++
break
}
word_start--
}
var input_word = completer.input.value.substring(word_start, completer.input.selectionEnd)
// check for keyword match and insert if exactly one match
var matches = []
for(var keyword in this.keyword_map) {
if(keyword.startsWith(input_word)){
matches.push(keyword)
}
}
if(matches.length > 1) {
// completer.output.innerHTML = whatever
}
if(matches.length == 1) {
// completer.output.innerHTML = ""
var cursor_pos = completer.input.selectionEnd
var completion = matches[0].substring(input_word.length)
var before = completer.input.value.substring(0, cursor_pos)
var after = completer.input.value.substring(cursor_pos)
completer.input.value = before + completion + after
var new_pos = cursor_pos + completion.length - this.keyword_map[matches[0]]
completer.input.setSelectionRange(new_pos, new_pos)
}
}
init_dynamic_keywords() {
// hit /schema, parse indexes, frames, add to list
}
add_keyword() {
// call when index or frame created in webui
}
remove_keyword() {
// call when index or frame deleted in webui
// issue: if e.g. multiple indexes have same frame, removing one removes all.
// solution: maintain count. requires more elaborate representation of keywords.
}
}
var input = document.getElementById('query')
var output = document.getElementById('outputs')
var button = document.getElementById('query-btn')
var autocomplete_output = document.getElementById('autocomplete-container')
autocompleter = new Autocompleter(input, autocomplete_output)
repl = new REPL(input, output, button, autocompleter)
repl.populate_index_dropdown()
repl.bind_events()
input.focus()
check_anchor_uri()
function isJSON(str) {
try {
JSON.parse(str)
} catch (e) {
return false
}
return true
}
function parse_query(query, indexname) {
var keys = query.replace(/\s+/g, " ").split(" ");
var command = keys[0];
var command_type = keys[1];
var command_name = keys[2];
var option_str = keys.slice(3, keys.length)
var options = parse_options(option_str);
if (command !== ":use") {
if (!command_name){
return {}
}
}
var parsed_query = {};
parsed_query["command"] = command.substr(1, command.length);
parsed_query["command_name"] = command_name;
switch (command) {
case ":create":
parsed_query["request"] = "POST";
if(Object.keys(options).length === 0) {
parsed_query["data"] = "";
} else {
var opts = {"options":{}};
for (var o in options) {
opts.options[o] = options[o]
}
parsed_query["data"] = JSON.stringify(opts);
}
switch (command_type){
case "index":
parsed_query["url"] = '/index/' + command_name;
break;
case "frame":
parsed_query["url"] = '/index/' + indexname + '/frame/' + command_name;
break
}
break;
case ":delete":
parsed_query["request"] = "DELETE";
switch (command_type){
case "index":
parsed_query["url"] = '/index/' + command_name;
parsed_query["data"] = "";
break;
case "frame":
parsed_query["url"] = '/index/' + indexname + '/frame/' + command_name;
parsed_query["data"] = "";
break;
}
break;
case ":use":
parsed_query["command_name"] = keys[1];
break;
default:
return {}
}
return parsed_query;
}
function parse_options(option_str) {
var int_keys = ["cacheSize"];
var options = {};
for (var i = 0; i < option_str.length; i++) {
var parts = option_str[i].split('=');
if (int_keys.indexOf(parts[0]) !== -1 ){
options[parts[0]] = Number(parts[1])
} else {
options[parts[0]] = parts[1]
}
}
return options;
}

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100.75 36.67"><defs><style>.cls-1{fill:#1db598;}.cls-2{fill:none;stroke:#3c5f8d;stroke-miterlimit:10;stroke-width:5px;}</style></defs><title>nav_cluster_1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><circle class="cls-1" cx="55.14" cy="18.24" r="9.61"/><circle class="cls-1" cx="18.33" cy="18.33" r="9.61"/><circle class="cls-1" cx="91.14" cy="18.24" r="9.61"/><circle class="cls-2" cx="18.33" cy="18.33" r="15.83"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 521 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100.75 36.67"><defs><style>.cls-1{fill:#1db598;}.cls-2{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:5px;}</style></defs><title>nav_cluster_1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><circle class="cls-1" cx="55.14" cy="18.24" r="9.61"/><circle class="cls-1" cx="18.33" cy="18.33" r="9.61"/><circle class="cls-1" cx="91.14" cy="18.24" r="9.61"/><circle class="cls-2" cx="18.33" cy="18.33" r="15.83"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 517 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.47 48.95"><defs><style>.cls-1{fill:none;stroke:#3c5f8d;stroke-miterlimit:10;stroke-width:6px;}.cls-2{fill:#1db598;}</style></defs><title>nav_console</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><rect class="cls-1" x="3" y="3" width="72.47" height="42.95"/><circle class="cls-2" cx="39.25" cy="25.92" r="9.61"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 416 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 78.47 48.95"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:6px;}.cls-2{fill:#1db598;}</style></defs><title>nav_console</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><rect class="cls-1" x="3" y="3" width="72.47" height="42.95"/><circle class="cls-2" cx="39.25" cy="25.92" r="9.61"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 414 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 44.5"><defs><style>.cls-1{fill:none;stroke:#3c5f8d;stroke-miterlimit:10;stroke-width:5px;}.cls-2{fill:#1db598;}</style></defs><title>documentation</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><line class="cls-1" y1="2.5" x2="70" y2="2.5"/><line class="cls-1" y1="20" x2="70" y2="20"/><line class="cls-1" y1="38.5" x2="52.82" y2="38.5"/><circle class="cls-2" cx="63.5" cy="38.5" r="6"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 492 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 44.5"><defs><style>.cls-1{fill:none;stroke:#fff;stroke-miterlimit:10;stroke-width:5px;}.cls-2{fill:#1db598;}</style></defs><title>documentation</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><line class="cls-1" y1="2.5" x2="70" y2="2.5"/><line class="cls-1" y1="20" x2="70" y2="20"/><line class="cls-1" y1="38.5" x2="52.82" y2="38.5"/><circle class="cls-2" cx="63.5" cy="38.5" r="6"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 488 B

View file

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.47 45.95"><defs><style>.cls-1{fill:none;stroke:#3c5f8d;stroke-miterlimit:10;stroke-width:3px;}.cls-2{fill:#1db598;}</style></defs><title>nav_item1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><rect class="cls-1" x="1.5" y="1.5" width="72.47" height="42.95"/><circle class="cls-2" cx="37.75" cy="24.42" r="9.61"/></g></g></svg>

Before

Width:  |  Height:  |  Size: 418 B

View file

@ -1,336 +0,0 @@
*{
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body{
font-family: sans-serif;
background-color: #fbfcfd;
margin: 0;
color: #102445;
}
h2{
margin-bottom: 30px;
}
h5{
text-transform: uppercase;
letter-spacing: 2px;
line-height: 1.21;
margin: 0;
}
a{
line-height: 1.38;
letter-spacing: 0.2px;
text-decoration: none;
color: #102445;
}
a:hover{
color: #1db598;
}
textarea{
width: 100%;
margin-bottom: 10px;
border-radius: 2px;
background-color: #fbfcfd;
border: solid 1.5px #e4eff4;
font-family: monospace;
font-size: 16px;
line-height: 1.5;
letter-spacing: 1.1px;
outline: none;
padding: 30px;
}
select{
/*-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: url("img/chevron-down.png") no-repeat calc(100% - 10px) !important;*/
border-radius: 3px;
background-color: #fbfcfd;
width: 187px;
height: 50px;
border: solid 1.5px #e4eff4;
font-size: 18px;
font-weight: bold;
line-height: 1.39;
letter-spacing: 0.2px;
color: #102445;
padding: 10.5px;
}
button{
width: 165px;
height: 50px;
border-radius: 3px;
background-color: #1db598;
outline: none;
border: none;
font-size: 16px;
color: white;
}
em{
font-style: normal;
opacity: 0.5;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.2px;
color: #102445;
}
.header{
height: 92px;
display: flex;
align-items: center;
justify-content: space-between;
width: 90%;
margin: auto;
}
.container{
display: flex;
height:100%;
min-height: 100vh;
}
.nav{
color: white;
display: flex;
flex-direction: column;
width: 150px;
background: #3c5f8d;
}
.nav-item{
height:150px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
border-bottom: 3px solid #2a4871;
cursor: pointer;
}
.nav-active{
background: #f2f7f9;
font-weight: bold;
color: #1db598;
}
.nav-item > .nav-image {
display: flex;
}
.nav-item > .nav-image-active {
display: none;
}
.nav-active > .nav-image {
display: none;
}
.nav-active > .nav-image-active {
display: flex;
}
.interface{
display: none;
flex: 1;
flex-direction: column;
align-items: center;
background: #f2f7f9;
}
.interface-active{
display: flex;
}
.query{
margin-bottom: 30px;
}
.query,
.output-container,
.status-container{
width: 75%;
}
.output{
margin-bottom: 30px;
}
.input-controls{
display: flex;
justify-content: flex-end;
}
.tabs{
display: flex;
background: #eaf2f6;
}
.active-tab{
background: white;
font-weight: bold;
color: #1db598;
}
.tab{
height:60px;
width: 100px;
border-top-right-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
visibility: visible;
cursor: pointer;
}
.pane{
background: white;
padding: 30px;
display: none;
}
.active{
display: block;
}
.result-io-header{
display: flex;
align-items: center;
margin-bottom: 15px;
}
.result-input,
.result-output,
.result-error{
height: 60px;
border-radius: 2px;
background-color: #fafafa;
border: solid 1.5px #e4eff4;
font-family: monospace;
font-size: 16px;
line-height: 1.5;
letter-spacing: 1.1px;
color: #102445;
padding: 15px;
margin-bottom: 15px;
word-break: break-all;
overflow-wrap: break-word;
overflow:hidden;
}
.result-output{
background-color: #edf9f7;
border-left: solid 4px #1db598;
}
.result-error{
background-color: #fbf1f0;
border-left: solid 4px #fa3035;
color: #fa3035;
}
.raw{
height: 253px;
display: flex;
align-items: center;
justify-content: center;
}
.result-table > table {
border-left: solid 4px #1db598;
}
table{
border: solid 0.5px #e0e0e0;
width: 100%;
margin-bottom: 30px;
/*color:#3c5f8d;*/
}
caption{
text-align:left;
font-size: 16px;
font-weight: bold;
line-height: 1.21;
letter-spacing: 2px;
text-align: left;
}
th{
font-size: 14px;
font-weight: bold;
line-height: 1.21;
letter-spacing: 2px;
color: #102445;
text-transform: uppercase;
text-align: left;
padding: 21px 30px;
background-color: white;
}
tr{
border: solid 0.5px #e0e0e0;
background-color: white;
}
tr:nth-child(even) {
background-color: #f2f7f9;
}
td{
padding: 21px 30px;
}
.expand {
text-align: center;
}
.query h2 {
display: inline-block;
}
.query-tooltip {
position: relative;
display: inline;
color: #000;
margin-left: 5px;
}
.query-tooltip:hover {
color: #000;
}
.query-tooltip-content {
background-color: rgb(250, 250, 250);
border: solid 1.5px #e4eff4;
color: #102445;
border-radius: 2px;
padding: 15px;
margin-bottom: 15px;
position: absolute;
left: 80px;
top: -30px;
z-index: 1;
}
.query-tooltip-container {
position: relative;
visibility: hidden;
}
.query-tooltip:hover+.query-tooltip-container{
visibility: visible;
}
.code{
font-family: monospace;
}

View file

@ -1,123 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/style.css">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Pilosa WebUI</title>
<link rel="icon" type="image/png" href="https://www.pilosa.com/img/favicon.png">
</head>
<body>
<div class="header">
<img src="https://www.pilosa.com/img/logo.svg" width="110" alt="">
<div id="server-version" class=""></div>
</div>
<div class="container">
<div class="nav">
<div class="nav-item nav-active" id="nav-console" onclick="handle_nav_click(this)">
<img style='margin-bottom:10px;' height='40px' src="/assets/nav-console.svg" alt="" class="nav-image">
<img style='margin-bottom:10px;' height='40px' src="/assets/nav-console-active.svg" alt="" class="nav-image-active">
Console
</div>
<div class="nav-item" id="nav-cluster" onclick="handle_nav_click(this)">
<img style='margin-bottom:10px;' height='20px' src="/assets/nav-cluster.svg" alt="" class="nav-image">
<img style='margin-bottom:10px;' height='20px' src="/assets/nav-cluster-active.svg" alt="" class="nav-image-active">
Cluster Admin
</div>
<div class="nav-item" id="nav-documentation" onclick="window.open('https://www.pilosa.com/docs/')">
<img style='margin-bottom:10px;' height='30px' src="/assets/nav-documentation.svg" alt="" class="nav-image">
<img style='margin-bottom:10px;' height='30px' src="/assets/nav-documentation-active.svg" alt="" class="nav-image-active">
Documentation
</div>
</div>
<div class="interface interface-active" id="interface-console">
<div class="query">
<h2>Query</h2>
<a class="query-tooltip" href="https://www.pilosa.com/docs/webui/" target="_blank"><sup>?</sup></a>
<div class="query-tooltip-container">
<div class='query-tooltip-content'>
<h5>PQL</h5>
<div class="code">
SetBit(frame=foo, row=0, col=0)<br />
ClearBit(frame=foo, row=0, col=0)<br />
SetRowAttrs(frame=foo, row=0, color="blue")<br />
SetColumnAttrs(frame=foo, col=0, shape="circle")<br />
SetFieldValue(frame=foo, col=0, age=30)<br />
Bitmap(frame=foo, row=0)<br />
Range(frame=foo, row=0, start="2010-01", end="2017-03")<br />
Count(&lt;BITMAP_CALL&gt;)<br />
TopN([BITMAP_CALL], frame=foo, n=20)<br />
Union([BITMAP_CALL, ...])<br />
Intersect(&lt;BITMAP_CALL&gt;, [BITMAP_CALL, ...])<br />
Difference(&lt;BITMAP_CALL&gt;, &lt;BITMAP_CALL&gt;)<br />
Xor([BITMAP_CALL, ...])<br />
Min([BITMAP_CALL], frame=foo, field=age)<br />
Max([BITMAP_CALL], frame=foo, field=age)<br />
Sum([BITMAP_CALL], frame=foo, field=age)
</div>
<br />
<h5>Special commands</h5>
<div class="code">
:create index test<br />
:use test<br />
:create frame foo<br />
:delete index test<br />
:delete frame foo
</div>
<br />
&lt;tab&gt;: autocomplete<br />
&lt;up&gt;/&lt;down&gt;: history<br />
</div>
</div>
<textarea name="name" id='query' rows="5" required></textarea>
<div class='input-controls'>
<div class="input-hints">
<select class="" name="" id="index-dropdown">
<option value="0">Select index</option>
</select>
&nbsp;&nbsp;&nbsp;
<button id='query-btn'>Run Query</button>
</div>
</div>
</div>
<div class="output-container">
<h2>Output</h2>
<div id="outputs">
<!-- results -->
</div>
</div>
</div>
<div class="interface" id="interface-cluster">
<div class="status-container">
<h2>Nodes</h2>
<div class="status-table-container" id="status-nodes">
</div>
</div>
<div class="status-container">
<h2>Indexes</h2>
<div class="status-table-container" id="status-indexes">
</div>
</div>
<div class="status-container">
<!--
<h2>Slice Distribution</h2>
<div class="status-table-container" id="status-slices">
</div>
-->
</div>
</div>
<div class="interface" id="interface-documentation">
docs!
</div>
</div>
<script type="text/javascript" src="/assets/main.js"></script>
</body>
</html>