incremental work on NBUI

This commit is contained in:
Jonathan Shook 2020-09-23 13:43:55 -05:00
parent 50c1cd2a1d
commit 69dbd0a6ed
11 changed files with 194 additions and 338 deletions

View File

@ -5,11 +5,6 @@
<v-btn to="/ui/run/" title="Run a workload">Run</v-btn> <v-btn to="/ui/run/" title="Run a workload">Run</v-btn>
<v-btn to="/ui/watch/" title="Watch workload status">Watch</v-btn> <v-btn to="/ui/watch/" title="Watch workload status">Watch</v-btn>
<v-btn to="/docs/" title="Documentation">Docs</v-btn> <v-btn to="/docs/" title="Documentation">Docs</v-btn>
<v-btn
title="Give us your feedback!"
href="https://github.com/nosqlbench/nosqlbench/wiki/Submitting-Feedback">
<v-icon>mdi-lightbulb-on-outline</v-icon>
</v-btn>
</div> </div>
</template> </template>

View File

@ -1,11 +1,24 @@
<template> <template>
<v-app-bar app fluid> <v-app-bar app fluid>
<!-- <v-app-bar app dark fluid dense flat>--> <!-- <v-app-bar app dark fluid dense flat>-->
<v-toolbar-title><slot></slot></v-toolbar-title> <v-toolbar-title>
<slot></slot>
</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-toolbar-items> <v-toolbar-items>
<app-selector></app-selector> <app-selector></app-selector>
<workspace-selector></workspace-selector> <workspace-selector></workspace-selector>
<v-row>
<v-col>
<v-btn icon large
title="Give us your feedback!"
href="https://github.com/nosqlbench/nosqlbench/wiki/Submitting-Feedback">
<v-icon>mdi-lightbulb-on-outline</v-icon>
</v-btn>
</v-col>
</v-row>
</v-toolbar-items> </v-toolbar-items>
</v-app-bar> </v-app-bar>

View File

@ -2,6 +2,8 @@
<div class="markdown-body" v-html="rendered"></div> <div class="markdown-body" v-html="rendered"></div>
</template> </template>
<!--previously: https://github.com/ravenq/markdown-it-vue/blob/master/src/markdown-it-vue.vue-->
<script> <script>
import "markdown-it" import "markdown-it"
import "markdown-it-imsize" import "markdown-it-imsize"

View File

@ -9,8 +9,8 @@
v-model="new_workspace" v-model="new_workspace"
ref="new_workspace_input" ref="new_workspace_input"
hint="workspace name" hint="workspace name"
@blur="commitWorkspace(new_workspace)" @blur="initializeWorkspace(new_workspace)"
@keydown.enter="commitWorkspace(new_workspace)" @keydown.enter="initializeWorkspace(new_workspace)"
@keydown.esc="cancelWorkspace()" @keydown.esc="cancelWorkspace()"
></v-text-field> ></v-text-field>
<!-- label="workspace"--> <!-- label="workspace"-->
@ -77,7 +77,7 @@ export default {
this.$refs.new_workspace_input.focus(); this.$refs.new_workspace_input.focus();
}); });
}, },
commitWorkspace: function ({$store}) { initializeWorkspace: function ({$store}) {
// console.log("commit:" + JSON.stringify(this.new_workspace)); // console.log("commit:" + JSON.stringify(this.new_workspace));
this.$store.dispatch("workspaces/activateWorkspace", this.new_workspace); this.$store.dispatch("workspaces/activateWorkspace", this.new_workspace);
this.new_workspace = ""; this.new_workspace = "";

View File

@ -114,36 +114,27 @@ export default {
} }
} }
</script> </script>
<style> <style scoped>
.container { /*.title {*/
min-height: 60vh; /* font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,*/
display: flex; /* 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;*/
justify-content: flex-start; /* display: block;*/
align-items: flex-start; /* font-weight: 300;*/
text-align: start; /* font-size: 100px;*/
margin: 0 auto 0 15px; /* color: #35495e;*/
} /* letter-spacing: 1px;*/
/*}*/
.title { /*.subtitle {*/
font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, /* font-weight: 300;*/
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; /* font-size: 42px;*/
display: block; /* color: #526488;*/
font-weight: 300; /* word-spacing: 5px;*/
font-size: 100px; /* padding-bottom: 15px;*/
color: #35495e; /*}*/
letter-spacing: 1px;
}
.subtitle { /*.links {*/
font-weight: 300; /* padding-top: 15px;*/
font-size: 42px; /*}*/
color: #526488;
word-spacing: 5px;
padding-bottom: 15px;
}
.links {
padding-top: 15px;
}
</style> </style>

View File

@ -1,52 +1,44 @@
<template> <template>
<v-app> <v-app>
<main-app-bar>NoSQLBench - Workload Builder</main-app-bar> <main-app-bar>NoSQLBench - Workload Builder</main-app-bar>
<v-layout>
<v-row>
<v-main> <v-main>
<v-container fluid> <v-container fluid>
<v-layout row> <v-row>
<v-flex> <v-alert
<v-card> v-if="!enabled"
<v-card-title> >This component is not online. This is only a preview. To use this, you must be running a local instance of NoSQLBench in appserver mode.</v-alert>
Workload details </v-row>
</v-card-title>
<v-col
cols="12"
sm="6"
md="10"
lg="10"
>
<v-text-field
outlined
label="Workload name"
v-model="workloadName"
></v-text-field>
<v-textarea <v-row class="d-flex justify-center">
outlined <v-btn
label="Create Table Statement" :disabled="buildmode==='cql'"
v-model="createTableDef" title="Build CQL workload from schema"
v-on:blur="parseStatement()" @click="buildmode=(buildmode==='cql' ? null : 'cql')"
></v-textarea> >CQL
</v-btn>
</v-col> <v-btn
:disabled="buildmode==='openapi'"
title="Build OpenAPI workload from OpenAPI spec"
@click="buildmode='openapi'"
>OpenAPI
</v-btn>
</v-row>
<v-col cols="12"> <v-row row v-if="buildmode==='openapi'">
<v-btn :title="save_title" v-if="parseSuccess" v-on:click="saveWorkloadToWorkspace()">{{ save_button }}</v-btn> <OpenApiBuilder></OpenApiBuilder>
<v-btn :title="dl_title" v-if="parseSuccess" v-on:click="downloadWorkload()">{{ dl_button }}</v-btn> </v-row>
</v-col>
</v-card>
</v-flex>
</v-layout>
<v-row row v-if="buildmode==='cql'">
<CqlBuilder></CqlBuilder>
</v-row>
</v-container> </v-container>
</v-main> </v-main>
</v-layout> </v-row>
<v-footer app> <v-footer app>
<span>&copy; 2020</span> <span>&copy; 2020</span>
@ -55,222 +47,35 @@
</v-app> </v-app>
</template> </template>
<script> <script>
import antlr4 from "antlr4";
import {saveAs} from "file-saver";
import yamlDumper from "js-yaml";
import CQL3Parser from '@/antlr/CQL3Parser.js';
import CQL3Lexer from '@/antlr/CQL3Lexer.js';
import defaultYaml from 'assets/default.yaml';
import basictypes from 'assets/basictypes.yaml';
import WorkspaceSelector from "@/components/WorkspaceSelector"; import WorkspaceSelector from "@/components/WorkspaceSelector";
import AppSelector from "@/components/AppSelector"; import AppSelector from "@/components/AppSelector";
import MainAppBar from "@/components/MainAppBar"; import MainAppBar from "@/components/MainAppBar";
import CqlBuilder from "~/components/builders/CqlBuilder";
import OpenApiBuilder from "~/components/builders/OpenApiBuilder";
export default { export default {
components: { components: {
MainAppBar, MainAppBar,
AppSelector, AppSelector,
WorkspaceSelector WorkspaceSelector,
CqlBuilder,
OpenApiBuilder
}, },
data(context) { data(context) {
let data = { let data = {
enabled: false, buildmode: 'cql',
createTableDef: "",
workloadName: "",
parseSuccess: false,
blob: null,
}; };
return data; return data;
}, },
computed: { async asyncData({store}) {
save_button: function () { await store.dispatch("service_status/loadEndpoints")
return "Save to workspace '" + this.$store.getters["workspaces/getWorkspace"] + "'"; return {
}, enabled: store.getters["service_status/getEnabled"]
dl_button: function () {
return "Download as " + this.filename;
},
dl_title: function () {
return "Click to download the workload as '" + this.filename + "'";
},
filename: function () {
return this.workloadName + ".yaml";
},
save_title: function () {
return "Click to save this workload in the '" + this.workspace + "' workspace, or change the workspace in the app bar first.\n"
},
workspace: function () {
return this.$store.getters["workspaces/getWorkspace"]
},
enabled: function () {
return this.$store.getters["service_status/getEndpoints"]
} }
}, },
methods: { computed: {
async parseStatement() { workspace: function () {
console.log(this.$data.createTableDef); return this.$store.getters["workspaces/getWorkspace"]
const input = this.$data.createTableDef;
const chars = new antlr4.InputStream(input);
const lexer = new CQL3Lexer.CQL3Lexer(chars);
lexer.strictMode = false; // do not use js strictMode
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new CQL3Parser.CQL3Parser(tokens);
const context = parser.create_table_stmt();
try {
const keyspaceName = context.table_name().keyspace_name().getChild(0).getText()
const tableName = context.table_name().table_name_noks().getChild(0).getText()
const columnDefinitions = context.column_definitions().column_definition();
let columns = [];
let partitionKeys = [];
let clusteringKeys = [];
columnDefinitions.forEach(columnDef => {
if (columnDef.column_name() != null) {
columns.push({
"name": columnDef.column_name().getText(),
"type": columnDef.column_type().getText()
})
} else {
const primaryKeyContext = columnDef.primary_key()
if (primaryKeyContext.partition_key() != null) {
const partitionKeysContext = primaryKeyContext.partition_key().column_name();
partitionKeysContext.map((partitionKey, i) => {
const partitionKeyName = partitionKey.getText()
const col = {
"name": partitionKeyName,
"type": columns.filter(x => x.name == partitionKeyName)[0].type
}
partitionKeys.push(col)
})
}
if (primaryKeyContext.clustering_column().length != 0) {
const clusteringKeysContext = primaryKeyContext.clustering_column();
clusteringKeysContext.map((clusteringKey, i) => {
const clusteringKeyName = clusteringKey.getText()
const col = {
"name": clusteringKeyName,
"type": columns.filter(x => x.name == clusteringKeyName)[0].type
}
clusteringKeys.push(col)
})
}
}
})
columns = columns.filter(col => {
return partitionKeys.filter(pk => pk.name == col.name).length == 0 && clusteringKeys.filter(cc => cc.name == col.name).length == 0
})
const allColumns = [].concat(columns, partitionKeys, clusteringKeys)
this.$data.tableName = tableName;
this.$data.keyspaceName = keyspaceName;
this.$data.columns = columns;
this.$data.clusteringKeys = clusteringKeys;
this.$data.partitionKeys = partitionKeys;
this.$data.allColumns = allColumns;
console.log(this.$data)
console.log(defaultYaml)
// schema and bindings
let createTableStatement = "CREATE TABLE IF NOT EXISTS <<keyspace:" + keyspaceName + ">>." + tableName + " (\n";
console.log(basictypes)
defaultYaml.bindings = {}
allColumns.forEach(column => {
let recipe = basictypes.bindings[column.type + "val"];
if (recipe == undefined) {
const chars = new antlr4.InputStream(column.type);
const lexer = new CQL3Lexer.CQL3Lexer(chars);
lexer.strictMode = false; // do not use js strictMode
const tokens = new antlr4.CommonTokenStream(lexer);
const parser = new CQL3Parser.CQL3Parser(tokens);
const typeContext = parser.column_type();
const collectionTypeContext = typeContext.data_type().collection_type();
const collectionType = collectionTypeContext.children[0].getText();
if (collectionType.toLowerCase() == "set") {
const type = collectionTypeContext.children[2].getText();
recipe = "Set(HashRange(1,<<set-count-" + column.name + ":5>>)," + basictypes.bindings[type + "val"] + ") -> java.util.Set"
} else if (collectionType.toLowerCase() == "list") {
const type = collectionTypeContext.children[2].getText();
recipe = "List(HashRange(1,<<list-count-" + column.name + ":5>>)," + basictypes.bindings[type + "val"] + ") -> java.util.List"
} else if (collectionType.toLowerCase() == "map") {
const type1 = collectionTypeContext.children[2].getText();
const type2 = collectionTypeContext.children[4].getText();
recipe = "Map(HashRange(1,<<map-count-" + column.name + ":5>>)," + basictypes.bindings[type1 + "val"] + "," + basictypes.bindings[type2 + "val"] + ") -> java.util.Map"
} else {
alert("Could not generate recipe for type: " + column.type + " for column: " + column.name)
}
}
defaultYaml.bindings[column.name] = recipe
createTableStatement = createTableStatement + column.name + " " + column.type + ",\n";
})
let pk = "PRIMARY KEY (("
pk = pk + partitionKeys.map(x => x.name).reduce((x, acc) => acc = acc + "," + x)
pk = pk + ")"
if (clusteringKeys.length > 0) {
pk = pk + "," + clusteringKeys.map(x => x.name).reduce((x, acc) => acc = acc + "," + x)
}
pk = pk + ")"
createTableStatement = createTableStatement + pk + "\n);"
defaultYaml.blocks[0].statements[0] = {"create-table": createTableStatement}
//rampup
let insertStatement = "INSERT INTO <<keyspace:" + keyspaceName + ">>." + tableName + " (\n";
insertStatement = insertStatement + allColumns.map(x => x.name).reduce((x, acc) => acc = acc + ",\n" + x) + "\n) VALUES (\n";
insertStatement = insertStatement + allColumns.map(x => "{" + x.name + "}").reduce((x, acc) => acc = acc + ",\n" + x) + "\n);"
defaultYaml.blocks[1].statements[0] = {"insert-rampup": insertStatement}
//main-write
defaultYaml.blocks[2].statements[0] = {"insert-main": insertStatement}
//main-read-partition
let readPartitionStatement = "SELECT * from <<keyspace:" + keyspaceName + ">>." + tableName + " WHERE ";
readPartitionStatement = readPartitionStatement + partitionKeys.map(x => x.name + "={" + x.name + "}").reduce((x, acc) => acc = acc + " AND " + x);
let readRowStatement = readPartitionStatement + ";";
if (clusteringKeys.length > 0) {
readPartitionStatement = readPartitionStatement + " AND " + clusteringKeys.map(x => x.name + "={" + x.name + "}").reduce((x, acc) => acc = acc + " AND " + x);
}
readPartitionStatement = readPartitionStatement + ";";
defaultYaml.blocks[3].statements[0] = {"read-partition": readPartitionStatement}
//main-read-row
defaultYaml.blocks[4].statements[0] = {"read-row": readRowStatement}
defaultYaml.description = this.$data.workloadName
const yamlOutputText = yamlDumper.dump(defaultYaml)
this.blob = new Blob([yamlOutputText], {type: "text/plain;charset=utf-8"});
this.parseSuccess = true;
} catch (e) {
console.log("blur, invalid create table def")
console.log(e)
}
},
downloadWorkload() {
saveAs(this.blob, this.$data.filename);
},
saveWorkloadToWorkspace() {
this.$store.dispatch("workspaces/putFile",{
workspace: this.workspace,
filename: this.filename,
content: this.blob
})
} }
}, },
created() { created() {
@ -279,33 +84,4 @@ export default {
} }
</script> </script>
<style> <style>
/*.container {*/
/* margin: 0 auto;*/
/* display: flex;*/
/* justify-content: center;*/
/* align-items: center;*/
/* text-align: center;*/
/*}*/
/*.title {*/
/* font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont,*/
/* 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;*/
/* display: block;*/
/* font-weight: 300;*/
/* font-size: 100px;*/
/* color: #35495e;*/
/* letter-spacing: 1px;*/
/*}*/
/*.subtitle {*/
/* font-weight: 300;*/
/* font-size: 42px;*/
/* color: #526488;*/
/* word-spacing: 5px;*/
/* padding-bottom: 15px;*/
/*}*/
/*.links {*/
/* padding-top: 15px;*/
/*}*/
</style> </style>

View File

@ -28,12 +28,10 @@
<v-btn-toggle v-model="toggle_builtins" @change="validateAndSearch()"> <v-btn-toggle v-model="toggle_builtins" @change="validateAndSearch()">
<v-btn :disabled="this.toggle_workspaces===undefined"> <v-btn :disabled="this.toggle_workspaces===undefined">
<v-container fluid class="d-flex">
<v-icon title="include built-in workloads">mdi-folder-open</v-icon> <v-icon title="include built-in workloads">mdi-folder-open</v-icon>
<div class="ma-2">bundled</div> <div class="ma-2">bundled</div>
<v-icon v-if="this.toggle_builtins===0">mdi-check</v-icon> <v-icon v-if="this.toggle_builtins===0">mdi-check</v-icon>
</v-container>
</v-btn> </v-btn>
</v-btn-toggle> </v-btn-toggle>

View File

@ -3,31 +3,42 @@ import {mapGetters} from "vuex";
import endpoints from "@/js/endpoints"; import endpoints from "@/js/endpoints";
export const state = () => ({ export const state = () => ({
endpoints: {}, endpoints: null,
enabled: false enabled: null
}); });
export const getters = { export const getters = {
getEndpoints: (state, getters) => { getEndpoints: (state, getters) => {
return state.endpoints; return state.endpoints;
},
getEnabled: (state, getters) => {
return state.enabled;
} }
} }
export const mutations = { export const mutations = {
setEndpoints(state, endpoints) { setEndpoints(state, endpoints) {
state.endpoints = endpoints; state.endpoints = endpoints;
},
setEnabled(state, enabled) {
state.enabled = enabled;
} }
} }
export const actions = { export const actions = {
async loadEndpoints(context, reason) { async loadEndpoints(context, reason) {
console.log("loading endpoint status because '" + reason + "'") let enabled = context.getters["getEnabled"]
await this.$axios.get(endpoints.url(document, context, "/services/status")) if (enabled === null || enabled === undefined) {
.then(res => { console.log("loading endpoint status because '" + reason + "'")
context.commit('setEndpoints', res) await this.$axios.get(endpoints.url(document, context, "/services/status"))
}) .then(res => {
.catch((e) => { context.commit('setEndpoints', res.data.endpoints)
console.error("axios/nuxt status async error:" + e); context.commit('setEnabled', res.data.enabled)
}) })
.catch((e) => {
console.error("axios/nuxt status async error:" + e);
})
}
// else use cache defined status
} }
}; };

View File

@ -5,7 +5,8 @@ import endpoints from "@/js/endpoints";
export const state = () => ({ export const state = () => ({
workspace: 'default', workspace: 'default',
workspaces: [], workspaces: [],
fileview: [] all_ws_files: [],
matching_ws_files: []
}); });
export const getters = { export const getters = {
@ -15,8 +16,11 @@ export const getters = {
getWorkspaces: (state, getters) => { getWorkspaces: (state, getters) => {
return state.workspaces; return state.workspaces;
}, },
getFileview: (state, getters) => { getAllFiles: (state, getters) => {
return state.fileview; return state.all_ws_files;
},
getMatchingFiles: (state, getters) => {
return state.matching_ws_files;
} }
// ...mapGetters(['workspace','workspaces']) // ...mapGetters(['workspace','workspaces'])
@ -29,21 +33,73 @@ export const mutations = {
setWorkspaces(state, workspaces) { setWorkspaces(state, workspaces) {
state.workspaces = workspaces; state.workspaces = workspaces;
}, },
setFileview(state, fileview) { setAllFiles(state, files) {
state.fileview = fileview; state.all_ws_files = files;
},
setMatchingFiles(state, files) {
state.matching_ws_files = files;
} }
}; };
export const actions = { export const actions = {
async importUrlToWorkspace(context, params) {
console.log("importUrlToWorkspace(ctx," + JSON.stringify(params, null, 2));
let workspace = params.workspace;
let import_url = params.import_url;
let import_as = params.import_as;
if (!workspace || !import_url || !import_as) {
throw("Unable to save file to workspace without params workspace, import_url, import_as");
}
this.$axios.$get(import_url)
.then(res => {
console.log('save url data:' + JSON.stringify(res, null, 2))
return res
}).then(data => {
context.dispatch("putFile", {
workspace: workspace,
filename: import_as,
content: data
}).catch((e) => {
throw "error while saving data:" + e
})
})
.catch((e) => {
throw "axios/nuxt workspaces async error:" + e
})
},
async setWorkspace(context, val) { async setWorkspace(context, val) {
// console.log("committing setWorkspace:" + JSON.stringify(val)); // console.log("committing setWorkspace:" + JSON.stringify(val));
context.commit('setWorkspace', val); context.commit('setWorkspace', val);
await context.dispatch("listWorkspaceFiles", {wsname: val})
}, },
async setWorkspaces(context, val) { async setWorkspaces(context, val) {
// console.log("committing setWorkspaces:" + JSON.stringify(val)); // console.log("committing setWorkspaces:" + JSON.stringify(val));
context.commit('setWorkspaces', val); context.commit('setWorkspaces', val);
}, },
async initWorkspaces(context, reason) { async listWorkspaceFiles(context, params) {
let wsname = params.wsname;
let contains = params.contains;
console.log("list params:" + JSON.stringify(params, null, 2))
let query = "?ls";
if (contains) {
query = query + "&contains=" + contains
}
await this.$axios.$get(endpoints.url(document, context, "/services/workspaces/" + wsname) + query)
.then(res => {
console.log("ls ws:" + JSON.stringify(res, null, 2))
if (contains) {
context.commit("setContains", res["ls"]);
} else {
context.commit("setFileview", res["ls"])
}
})
.catch((e) => {
throw "axios/nuxt workspaces async error:" + e
})
},
async loadWorkspaces(context, reason) {
// console.log("initializing workspaces because '" + reason + "'") // console.log("initializing workspaces because '" + reason + "'")
this.$axios.$get(endpoints.url(document, context, "/services/workspaces/")) this.$axios.$get(endpoints.url(document, context, "/services/workspaces/"))
.then(res => { .then(res => {
@ -52,7 +108,7 @@ export const actions = {
context.commit('setWorkspaces', res) context.commit('setWorkspaces', res)
}) })
.catch((e) => { .catch((e) => {
console.error("axios/nuxt workspaces async error:", e); throw "axios/nuxt workspaces async error:" + e
}) })
}, },
async putFile(context, params) { async putFile(context, params) {
@ -60,25 +116,27 @@ export const actions = {
let to_filename = params.filename; let to_filename = params.filename;
let to_content = params.content; let to_content = params.content;
if (!to_workspace || !to_filename || !to_content) { if (!to_workspace || !to_filename || !to_content) {
console.log("params:" + JSON.stringify(params, null, 2))
throw("Unable to save file to workspace without params having workspace, filename, content"); throw("Unable to save file to workspace without params having workspace, filename, content");
} }
const result = await this.$axios.$post(endpoints.url(document, context, "/services/workspaces/" + to_workspace + "/" + to_filename, to_content)) console.log("to_content:" + JSON.stringify(to_content, null, 2))
const result = await this.$axios.put(endpoints.url(document, context, "/services/workspaces/" + to_workspace + "/" + to_filename), to_content)
.then(res => { .then(res => {
console.log("axios/vuex workspace put:" + JSON.stringify(res)); console.log("axios/vuex workspace put:" + JSON.stringify(res));
return res; return res;
}) })
.catch((e) => { .catch((e) => {
console.error("axios/vuex workspace put:", e) throw "axios/vuex workspace put:" + e
}); });
}, },
async activateWorkspace(context, workspace) { async initializeWorkspace(context, workspace) {
const fresh_workspace = await this.$axios.$get(endpoints.url(document, context, "/services/workspaces/" + workspace)) const fresh_workspace = await this.$axios.$get(endpoints.url(document, context, "/services/workspaces/" + workspace) + "?ls")
.then(res => { .then(res => {
// console.log("axios/vuex workspace async get:" + JSON.stringify(res)) // console.log("axios/vuex workspace async get:" + JSON.stringify(res))
return res; return res;
}) })
.catch((e) => { .catch((e) => {
console.error("axios/nuxt getWorkspace async error:", e) throw "axios/nuxt getWorkspace async error:" + e
}) })
await context.dispatch('initWorkspaces', "workspace '" + workspace + "' added"); await context.dispatch('initWorkspaces', "workspace '" + workspace + "' added");
// await dispatch.initWorkspaces({commit, state, dispatch}, "workspace '" + workspace + "' added") // await dispatch.initWorkspaces({commit, state, dispatch}, "workspace '" + workspace + "' added")
@ -94,7 +152,7 @@ export const actions = {
return res; return res;
}) })
.catch((e) => { .catch((e) => {
console.error("axios/nuxt purgeWorkspace error:", e) throw "axios/nuxt purgeWorkspace error:" + e
}) })
const found = this.state.workspaces.workspaces.find(w => w.name === workspace); const found = this.state.workspaces.workspaces.find(w => w.name === workspace);
if (!found) { if (!found) {

View File

@ -16,6 +16,7 @@ import javax.ws.rs.core.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
@Path("/services/workspaces") @Path("/services/workspaces")
@Singleton @Singleton
@ -79,7 +80,7 @@ public class WorkspacesEndpoint implements WebServiceObject {
return Response.ok().build(); return Response.ok().build();
} }
@POST @PUT
@Path("/{workspaceName}/{filepath:.+}") @Path("/{workspaceName}/{filepath:.+}")
@Consumes(MediaType.WILDCARD) @Consumes(MediaType.WILDCARD)
@Produces(MediaType.WILDCARD) @Produces(MediaType.WILDCARD)
@ -103,17 +104,20 @@ public class WorkspacesEndpoint implements WebServiceObject {
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
public Response getWorkspaceInfo( public Response getWorkspaceInfo(
@PathParam("workspace") String workspace, @PathParam("workspace") String workspace,
@QueryParam("ls") String ls @QueryParam("ls") String ls,
@QueryParam("contains") String contains
) { ) {
try { try {
if (ls!=null && !ls.toLowerCase().equals("false")) { WorkSpace ws = getSvc().getWorkspace(workspace);
WorkSpace ws = getSvc().getWorkspace(workspace); WorkspaceView wsview = ws.getWorkspaceView();
if (ls != null && !ls.toLowerCase().equals("false")) {
List<WorkspaceItemView> listing = ws.getWorkspaceListingView(""); List<WorkspaceItemView> listing = ws.getWorkspaceListingView("");
return Response.ok(listing).build(); if (contains != null) {
} else { listing = listing.stream().filter(i -> i.contains(contains)).collect(Collectors.toList());
WorkspaceView workpaceView = getSvc().getWorkspaceView(workspace); }
return Response.ok(workpaceView).build(); wsview.setListing(listing);
} }
return Response.ok(wsview).build();
} catch (Exception e) { } catch (Exception e) {
return Response.serverError().entity(e.getMessage()).build(); return Response.serverError().entity(e.getMessage()).build();
} }
@ -128,7 +132,7 @@ public class WorkspacesEndpoint implements WebServiceObject {
@QueryParam("ls") String ls) { @QueryParam("ls") String ls) {
try { try {
if (ls!=null && !ls.toLowerCase().equals("false")) { if (ls != null && !ls.toLowerCase().equals("false")) {
WorkSpace ws = getSvc().getWorkspace(workspace); WorkSpace ws = getSvc().getWorkspace(workspace);
List<WorkspaceItemView> listing = ws.getWorkspaceListingView(filename); List<WorkspaceItemView> listing = ws.getWorkspaceListingView(filename);
return Response.ok(listing).build(); return Response.ok(listing).build();

View File

@ -11,6 +11,7 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
public class WorkspaceView { public class WorkspaceView {
@ -25,6 +26,9 @@ public class WorkspaceView {
private final Path workspaceRoot; private final Path workspaceRoot;
private Summary summary; private Summary summary;
@JsonProperty("ls")
private List<WorkspaceItemView> listing = null;
public WorkspaceView(Path workspaceRoot) { public WorkspaceView(Path workspaceRoot) {
this.workspaceRoot = workspaceRoot; this.workspaceRoot = workspaceRoot;
} }
@ -56,13 +60,17 @@ public class WorkspaceView {
return this.summary; return this.summary;
} }
public void setListing(List<WorkspaceItemView> listing) {
this.listing = listing;
}
public final static class Summary extends SimpleFileVisitor<Path> { public final static class Summary extends SimpleFileVisitor<Path> {
private final Path root; private final Path root;
public long total_bytes = 0L; public long total_bytes = 0L;
public long total_files = 0L; public long total_files = 0L;
public long last_changed_epoch =Long.MIN_VALUE; public long last_changed_epoch = Long.MIN_VALUE;
public String last_changed_filename = ""; public String last_changed_filename = "";
public String getLast_changed_ago() { public String getLast_changed_ago() {