Remove WebUI (now contained in a separate package)
It now lives at https://github.com/pilosa/webui
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
16
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
23
handler.go
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -1 +0,0 @@
|
|||
/statik.go
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -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>
|
||||
|
||||
<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>
|
||||
|
||||
<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;
|
||||
}
|
||||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -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;
|
||||
}
|
||||
|
||||
123
webui/index.html
|
|
@ -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(<BITMAP_CALL>)<br />
|
||||
TopN([BITMAP_CALL], frame=foo, n=20)<br />
|
||||
Union([BITMAP_CALL, ...])<br />
|
||||
Intersect(<BITMAP_CALL>, [BITMAP_CALL, ...])<br />
|
||||
Difference(<BITMAP_CALL>, <BITMAP_CALL>)<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 />
|
||||
<tab>: autocomplete<br />
|
||||
<up>/<down>: 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>
|
||||
|
||||
<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>
|
||||