From 09ed84daf3be0b012ee2dd01df6ff1912a544048 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Tue, 19 Jun 2018 20:16:57 +0200 Subject: [PATCH 01/29] web ui: prefer `array` metadata over children count method --- src/tools/web/client/src/components/TreeItem/index.js | 9 +++++++++ src/tools/web/client/src/containers/ConnectedTreeItem.js | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 79be390fdec..13aa823351e 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -158,6 +158,15 @@ export default class TreeItem extends Component { } getArrayKeyLength (item) { + if (!item || !item.path) return false + const { kdbState, instanceId } = this.props + const data = kdbState[instanceId] + if (data && data[item.path] && data[item.path].meta) { + const meta = data[item.path].meta + if (meta && meta['array'] && meta['array'] > 0) { + return Number(meta['array']) + } + } return (item && Array.isArray(item.children)) ? item.children.reduce((res, i) => { if (res === false) return false diff --git a/src/tools/web/client/src/containers/ConnectedTreeItem.js b/src/tools/web/client/src/containers/ConnectedTreeItem.js index 0317d06daa2..fd4c1536e85 100644 --- a/src/tools/web/client/src/containers/ConnectedTreeItem.js +++ b/src/tools/web/client/src/containers/ConnectedTreeItem.js @@ -19,7 +19,7 @@ import { import { actions as undoActions } from 'redux-undo-redo-middleware' const mapStateToProps = (state) => { - return { batchUndo: state.batchUndo } + return { batchUndo: state.batchUndo, kdbState: state.kdb } } const mapDispatchToProps = (dispatch) => From 964afa627b9b9cbe9751e5ef1897e7fe39a7f7d9 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Wed, 20 Jun 2018 11:39:24 +0200 Subject: [PATCH 02/29] web ui: refresh key name when add dialog is opened --- .../web/client/src/components/TreeItem/dialogs/AddDialog.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx b/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx index c6bd94d1af3..f69e078c9ca 100644 --- a/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx +++ b/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx @@ -38,7 +38,8 @@ export default class AddDialog extends Component { } componentWillReceiveProps (nextProps) { - if (this.state.name.length === 0 && nextProps.arrayKeyLength) { + const wasOpened = this.props.open === false && nextProps.open === true + if (wasOpened && nextProps.arrayKeyLength) { this.setState({ name: this.generateArrayKey(nextProps.arrayKeyLength) }) } } From e3049db81550dc4af26870af7c3a49b607d6d958 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Wed, 20 Jun 2018 11:58:37 +0200 Subject: [PATCH 03/29] web ui: prefer showing errors over loading message (fixes 404 page) --- .../client/src/components/InstanceError.jsx | 36 ++++++++++++------- .../src/components/pages/Configuration.jsx | 11 ++++++ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/tools/web/client/src/components/InstanceError.jsx b/src/tools/web/client/src/components/InstanceError.jsx index b772084b7d2..d8778dcddfc 100644 --- a/src/tools/web/client/src/components/InstanceError.jsx +++ b/src/tools/web/client/src/components/InstanceError.jsx @@ -11,8 +11,18 @@ import FlatButton from 'material-ui/FlatButton' import NavigationRefresh from 'material-ui/svg-icons/navigation/refresh' const InstanceError = ({ instance, error, refresh }) => { - const { host } = instance const { name, message } = error + const hint = (instance && instance.host) + ? ( + + Please make sure elektrad is running on {instance.host} + + ) : ( + + Are you sure this instance exists? + + ) + const showRefreshButton = (instance && instance.host) return (

Connection to instance failed.

@@ -20,18 +30,20 @@ const InstanceError = ({ instance, error, refresh }) => { {name}: {message}

- Please make sure elektrad is running on {host} -

-

- Once it is running, you can - } - onClick={refresh} - style={{ marginLeft: '0.5em', marginRight: '0.5em' }} - /> - to attempt reconnecting to the instance. + {hint}

+ {showRefreshButton && +

+ Once it is running, you can + } + onClick={refresh} + style={{ marginLeft: '0.5em', marginRight: '0.5em' }} + /> + to attempt reconnecting to the instance. +

+ }
) } diff --git a/src/tools/web/client/src/components/pages/Configuration.jsx b/src/tools/web/client/src/components/pages/Configuration.jsx index 2aa398dccc2..11d5097de99 100644 --- a/src/tools/web/client/src/components/pages/Configuration.jsx +++ b/src/tools/web/client/src/components/pages/Configuration.jsx @@ -181,6 +181,17 @@ export default class Configuration extends Component { const { instance, match, instanceError, search } = this.props const { data } = this.state + if (instanceError) { + return ( + + 404 instance not found} /> + + + + + ) + } + if (!instance) { const title = (

Loading instance... please wait

From c1f0c74a7ca1ffa47ccc046700fe123ac788d5c6 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Wed, 20 Jun 2018 12:02:26 +0200 Subject: [PATCH 04/29] web ui: allow drag & copy with alt or ctrl --- src/tools/web/client/src/components/TreeView.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/TreeView.jsx b/src/tools/web/client/src/components/TreeView.jsx index 6ff65338f9a..2a73e66f471 100644 --- a/src/tools/web/client/src/components/TreeView.jsx +++ b/src/tools/web/client/src/components/TreeView.jsx @@ -79,7 +79,8 @@ export default class TreeView extends React.Component { const { instanceId, moveKey, copyKey } = this.props const { selection } = inputs - const action = (evt && evt.altKey) // alt pressed -> copy + // alt or ctrl pressed -> copy + const action = (evt && (evt.altKey || evt.ctrlKey)) ? copyKey : moveKey From 392d00541f89077c45f850e31b6d39c620ef322d Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Wed, 20 Jun 2018 12:02:53 +0200 Subject: [PATCH 05/29] web ui: disable "reloaded" message as it is too spammy and covers more important messages --- src/tools/web/client/src/components/pages/Configuration.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tools/web/client/src/components/pages/Configuration.jsx b/src/tools/web/client/src/components/pages/Configuration.jsx index 11d5097de99..c668211319b 100644 --- a/src/tools/web/client/src/components/pages/Configuration.jsx +++ b/src/tools/web/client/src/components/pages/Configuration.jsx @@ -51,9 +51,6 @@ const parseDataSet = (getKey, sendNotification, instanceId, tree, path, parent) ? (notify = true) => { return new Promise(resolve => { getKey(instanceId, newPath, true) - if (notify) { - sendNotification('finished (re-)loading \'' + newPath + '\' keyset') - } resolve(children) }) } : false From da9dd00eeb01d1076e8906a181cef535467cbe79 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Wed, 20 Jun 2018 12:09:55 +0200 Subject: [PATCH 06/29] web ui: show notification after drag & drop --- src/tools/web/client/src/components/TreeView.jsx | 14 ++++++++++---- .../web/client/src/containers/ConnectedTreeView.js | 4 ++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/tools/web/client/src/components/TreeView.jsx b/src/tools/web/client/src/components/TreeView.jsx index 2a73e66f471..b938a3bc88a 100644 --- a/src/tools/web/client/src/components/TreeView.jsx +++ b/src/tools/web/client/src/components/TreeView.jsx @@ -76,7 +76,7 @@ export default class TreeView extends React.Component { } handleDrop = (target, evt, inputs) => { - const { instanceId, moveKey, copyKey } = this.props + const { instanceId, moveKey, copyKey, sendNotification } = this.props const { selection } = inputs // alt or ctrl pressed -> copy @@ -84,9 +84,15 @@ export default class TreeView extends React.Component { ? copyKey : moveKey - selection.map( - sel => action(instanceId, sel.path, target.path + '/' + sel.name) - ) + const actionName = (evt && (evt.altKey || evt.ctrlKey)) + ? 'copied' + : 'moved' + + Promise.all( + selection.map( + sel => action(instanceId, sel.path, target.path + '/' + sel.name) + ) + ).then(() => sendNotification(`successfully ${actionName} key(s) to ${target.path}`)) this.setState({ selection: [] }) } diff --git a/src/tools/web/client/src/containers/ConnectedTreeView.js b/src/tools/web/client/src/containers/ConnectedTreeView.js index b29ca3b245e..3ce7c8101a0 100644 --- a/src/tools/web/client/src/containers/ConnectedTreeView.js +++ b/src/tools/web/client/src/containers/ConnectedTreeView.js @@ -12,13 +12,13 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import TreeView from '../components/TreeView.jsx' -import { getKey, moveKey, copyKey, updateInstance } from '../actions' +import { getKey, moveKey, copyKey, updateInstance, sendNotification } from '../actions' const mapStateToProps = (state, { instanceId, treeRef }) => { return { kdb: state.kdb && state.kdb[instanceId], ref: treeRef } } const mapDispatchToProps = (dispatch) => - bindActionCreators({ getKey, moveKey, copyKey, updateInstance }, dispatch) + bindActionCreators({ getKey, moveKey, copyKey, updateInstance, sendNotification }, dispatch) export default connect(mapStateToProps, mapDispatchToProps)(TreeView) From 78ae4f2571abc16150730c913da4823c953481f9 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 18:04:57 +0200 Subject: [PATCH 07/29] web ui: display search errors --- src/tools/web/client/src/actions/utils.js | 9 +++++--- .../web/client/src/components/TreeSearch.jsx | 8 +++++-- .../src/components/pages/Configuration.jsx | 23 +++++++++++-------- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/tools/web/client/src/actions/utils.js b/src/tools/web/client/src/actions/utils.js index 2af28366db3..4d9ae0101c9 100644 --- a/src/tools/web/client/src/actions/utils.js +++ b/src/tools/web/client/src/actions/utils.js @@ -19,9 +19,12 @@ export const thunkCreator = (action) => { return promise .then( - (result) => result && result.error // `error` is only returned in error messages - ? dispatch({ ...rest, type: REJECTED, error: result.error }) // if error, dispatch it - : dispatch({ ...rest, type: RESOLVED, result }) // otherwise, dispatch result + (result) => { + const error = (result && result.error) || (result && result.result && result.result.error) + return error + ? dispatch({ ...rest, type: REJECTED, error }) // `error` is only returned in error messages + : dispatch({ ...rest, type: RESOLVED, result }) // otherwise, dispatch result + } ) .catch( (error) => dispatch({ ...rest, type: REJECTED, error }) diff --git a/src/tools/web/client/src/components/TreeSearch.jsx b/src/tools/web/client/src/components/TreeSearch.jsx index c8c33c1d523..468e0323cec 100644 --- a/src/tools/web/client/src/components/TreeSearch.jsx +++ b/src/tools/web/client/src/components/TreeSearch.jsx @@ -35,8 +35,12 @@ export default class TreeSearch extends React.Component { const { instanceId, findKey, clearSearch, sendNotification } = this.props if (value && value.length > 0) { findKey(instanceId, value) - .then(() => sendNotification('search completed successfully!')) - .catch(() => sendNotification('error while searching!')) + .then(res => { + if (res && res.type === 'FIND_KEY_SUCCESS') { + return sendNotification('search completed successfully!') + } + return sendNotification('error while searching!') + }) } else { clearSearch() setTimeout(() => sendNotification('search cleared!'), 200) diff --git a/src/tools/web/client/src/components/pages/Configuration.jsx b/src/tools/web/client/src/components/pages/Configuration.jsx index c668211319b..ce06b2d62e4 100644 --- a/src/tools/web/client/src/components/pages/Configuration.jsx +++ b/src/tools/web/client/src/components/pages/Configuration.jsx @@ -220,6 +220,7 @@ export default class Configuration extends Component { const isSearching = search && search.done const hasResults = search && search.results && search.results.length > 0 + const searchError = search && search.error const filteredData = (isSearching && hasResults) ? this.generateData({ ...this.props, ls: search.results }) @@ -247,17 +248,21 @@ export default class Configuration extends Component { : (data && Array.isArray(data) && data.length > 0) ? [ , - (isSearching && !hasResults) + searchError ?
- No results found for "{search.query}". + {searchError.name}: {searchError.message}
- : , + : (isSearching && !hasResults) + ?
+ No results found for "{search.query}". +
+ : , ] :
Loading configuration data... From 47c64c8da90516d5b15cb2c3f4fc4c431f894fe8 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 18:05:51 +0200 Subject: [PATCH 08/29] bump version to 1.6 --- src/tools/web/client/package.json | 2 +- src/tools/web/client/src/components/Menu.jsx | 2 +- src/tools/web/elektrad/package.json | 2 +- src/tools/web/webd/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/web/client/package.json b/src/tools/web/client/package.json index 7e9766b1a9e..90ba40be8df 100644 --- a/src/tools/web/client/package.json +++ b/src/tools/web/client/package.json @@ -1,6 +1,6 @@ { "name": "@elektra-web/client", - "version": "1.3.0", + "version": "1.6.0", "description": "a web interface for elektra", "keywords": [ "elektra", diff --git a/src/tools/web/client/src/components/Menu.jsx b/src/tools/web/client/src/components/Menu.jsx index 808627f2b96..e1777b0f03e 100644 --- a/src/tools/web/client/src/components/Menu.jsx +++ b/src/tools/web/client/src/components/Menu.jsx @@ -65,7 +65,7 @@ export default class Menu extends React.Component { } = this.props const titleText = ( - elektra-web v1.5 + elektra-web v1.6 ) const title = ( diff --git a/src/tools/web/elektrad/package.json b/src/tools/web/elektrad/package.json index dd8eea41c2c..a0c054a347e 100644 --- a/src/tools/web/elektrad/package.json +++ b/src/tools/web/elektrad/package.json @@ -1,6 +1,6 @@ { "name": "@elektra-web/elektrad", - "version": "1.3.0", + "version": "1.6.0", "description": "server to remotely control Elektra", "keywords": [ "elektra", diff --git a/src/tools/web/webd/package.json b/src/tools/web/webd/package.json index 25d6ab40a0c..4518a3d3153 100644 --- a/src/tools/web/webd/package.json +++ b/src/tools/web/webd/package.json @@ -1,6 +1,6 @@ { "name": "@elektra-web/webd", - "version": "1.3.0", + "version": "1.6.0", "description": "server to control (multiple) elektrad instances", "keywords": [ "elektra", From cb080c50339d5842c800820588855231178a1105 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 18:11:54 +0200 Subject: [PATCH 09/29] web ui: don't automatically unfold search results (fixes performance issue with search) --- .../src/components/pages/Configuration.jsx | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/tools/web/client/src/components/pages/Configuration.jsx b/src/tools/web/client/src/components/pages/Configuration.jsx index ce06b2d62e4..512443a66b0 100644 --- a/src/tools/web/client/src/components/pages/Configuration.jsx +++ b/src/tools/web/client/src/components/pages/Configuration.jsx @@ -64,21 +64,6 @@ const parseData = (getKey, sendNotification, instanceId, ls, kdb) => { return parseDataSet(getKey, sendNotification, instanceId, tree) } -const getUnfolded = (searchResults) => { - let unfolded = [] - for (let r of searchResults) { - const parts = r.split('/') - let path = '' - for (let p of parts) { - path = path.length === 0 ? p : (path + '/' + p) - if (!unfolded.includes(path)) { - unfolded.push(path) - } - } - } - return unfolded -} - // configuration page export default class Configuration extends Component { constructor (props, ...rest) { @@ -226,10 +211,6 @@ export default class Configuration extends Component { ? this.generateData({ ...this.props, ls: search.results }) : data - const filteredInstance = (isSearching && hasResults) - ? { ...instance, unfolded: getUnfolded(search.results) } - : instance - return ( : Date: Thu, 21 Jun 2018 18:20:08 +0200 Subject: [PATCH 10/29] web ui: add realworld config to elektrad-demo docker image --- scripts/docker/web/elektrad-demo/Dockerfile | 4 + .../docker/web/elektrad-demo/realworld.ini | 392 ++++++++++++++++++ 2 files changed, 396 insertions(+) create mode 100644 scripts/docker/web/elektrad-demo/realworld.ini diff --git a/scripts/docker/web/elektrad-demo/Dockerfile b/scripts/docker/web/elektrad-demo/Dockerfile index 2af0928ecaf..b9f97eb4566 100644 --- a/scripts/docker/web/elektrad-demo/Dockerfile +++ b/scripts/docker/web/elektrad-demo/Dockerfile @@ -20,6 +20,10 @@ RUN cp /etc/hosts /home/elektra/.config/hosts COPY --chown=elektra:elektra demo.kdb /home/elektra/ RUN kdb import user/app < /home/elektra/demo.kdb +# create user/realworld structure +COPY --chown=elektra:elektra realworld.ini /home/elektra/ +RUN kdb import user/realworld ini < /home/elektra/realworld.ini + # run elektrad EXPOSE 33333 CMD ["kdb","run-elektrad"] diff --git a/scripts/docker/web/elektrad-demo/realworld.ini b/scripts/docker/web/elektrad-demo/realworld.ini new file mode 100644 index 00000000000..7bbe0f23a49 --- /dev/null +++ b/scripts/docker/web/elektrad-demo/realworld.ini @@ -0,0 +1,392 @@ +;Ni1 +; Generated by Nickel Plugin using Elektra (see libelektra.org). + +sw/app/lift/floor/height = 22.239900 +env/override/IPEDOCUMENT = [draft,final]{vutinfth} +order/a/b = +env/override/SSH_AGENT_PID = 24594 +MyApp/mykey = new_value2 +huhu/test/no = x +sw/tutorial/cascading/#0/current/test = hello galaxy +sw/asciiwm/current/window/hardcoded/x_br = 20 +sw/pi/tamper/% = false +sw/asciiwm/current/window = +shelltests/crazynames/!"§$%&/(()\=?``´\\\\/abc = +sw/asciiwm/current/bindkey/resize_increase = + +guitest/something = huhus2 +sw/asciiwm/current/window/cl = +sw/tools/lock/command/suspend = qdbus org.kde.Solid.PowerManagement /org/freedesktop/PowerManagement Suspend\; sleep 5 +sw/weyoume/name/me = Markus +sw/asciiwm/current/terminal/height = 25 +shelltests/a/b = ab +order/a!b = +sw/asciiwm/current/window/cl/max_width = 80 +sw/asciiwm/current/bindkey/scroll = +level1/level2 = +env/override/GREP_OPTIONS = --color=auto +sw/weyoume/name/you = Natalie +inibug/debienna/test = value +guitest/something4 = huhus_ss3 +sw/asciiwm/current/window/hardcoded/y_tl = 0 +env/override/GIMP2_HELP_URI = http://example.com +order/a/a = +sw/wb/gui/y = 100 +sw/wb/gui/x = 100 +guitest/something2 = huhus_ss3 +sw/elektra/examples/kdb-ls/tost = val3 +elektra/intercept/getenv/override/MANOPT = -LC +sw/env/xyz/limit/s/b/more/deep = +sw/app/lift/algorithm = stay +sw/defaults/editor = kate +sw/asciiwm/current/bindkey/left = L +sw/elektra/examples/kdb-ls/tost/level = lvl +sw/weyoume/name/we = Beide +sw/asciiwm/current/bindkey/execute = e +hello_world = Hello World +sw/asciiwm/current/window/hardcoded/max_height = 100 +mode/no_change = 555 +elektra/intercept/getenv/layer/place = work +shelltests/crazynames = +printers/work = kv +something/u = blahss +sw/elektra/qtgui/#0/current/color/node/with = #393939 +sw/app/lift/floor/#3/height = 33 +sw/asciiwm/current/bindkey/cycle = b +sw/asciiwm/current/bindkey = +sw/asciiwm/current/bindkey/scroll/page_up = p +sw/app/lift/emergency/action/stops = true +inibug/binarytest = +sw/app/person_lift/limit = 12 +sw/asciiwm/current/bindkey/right = R +sw/asciiwm/current/window/cl/x_tl = 30 +sw/asciiwm/current/window/hardcoded/min_height = 3 +sw/elektra/qtgui/#0/current/mode/viewer = 0 +level1 = +printers/home = minkv +sw/asciiwm/current/window/cl/x_br = 39 +something = huhuss +sw/elektra/qtgui/#0/current/color/node/without = #2d2d2d +sw/pi/irrelevant = 0 +sw/tools/lock/disable = off +sw/elektra/examples/kdb-ls/test/foo/bar = val2 +sw/asciiwm/current/window/hardcoded/y_br = 5 +sw/asciiwm/current/bindkey/resize_decrease = - +inibug/MyApp/mykey = new_value +sw/asciiwm/current/bindkey/down_rotate = d +sw/tools/lock/time = 15 +sw/tools/lock/commandlock = xset dpms force off && xtrlock +metakey = +shelltests/a = a +sw/kdb/current/plugins = +sw/app/lift/write = false +sw/asciiwm/current/window/hardcoded/max_width = 30 +sw/asciiwm/current/bindkey/left_rotate = l +guitest/b = D +sw/asciiwm/current/bindkey/menu/quit = q +sw/MyApp/key = us_lkllk +users/markus/konqueror/HOME = /home/download +guitest/something6 = huhus_ss6 +sw/asciiwm/current/window/cl/max_height = 25 +sw/tools/lock/command/lock = xset dpms force off && xtrlock +huhu/test = +sw/Oyranos/Tests/device/test_key/#0/array_key = ArrayValue +sw/elektra/examples/kdb-ls/test/fizz/buzz = fizzbuzz +sw/kdb/current/format = ni +sw/wb/werte/Abbrennen = 23 +env/override/LANGUAGE = en +sw/MyApp/Tests/TestKey1 = NULLTestValueEdited +binarytest = +sw/asciiwm/current/window/cl/min_height = 3 +sw/pi/benchmark/activate = 0 +env/layer/user = markus +shelltests/xxx/#3 = OUTSIDEs +valueable_data = 1234_important_data +sw/asciiwm/current/window/cl/min_width = 3 +org/freedesktop = test +tutorial/links/url = invalid url +sw/wb/werte/Seed = 3 +guitest/something5 = huhus_ss5 +sw/asciiwm/current/bindkey/quit = Q +sw/app/lift/limit = 42 +sw/app/lift/emergency/delay = xx +sw/asciiwm/current/bindkey/up_rotate = u +sw/asciiwm/current/terminal/width = 80 +sw/elektra/examples/kdb-ls/test = val1 +sw/asciiwm/current/window/hardcoded/x_tl = 0 +something/a = b +sw/wb/farben/Baum = 2 +sw/kdb/current/cmdline/format = ni +sw/asciiwm/current/bindkey/up = U +sw/asciiwm/current/window/hardcoded = +shelltests/xxx/#0 = xxx +huhu/a = b +sw/wb/werte/Wahrscheinlichkeit = 23 +guitest/something3 = huhus_ss3 +sw/asciiwm/current/bindkey/scroll/page_down = n +shelltests/xxx = xxx +qtgui/binarytest = (bin +sw/tools/lock/command = xset dpms force off && xtrlock +debienna/test = value +sw/asciiwm/current/bindkey/right_rotate = r +sw/asciiwm/current/window/cl/y_tl = 15 +sw/wb/werte/Walddichte = 94 +sw/wb/werte/Brenndichte = 30 +env/something = +sw/asciiwm/current/terminal = +locationtracker/battery/level = low +sw/asciiwm/current/window/cl/y_br = 22 +sw/asciiwm/current/window/hardcoded/min_width = 3 +shelltests/xxx/#2 = zzzr +sw/asciiwm/current/bindkey/down = D +sw/elektra/qtgui/#0/current/color/frame = #000000 +sw/env/xyz/limit = no limit +shelltests/xxx/#1 = yyy +env/layer/abc = def +env/override/QT_PLUGIN_PATH = +sw/asciiwm/current/bindkey/menu = m + +[order/a/b] + binary = + +[MyApp/mykey] + test = ds + +[huhu/test/no] + ini/key/number = #0 + parent = user/huhu/test + comments/#0 = + order = #2 + comments = #0 + +[sw/asciiwm/current/window/hardcoded/x_br] + mode = 644 + comment = x value of bottom right point + +[sw/asciiwm/current/window] + mode = 755 + +[sw/asciiwm/current/bindkey/resize_increase] + mode = 664 + +[sw/asciiwm/current/window/cl] + mode = 755 + +[sw/asciiwm/current/terminal/height] + mode = 664 + +[order/a!b] + binary = + +[sw/asciiwm/current/window/cl/max_width] + mode = 644 + +[sw/asciiwm/current/bindkey/scroll] + mode = 755 + +[level1/level2] + binary = + +[sw/asciiwm/current/window/hardcoded/y_tl] + mode = 644 + comment = y value of top left point + +[order/a/a] + binary = + +[sw/env/xyz/limit/s/b/more/deep] + binary = + +[sw/asciiwm/current/bindkey/left] + mode = 664 + comment = Cursor left + +[sw/asciiwm/current/bindkey/execute] + mode = 664 + +[sw/asciiwm/current/window/hardcoded/max_height] + mode = 644 + +[something/u] + lastChild = #0 + parent = user/something + comments/#0 = + comments/#1 = + order = #0 + keyNo = #0 + comments = #1 + +[sw/asciiwm/current/bindkey/cycle] + mode = 664 + comment = Tab + +[sw/asciiwm/current/bindkey] + mode = 755 + +[sw/asciiwm/current/bindkey/scroll/page_up] + mode = 664 + +[inibug/binarytest] + binary = + +[sw/asciiwm/current/bindkey/right] + mode = 664 + comment = Cursor right + +[sw/asciiwm/current/window/cl/x_tl] + mode = 644 + comment = x value of top left point + +[sw/asciiwm/current/window/hardcoded/min_height] + mode = 644 + +[level1] + binary = + +[sw/asciiwm/current/window/cl/x_br] + mode = 644 + comment = x value of bottom right point + +[something] + parent = user/something + order = #1 + +[sw/asciiwm/current/window/hardcoded/y_br] + mode = 644 + comment = y value of bottom right point + +[sw/asciiwm/current/bindkey/resize_decrease] + mode = 664 + +[sw/asciiwm/current/bindkey/down_rotate] + mode = 664 + +[metakey] + a = b + c = d + e = f\\nk + n = f\nh + x = + n\nh = a + +[sw/kdb/current/plugins] + binary = + +[sw/asciiwm/current/window/hardcoded/max_width] + mode = 644 + +[sw/asciiwm/current/bindkey/left_rotate] + mode = 664 + +[sw/asciiwm/current/bindkey/menu/quit] + mode = 664 + +[sw/asciiwm/current/window/cl/max_height] + mode = 644 + +[huhu/test] + comments/#2 = + parent = user/huhu + comments/#0 = + comments/#1 = #abc + order = #2 + ini/key/last = #1 + binary = + comments = #2 + +[sw/Oyranos/Tests/device/test_key/#0/array_key] + comment = ArrayComment + +[sw/MyApp/Tests/TestKey1] + comment = NULLTestComment + +[binarytest] + sss = sss + xxx = x + ddd = + +[sw/asciiwm/current/window/cl/min_height] + mode = 644 + +[shelltests/xxx/#3] + sss = SSSSSSSSSS + yyyy = yyy + +[valueable_data] + trigger/error = 10 + +[sw/asciiwm/current/window/cl/min_width] + mode = 644 + +[tutorial/links/url] + description = A link to some website + check/validation/match = LINE + check/validation = https?://.*\\..* + check/validation/message = not a valid URL + +[sw/asciiwm/current/bindkey/quit] + mode = 664 + comment = Escape + +[sw/asciiwm/current/bindkey/up_rotate] + mode = 664 + +[sw/asciiwm/current/terminal/width] + mode = 664 + +[sw/asciiwm/current/window/hardcoded/x_tl] + mode = 644 + comment = x value of top left point + +[something/a] + parent = user/something + comments/#0 = + order = #0 + keyNo = #1 + comments = #0 + +[sw/asciiwm/current/bindkey/up] + mode = 664 + +[sw/asciiwm/current/window/hardcoded] + mode = 755 + +[huhu/a] + ini/key/number = #0 + parent = user/huhu + order = #0 + ini/key/last = #0 + +[sw/asciiwm/current/bindkey/scroll/page_down] + mode = 664 + +[qtgui/binarytest] + binary = + +[sw/asciiwm/current/bindkey/right_rotate] + mode = 664 + +[sw/asciiwm/current/window/cl/y_tl] + mode = 644 + comment = y value of top left point + +[env/something] + binary = + +[sw/asciiwm/current/terminal] + mode = 755 + +[sw/asciiwm/current/window/cl/y_br] + mode = 644 + comment = y value of bottom right point + +[sw/asciiwm/current/window/hardcoded/min_width] + mode = 644 + +[sw/asciiwm/current/bindkey/down] + mode = 664 + +[env/override/QT_PLUGIN_PATH] + binary = + +[sw/asciiwm/current/bindkey/menu] + mode = 755 From 6a4e7a7cda84243b95d2ceff0c45eaa17ece80ce Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 18:41:37 +0200 Subject: [PATCH 11/29] web ui: allow deletion of namespaces (but protect user/sw/elektra/web) --- .../web/client/src/components/TreeItem/index.js | 2 +- src/tools/web/kdb.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 13aa823351e..50a01675287 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -332,7 +332,7 @@ export default class TreeItem extends Component { instanceVisibility={instanceVisibility} refreshKey={() => refreshPath(item.path)} /> - {!rootLevel && !(meta && meta['restrict/remove'] === '1') && + {!(meta && meta['restrict/remove'] === '1') && } onClick={e => { this.handleDelete(item) e.preventDefault() diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index ede0202855a..e772ac281ed 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -190,8 +190,17 @@ const cp = (path, destination) => safeExec(escapeValues`${KDB_COMMAND} cp -r ${path} ${destination}`) // remove value at given `path` -const rm = (path) => - safeExec(escapeValues`${KDB_COMMAND} rm -r ${path}`) +const rm = (path) => { + if (path === 'user') { + return ls('user') + .then(paths => { + return Promise.all(paths.map(p => { + if (!p.startsWith('user/sw/elektra/web')) rm(p) + })) + }) + } + return safeExec(escapeValues`${KDB_COMMAND} rm -r ${path}`) +} // list meta values at given `path` const lsmeta = (path) => From 711d809663b2d2c81cac95a36eee096e8f96ba8a Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 18:48:56 +0200 Subject: [PATCH 12/29] web ui: add button to show error details --- .../client/src/components/ErrorSnackbar.jsx | 26 ++++++++++++++++--- src/tools/web/kdb.js | 1 + 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/tools/web/client/src/components/ErrorSnackbar.jsx b/src/tools/web/client/src/components/ErrorSnackbar.jsx index 8082fa0bce5..ec86e7d3c0e 100644 --- a/src/tools/web/client/src/components/ErrorSnackbar.jsx +++ b/src/tools/web/client/src/components/ErrorSnackbar.jsx @@ -10,26 +10,31 @@ import React from 'react' import Snackbar from 'material-ui/Snackbar' import Dialog from 'material-ui/Dialog' import FlatButton from 'material-ui/FlatButton' +import MoreIcon from 'material-ui/svg-icons/navigation/chevron-right' export default class ErrorSnackbar extends React.Component { constructor (...args) { super(...args) - this.state = { dialogOpen: false } + this.state = { dialogOpen: false, showDetails: false } } handleOpen = () => { - this.setState({ dialogOpen: true }) + this.setState({ dialogOpen: true, details: false }) } handleClose = () => { const { dismissError } = this.props - this.setState({ dialogOpen: false }) + this.setState({ dialogOpen: false, details: false }) dismissError() } + showDetails = () => { + this.setState({ details: true }) + } + render () { const { error, dismissError } = this.props - const { dialogOpen } = this.state + const { dialogOpen, details } = this.state if (error) console.error(error) @@ -81,6 +86,19 @@ export default class ErrorSnackbar extends React.Component {
           {message}
         
+ {details + ?
+

Error Details

+
+                {error.details}
+              
+
+ : } + onClick={this.showDetails} + /> + } ] } diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index e772ac281ed..b77fbc8ce96 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -54,6 +54,7 @@ function parseError (message) { function KDBError (message) { this.name = 'KDBError' let isError = false + this.details = message for (let line of message.split('\n')) { let res if (res = line.match(ERROR_REGEX)) { From 56bd5bcbc0eb17c4f660c205215fa15976932b2e Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 19:05:02 +0200 Subject: [PATCH 13/29] web ui: re-throw errors from deletion --- src/tools/web/kdb.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index b77fbc8ce96..4c6b6c19bef 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -199,6 +199,7 @@ const rm = (path) => { if (!p.startsWith('user/sw/elektra/web')) rm(p) })) }) + .catch(err => { throw err }) // re-throw error } return safeExec(escapeValues`${KDB_COMMAND} rm -r ${path}`) } From 4bd4dbd5b1afdc807404c17258be706c2477fb61 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 19:08:51 +0200 Subject: [PATCH 14/29] web ui: update release notes --- doc/news/_preparation_next_release.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/news/_preparation_next_release.md b/doc/news/_preparation_next_release.md index eb711b47a21..3a700b8e8f9 100644 --- a/doc/news/_preparation_next_release.md +++ b/doc/news/_preparation_next_release.md @@ -39,7 +39,7 @@ You can also read the news [on our website](https://www.libelektra.org/news/0.8. - Type system preview - Chef Cookbook -- Elektra Web 1.5 +- Elektra Web 1.6 ### Type system preview @@ -97,14 +97,16 @@ end Thanks to Michael Zronek and Vanessa Kos. -### Elektra Web 1.5 +### Elektra Web 1.6 The new release of Elektra Web features many UX improvements from the usability test! -[![Elektra Web 1.5 video](https://img.youtube.com/vi/lLg9sk6Hx-E/0.jpg)](https://www.youtube.com/watch?v=lLg9sk6Hx-E) +[![Elektra Web 1.6 video](https://img.youtube.com/vi/lLg9sk6Hx-E/0.jpg)](https://www.youtube.com/watch?v=lLg9sk6Hx-E) Try it out now on: http://webui.libelektra.org:33334/ +1.5 changelog: + - search completely reworked - it does not act as a filter on already opened keys anymore, and instead searches the whole key database - feedback from the search was also greatly improved (pulsating while searching, glowing blue when done) - added "abort" buttons to dialogs to revert actions - added "create array" button to easily create arrays @@ -118,6 +120,14 @@ Try it out now on: http://webui.libelektra.org:33334/ - improved keyboard support - fixed many small issues (#2037) +1.6 changelog: + +- fixed bugs related to arrays (#2103) +- improved performance of search for many results +- added 404 page for invalid instance ids +- implement drag & copy by holding the Ctrl or Alt key +- add button to show error details +- allow deleting all keys in a namespace ## Plugins From 6674062f06007e9bc7cea2886f69c54a5894c576 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 19:16:11 +0200 Subject: [PATCH 15/29] web ui: fix bug where 404 was still visible after going back and selecting a valid instance --- src/tools/web/client/src/reducers/error.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/reducers/error.js b/src/tools/web/client/src/reducers/error.js index fe63434b5bc..0319278fd09 100644 --- a/src/tools/web/client/src/reducers/error.js +++ b/src/tools/web/client/src/reducers/error.js @@ -13,7 +13,7 @@ import { CREATE_INSTANCE_FAILURE, GET_KEY_FAILURE, SET_KEY_FAILURE, CREATE_KEY_FAILURE, DISMISS_ERROR, - GET_KDB_FAILURE, + GET_KDB_FAILURE, GET_KDB_SUCCESS, } from '../actions' export default function errorReducer (state = false, action) { @@ -21,6 +21,9 @@ export default function errorReducer (state = false, action) { case GET_KDB_FAILURE: return { ...action.error, instanceError: true } + case GET_KDB_SUCCESS: + return false + case INSTANCES_FAILURE: case INSTANCE_UPDATE_FAILURE: case INSTANCE_DELETE_FAILURE: From 3fa96ba55f47e4c2d5fe71bf502fc3e4cd6f39bd Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Thu, 21 Jun 2018 19:18:47 +0200 Subject: [PATCH 16/29] web ui: fix typo --- src/tools/web/client/src/components/ErrorSnackbar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/ErrorSnackbar.jsx b/src/tools/web/client/src/components/ErrorSnackbar.jsx index ec86e7d3c0e..f2487b522a9 100644 --- a/src/tools/web/client/src/components/ErrorSnackbar.jsx +++ b/src/tools/web/client/src/components/ErrorSnackbar.jsx @@ -15,7 +15,7 @@ import MoreIcon from 'material-ui/svg-icons/navigation/chevron-right' export default class ErrorSnackbar extends React.Component { constructor (...args) { super(...args) - this.state = { dialogOpen: false, showDetails: false } + this.state = { dialogOpen: false, details: false } } handleOpen = () => { From 4c4ceb2880d62dfcb26ecf5839c05a16d7fbc677 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Fri, 22 Jun 2018 10:09:07 +0200 Subject: [PATCH 17/29] web ui: auto-unfold if less than 10 results --- .../src/components/pages/Configuration.jsx | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/pages/Configuration.jsx b/src/tools/web/client/src/components/pages/Configuration.jsx index 512443a66b0..544dea0c7a8 100644 --- a/src/tools/web/client/src/components/pages/Configuration.jsx +++ b/src/tools/web/client/src/components/pages/Configuration.jsx @@ -64,6 +64,21 @@ const parseData = (getKey, sendNotification, instanceId, ls, kdb) => { return parseDataSet(getKey, sendNotification, instanceId, tree) } +const getUnfolded = (searchResults) => { + let unfolded = [] + for (let r of searchResults) { + const parts = r.split('/') + let path = '' + for (let p of parts) { + path = path.length === 0 ? p : (path + '/' + p) + if (!unfolded.includes(path)) { + unfolded.push(path) + } + } + } + return unfolded +} + // configuration page export default class Configuration extends Component { constructor (props, ...rest) { @@ -211,6 +226,13 @@ export default class Configuration extends Component { ? this.generateData({ ...this.props, ls: search.results }) : data + const autoUnfold = isSearching && hasResults && search.results && + search.results.length <= 10 + + const filteredInstance = autoUnfold + ? { ...instance, unfolded: getUnfolded(search.results) } + : instance + return ( : Date: Mon, 25 Jun 2018 11:22:58 +0200 Subject: [PATCH 18/29] web ui: do not delete arrays when they get empty --- src/tools/web/client/src/components/TreeItem/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 50a01675287..cb734f7f4ee 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -70,9 +70,7 @@ export default class TreeItem extends Component { if (item && item.parent) { const arrayKeyLength = this.getArrayKeyLength(item.parent) - if (!arrayKeyLength || arrayKeyLength <= 1) { // not an array (anymore) - deleteMetaKey(instanceId, item.parent.path, 'array') - } else { + if (arrayKeyLength && arrayKeyLength > 0) { setMetaKey(instanceId, item.parent.path, 'array', String(arrayKeyLength - 1)) } } From 19ddf44ab79e3e793088285eadacc9148a7ba29e Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 11:28:08 +0200 Subject: [PATCH 19/29] web ui: show success messages on top and errors on bottom --- .../client/src/components/NotificationSnackbar.jsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/NotificationSnackbar.jsx b/src/tools/web/client/src/components/NotificationSnackbar.jsx index aba25b5164a..61650309d4b 100644 --- a/src/tools/web/client/src/components/NotificationSnackbar.jsx +++ b/src/tools/web/client/src/components/NotificationSnackbar.jsx @@ -9,9 +9,18 @@ import React from 'react' import Snackbar from 'material-ui/Snackbar' +const fromTop = (message) => ({ + top: 0, + bottom: 'auto', + left: (window.innerWidth - 288) / 2, + transform: message ? + 'translate3d(0, 0, 0)' : + `translate3d(0, -50px, 0)` +}) + const NotificationSnackbar = ({ message }) => { return ( - + ) } From c198b071b3317f32df30050ee8ea0070d510691e Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 11:57:33 +0200 Subject: [PATCH 20/29] web ui: never remove keys with restrict/remove set --- src/tools/web/client/src/actions/kdb.js | 8 +++---- .../client/src/components/TreeItem/index.js | 10 ++++---- src/tools/web/client/src/reducers/kdb.js | 7 ++++-- src/tools/web/client/src/reducers/ls.js | 8 ++++++- src/tools/web/client/src/undo.js | 6 ++--- src/tools/web/kdb.js | 24 ++++++++++++------- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/src/tools/web/client/src/actions/kdb.js b/src/tools/web/client/src/actions/kdb.js index d2302843217..f6d562c13a7 100644 --- a/src/tools/web/client/src/actions/kdb.js +++ b/src/tools/web/client/src/actions/kdb.js @@ -74,8 +74,8 @@ export const CREATE_KEY_REQUEST = 'CREATE_KEY_REQUEST' export const CREATE_KEY_SUCCESS = 'CREATE_KEY_SUCCESS' export const CREATE_KEY_FAILURE = 'CREATE_KEY_FAILURE' -export const createKey = (id, path, value) => thunkCreator({ - id, path, value, +export const createKey = (id, path, value, kdb) => thunkCreator({ + id, path, value, kdb, request: { id, path, value }, types: [CREATE_KEY_REQUEST, CREATE_KEY_SUCCESS, CREATE_KEY_FAILURE], promise: fetch( @@ -97,8 +97,8 @@ export const DELETE_KEY_REQUEST = 'DELETE_KEY_REQUEST' export const DELETE_KEY_SUCCESS = 'DELETE_KEY_SUCCESS' export const DELETE_KEY_FAILURE = 'DELETE_KEY_FAILURE' -export const deleteKey = (id, path) => thunkCreator({ - id, path, +export const deleteKey = (id, path, kdb) => thunkCreator({ + id, path, kdb, request: { id, path }, types: [DELETE_KEY_REQUEST, DELETE_KEY_SUCCESS, DELETE_KEY_FAILURE], promise: fetch( diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index cb734f7f4ee..273308aa080 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -66,7 +66,7 @@ export default class TreeItem extends Component { } handleDelete = (item) => { - const { instanceId, deleteKey, setMetaKey, deleteMetaKey, sendNotification } = this.props + const { instanceId, deleteKey, setMetaKey, deleteMetaKey, sendNotification, kdbState } = this.props if (item && item.parent) { const arrayKeyLength = this.getArrayKeyLength(item.parent) @@ -75,11 +75,11 @@ export default class TreeItem extends Component { } } - deleteKey(instanceId, item.path) + deleteKey(instanceId, item.path, kdbState && kdbState[instanceId]) .then(() => { if (Array.isArray(item.children) && item.children.length > 0) { return Promise.all(item.children.map( - child => deleteKey(instanceId, child.path) + child => deleteKey(instanceId, child.path, kdbState && kdbState[instanceId]) )) } }) @@ -87,9 +87,9 @@ export default class TreeItem extends Component { } handleAdd = (path, addKeyName, addKeyValue) => { - const { instanceId, createKey, sendNotification } = this.props + const { instanceId, createKey, sendNotification, kdbState } = this.props const fullPath = path + '/' + addKeyName - createKey(instanceId, fullPath, addKeyValue).then(() => + createKey(instanceId, fullPath, addKeyValue, kdbState && kdbState[instanceId]).then(() => sendNotification('successfully created key: ' + fullPath) ) } diff --git a/src/tools/web/client/src/reducers/kdb.js b/src/tools/web/client/src/reducers/kdb.js index d629be2bb5f..5679dca54d4 100644 --- a/src/tools/web/client/src/reducers/kdb.js +++ b/src/tools/web/client/src/reducers/kdb.js @@ -72,8 +72,11 @@ export default function keyReducer (state = {}, action) { ...state, [id]: state[id] && Object.keys(state[id]).reduce( (res, key) => { - if (key !== path) { - res[key] = state[id][key] + const data = state[id][key] + const restricted = data && data.meta && data.meta['restrict/remove'] + && data.meta['restrict/remove'] === '1' + if (key !== path || restricted) { + res[key] = data } return res }, {} diff --git a/src/tools/web/client/src/reducers/ls.js b/src/tools/web/client/src/reducers/ls.js index cf062c810a8..28fed41a613 100644 --- a/src/tools/web/client/src/reducers/ls.js +++ b/src/tools/web/client/src/reducers/ls.js @@ -29,7 +29,13 @@ export default function pathReducer (state = [], action) { case DELETE_KEY_SUCCESS: { const { path } = action && action.request - return state.filter(p => (p !== path) && !p.startsWith(path + '/')) + const data = action && action.kdb + return state.filter(p => { + const restricted = data && data[p] && data[p].meta + && data[p].meta['restrict/remove'] + && data[p].meta['restrict/remove'] === '1' + return restricted || ((p !== path) && !p.startsWith(path + '/')) + }) } case CREATE_KEY_SUCCESS: diff --git a/src/tools/web/client/src/undo.js b/src/tools/web/client/src/undo.js index b59d1ead7b6..a501c41f8ac 100644 --- a/src/tools/web/client/src/undo.js +++ b/src/tools/web/client/src/undo.js @@ -33,14 +33,14 @@ const undoMiddleware = createUndoMiddleware({ action: ({ id, path, value }) => setKey(id, path, value), }, 'DELETE_KEY_SUCCESS': { - action: ({ id, path }, { previousValue, from, to }) => + action: ({ id, path, kdb }, { previousValue, from, to }) => (from && to) // we are reverting a copy action ? copyKey(id, from, to) - : createKey(id, path, previousValue), + : createKey(id, path, previousValue, kdb), createArgs: storePreviousValue, }, 'CREATE_KEY_SUCCESS': { - action: ({ id, path }) => deleteKey(id, path), + action: ({ id, path, kdb }) => deleteKey(id, path, kdb), createArgs: (state, { value }) => ({ previousValue: value }), }, 'SET_META_SUCCESS': { diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index 4c6b6c19bef..1ba03c4243d 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -190,18 +190,24 @@ const mv = (path, destination) => const cp = (path, destination) => safeExec(escapeValues`${KDB_COMMAND} cp -r ${path} ${destination}`) +// remove single value at `path` +const rmSingle = (path) => + safeExec(escapeValues`${KDB_COMMAND} rm ${path}`) + // remove value at given `path` const rm = (path) => { - if (path === 'user') { - return ls('user') - .then(paths => { - return Promise.all(paths.map(p => { - if (!p.startsWith('user/sw/elektra/web')) rm(p) - })) + return ls(path) + .then(paths => Promise.all( + paths.map(p => { + if (p.startsWith('user/sw/elektra/web')) return { p, r: '1' } // always restricted + return getmeta(p, 'restrict/remove').then(r => ({ p, r })) }) - .catch(err => { throw err }) // re-throw error - } - return safeExec(escapeValues`${KDB_COMMAND} rm -r ${path}`) + )) + .then(restricted => Promise.all( + restricted.map(({ p, r }) => { + if (r !== '1') return rmSingle(p) + }) + )) } // list meta values at given `path` From c5547173a379a093853bfbbab78aca44eba8d7bd Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:06:39 +0200 Subject: [PATCH 21/29] web ui: allow only numbers to be entered in number fields --- .../TreeItem/fields/SimpleTextField.jsx | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx b/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx index a457c60c9b6..a8a34aeafd3 100644 --- a/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx +++ b/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx @@ -12,7 +12,7 @@ import TextField from 'material-ui/TextField' import validateType from './validateType' import debounce from '../../debounce' -import { fromElektraBool } from '../../../utils' +import { fromElektraBool, isNumberType } from '../../../utils' const DebouncedTextField = debounce(TextField, { timeout: 1500 }) @@ -33,6 +33,7 @@ export default class SimpleTextField extends Component { const val = this.state.value const comp = debounce ? DebouncedTextField : TextField const isBinary = meta && meta.hasOwnProperty('binary') + const type = meta && meta['check/type'] || 'any' return (
e.preventDefault()}> @@ -61,6 +62,24 @@ export default class SimpleTextField extends Component { floatingLabelText: label, floatingLabelFixed: !!label, onKeyPress: onKeyPress, + onKeyDown: isNumberType(type) && ((e) => { + if ([46, 8, 9, 27, 13, 110, 190].includes(e.keyCode) || // allow backspace, delete, etc + // allow: ctrl/cmd+A + (e.keyCode == 65 && (e.ctrlKey === true || e.metaKey === true)) || + // allow: ctrl/cmd+C + (e.keyCode == 67 && (e.ctrlKey === true || e.metaKey === true)) || + // allow: ctrl/cmd+X + (e.keyCode == 88 && (e.ctrlKey === true || e.metaKey === true)) || + // allow: home, end, left, right + (e.keyCode >= 35 && e.keyCode <= 39)) { + // let it happen, don't do anything + return + } + // ensure that it is a number and stop the keypress + if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) { + e.preventDefault() + } + }) })}
) From b3571acf5e85cd829910c56abb1fe80eacf8b6cd Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:25:02 +0200 Subject: [PATCH 22/29] web ui: check default value depending on type --- .../TreeItem/dialogs/SettingsDialog.jsx | 31 +++++++++++++++++-- .../TreeItem/fields/validateType.js | 30 ++++++++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx b/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx index a02019f0f11..4990abda7b2 100644 --- a/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx +++ b/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx @@ -23,7 +23,7 @@ import AdditionalMetakeysSubDialog from './AdditionalMetakeysSubDialog.jsx' import debounce from '../../debounce' import { VISIBILITY_LEVELS, visibility, toElektraBool, fromElektraBool, isNumberType } from '../../../utils' import { KEY_TYPES } from './utils' -import { validateRange } from '../fields/validateType' +import validateType, { validateRange } from '../fields/validateType' const DebouncedTextField = debounce(TextField) @@ -33,11 +33,11 @@ const DEBOUNCED = 'DEBOUNCED' export default class SettingsDialog extends Component { constructor (...args) { super(...args) - this.state = { regexError: false, rangeError: false, regexStr: false, rangeStr: false, paused: false } + this.state = { regexError: false, rangeError: false, regexStr: false, rangeStr: false, defaultError: false, defaultStr: false, paused: false } } componentWillReceiveProps (nextProps) { - const { regexError, rangeError, regexStr, rangeStr } = this.state + const { regexError, rangeError, regexStr, rangeStr, defaultError, defaultStr } = this.state if (regexError) { this.handleEdit('check/validation', IMMEDIATE)(regexStr) setTimeout(() => this.handleEdit('check/validation', DEBOUNCED)(regexStr), 500) @@ -46,6 +46,10 @@ export default class SettingsDialog extends Component { this.handleEdit('check/range', IMMEDIATE)(rangeStr) setTimeout(() => this.handleEdit('check/range', DEBOUNCED)(rangeStr), 500) } + if (defaultError) { + this.handleEdit('default', IMMEDIATE)(defaultStr) + setTimeout(() => this.handleEdit('default', DEBOUNCED)(defaultStr), 500) + } } ensureRegex = (regexStr, data) => { @@ -74,6 +78,19 @@ export default class SettingsDialog extends Component { this.setState({ rangeError: false, rangeStr: false }) } + ensureDefaultValue = (defaultStr) => { + console.log('ensure default', defaultStr) + const { meta } = this.props + if (defaultStr) { + const err = validateType(meta, defaultStr) + console.log('err', err) + if (err) { + return this.setState({ defaultError: err, defaultStr }) + } + } + this.setState({ defaultError: false, defaultStr: false }) + } + handleEdit = (key, debounced = false) => (value) => { const { data, setMeta, deleteMeta } = this.props @@ -88,6 +105,10 @@ export default class SettingsDialog extends Component { if (key === 'check/range') { this.ensureRange(value, data) } + + if (key === 'default') { + this.ensureDefaultValue(value) + } } if (!debounced || debounced === DEBOUNCED) { @@ -97,6 +118,9 @@ export default class SettingsDialog extends Component { if (key === 'check/range' && this.state.rangeError) { return // do not save range that does not match } + if (key === 'default' && this.state.defaultError) { + return // do not save range that does not match + } // persist value to kdb and show notification const { timeout } = this.state[key] || {} @@ -344,6 +368,7 @@ export default class SettingsDialog extends Component { floatingLabelText="default value" floatingLabelFixed={true} tabIndex="0" + errorText={this.state.defaultError} onChange={this.handleEdit('default', IMMEDIATE)} onDebounced={this.handleEdit('default', DEBOUNCED)} value={this.getMeta('default', '')} diff --git a/src/tools/web/client/src/components/TreeItem/fields/validateType.js b/src/tools/web/client/src/components/TreeItem/fields/validateType.js index f72995945a2..4c0405c2681 100644 --- a/src/tools/web/client/src/components/TreeItem/fields/validateType.js +++ b/src/tools/web/client/src/components/TreeItem/fields/validateType.js @@ -43,6 +43,36 @@ const validateType = (metadata, value) => { if (!metadata) return false // no metadata, no validation const type = metadata['check/type'] || 'any' + if (type === 'boolean') { + if (value !== '0' && value !== '1') { + return 'invalid boolean, 0 or 1 expected' + } + } + + if (type === 'enum') { + let done = false + let valid = false + let i = 0 + let values = [] + while (!done) { + const v = metadata[`check/enum/#${i}`] + if (!v) { + done = true + break + } + values.push(v) + if (value === v) { + done = true + valid = true + break + } + i++ + } + if (!valid) { + return 'invalid value, expected one of: ' + values.join(', ') + } + } + if (FLOAT_TYPES.includes(type)) { if (!isNumber(value)) { return 'invalid number, float expected' From a496486a5f75d08137dc307cc429d7b84f246800 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:38:34 +0200 Subject: [PATCH 23/29] web ui: fix removal of keys --- src/tools/web/kdb.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index 1ba03c4243d..eb95837562b 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -200,7 +200,9 @@ const rm = (path) => { .then(paths => Promise.all( paths.map(p => { if (p.startsWith('user/sw/elektra/web')) return { p, r: '1' } // always restricted - return getmeta(p, 'restrict/remove').then(r => ({ p, r })) + return getmeta(p, 'restrict/remove') + .then(r => ({ p, r })) + .catch(err => ({ p, r: '0' })) // restrict/remove key not present }) )) .then(restricted => Promise.all( From 29f6a3789b122715739abe2dac3172b9589090b1 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:47:25 +0200 Subject: [PATCH 24/29] web ui: properly handle empty arrays --- src/tools/web/client/src/components/TreeItem/ArrayIcon.jsx | 2 +- .../client/src/components/TreeItem/dialogs/AddDialog.jsx | 4 ++-- src/tools/web/client/src/components/TreeItem/index.js | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/ArrayIcon.jsx b/src/tools/web/client/src/components/TreeItem/ArrayIcon.jsx index e8204cb4e38..ecf893ce15a 100644 --- a/src/tools/web/client/src/components/TreeItem/ArrayIcon.jsx +++ b/src/tools/web/client/src/components/TreeItem/ArrayIcon.jsx @@ -14,7 +14,7 @@ const ArrayIcon = () => diff --git a/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx b/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx index f69e078c9ca..86cbf45cdfe 100644 --- a/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx +++ b/src/tools/web/client/src/components/TreeItem/dialogs/AddDialog.jsx @@ -39,7 +39,7 @@ export default class AddDialog extends Component { componentWillReceiveProps (nextProps) { const wasOpened = this.props.open === false && nextProps.open === true - if (wasOpened && nextProps.arrayKeyLength) { + if (wasOpened && nextProps.arrayKeyLength !== false) { this.setState({ name: this.generateArrayKey(nextProps.arrayKeyLength) }) } } @@ -83,7 +83,7 @@ export default class AddDialog extends Component { if (v !== 'user') { setMetaByPath(path + '/' + name, 'visibility', v) } - if (arrayKeyLength) { // is child of array key + if (arrayKeyLength !== false) { // is child of array key // update array metakey in parent setMetaByPath(path, 'array', String(arrayKeyLength + 1)) } diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 273308aa080..e1e73a83871 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -161,7 +161,7 @@ export default class TreeItem extends Component { const data = kdbState[instanceId] if (data && data[item.path] && data[item.path].meta) { const meta = data[item.path].meta - if (meta && meta['array'] && meta['array'] > 0) { + if (meta && meta['array'] && meta['array'] >= 0) { return Number(meta['array']) } } @@ -194,13 +194,14 @@ export default class TreeItem extends Component { const meta = data && data.meta const isCheckbox = meta && meta['check/type'] && meta['check/type'] === 'boolean' - const valueVisible = !rootLevel && data && !item.children // we return no value property if the key doesn't exist, otherwise we return an *empty* value const keyExists = rootLevel || (data && data.exists) const arrayKeyLength = this.getArrayKeyLength(item) const parentArrayKeyLength = this.getArrayKeyLength(item.parent) + const valueVisible = !rootLevel && data && !item.children && arrayKeyLength === false + const renderedField = (
@@ -239,7 +240,7 @@ export default class TreeItem extends Component { ) : {prettyPrintArrayIndex(item.name)} - {(arrayKeyLength || (item && item.meta && item.meta['array'])) && + {(arrayKeyLength !== false) && From 95b67abfb95eb797e0372f1a91d9a04534e2fc75 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:54:49 +0200 Subject: [PATCH 25/29] web ui: show value of keys with subkeys on hover --- .../web/client/src/components/TreeItem/index.js | 1 + src/tools/web/client/src/css/treeview.css | 12 ++++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index e1e73a83871..857ed80178b 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -249,6 +249,7 @@ export default class TreeItem extends Component { } + {(!valueVisible && data && data.value) && {data.value}} {valueVisible && sendNotification('Copied value of ' + item.path + ' to clipboard!')}> } tooltip="copy value" /> diff --git a/src/tools/web/client/src/css/treeview.css b/src/tools/web/client/src/css/treeview.css index bb884148216..4912cad88b6 100644 --- a/src/tools/web/client/src/css/treeview.css +++ b/src/tools/web/client/src/css/treeview.css @@ -25,17 +25,25 @@ opacity: 1; } .ExplorerView ul > li a .actions > button, -.ExplorerView ul > li a .actions > i { +.ExplorerView ul > li a .actions > i, +.ExplorerView ul > li a .actions > span { opacity: 0; transition: opacity 0.2s; } .ExplorerView ul > li a .actions > i { margin-left: 1.5em; } +.ExplorerView ul > li a .actions > i, +.ExplorerView ul > li a .actions > span { + position: relative; + bottom: 3px; + margin-right: 1em; +} /* when root element is hovered with mouse */ .ExplorerView ul > li a:hover .actions > button, -.ExplorerView ul > li a:hover .actions > i { +.ExplorerView ul > li a:hover .actions > i, +.ExplorerView ul > li a:hover .actions > span { opacity: 0.5; } /* when action button is focussed with keyboard or hovered with mouse */ From ae53161a4450ef8bcd32a652ab984445e4a773a2 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 12:56:27 +0200 Subject: [PATCH 26/29] web ui: make namespaces a bit larger --- src/tools/web/client/src/components/TreeItem/index.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 857ed80178b..4e9e6834623 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -224,8 +224,11 @@ export default class TreeItem extends Component { 'Configure metadata of this key to remove the binary flag.') } + // make namespaces a bit larger + const rootStyle = rootLevel ? { fontSize: 'medium' } : {} + return ( - + {valueVisible ? ( From 6506fc54263d9a7d16a2c8755ae8444c4d1437ae Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 13:00:13 +0200 Subject: [PATCH 27/29] web ui: fix error when no search results --- src/tools/web/kdb.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tools/web/kdb.js b/src/tools/web/kdb.js index eb95837562b..a4d3ce95fc2 100644 --- a/src/tools/web/kdb.js +++ b/src/tools/web/kdb.js @@ -173,6 +173,7 @@ const ls = (path) => const find = (query) => safeExec(escapeValues`${KDB_COMMAND} find -0 ${query}`) .then(stdout => stdout && stdout.split('\0')) + .then(res => res || []) // get value from given `path` const get = (path) => From 18a4629f7d4d92890b3490487219c41abc99ffa4 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 13:01:42 +0200 Subject: [PATCH 28/29] web ui: fix linter warnings --- .../src/components/TreeItem/fields/SimpleTextField.jsx | 8 ++++---- src/tools/web/client/src/components/TreeItem/index.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx b/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx index a8a34aeafd3..90d55e74de2 100644 --- a/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx +++ b/src/tools/web/client/src/components/TreeItem/fields/SimpleTextField.jsx @@ -33,7 +33,7 @@ export default class SimpleTextField extends Component { const val = this.state.value const comp = debounce ? DebouncedTextField : TextField const isBinary = meta && meta.hasOwnProperty('binary') - const type = meta && meta['check/type'] || 'any' + const type = (meta && meta['check/type']) || 'any' return (
e.preventDefault()}> @@ -65,11 +65,11 @@ export default class SimpleTextField extends Component { onKeyDown: isNumberType(type) && ((e) => { if ([46, 8, 9, 27, 13, 110, 190].includes(e.keyCode) || // allow backspace, delete, etc // allow: ctrl/cmd+A - (e.keyCode == 65 && (e.ctrlKey === true || e.metaKey === true)) || + (e.keyCode === 65 && (e.ctrlKey === true || e.metaKey === true)) || // allow: ctrl/cmd+C - (e.keyCode == 67 && (e.ctrlKey === true || e.metaKey === true)) || + (e.keyCode === 67 && (e.ctrlKey === true || e.metaKey === true)) || // allow: ctrl/cmd+X - (e.keyCode == 88 && (e.ctrlKey === true || e.metaKey === true)) || + (e.keyCode === 88 && (e.ctrlKey === true || e.metaKey === true)) || // allow: home, end, left, right (e.keyCode >= 35 && e.keyCode <= 39)) { // let it happen, don't do anything diff --git a/src/tools/web/client/src/components/TreeItem/index.js b/src/tools/web/client/src/components/TreeItem/index.js index 4e9e6834623..8b39a124f08 100644 --- a/src/tools/web/client/src/components/TreeItem/index.js +++ b/src/tools/web/client/src/components/TreeItem/index.js @@ -66,7 +66,7 @@ export default class TreeItem extends Component { } handleDelete = (item) => { - const { instanceId, deleteKey, setMetaKey, deleteMetaKey, sendNotification, kdbState } = this.props + const { instanceId, deleteKey, setMetaKey, sendNotification, kdbState } = this.props if (item && item.parent) { const arrayKeyLength = this.getArrayKeyLength(item.parent) From 7eaea7f24d2875e88b67e6a58620d7d45fe202c2 Mon Sep 17 00:00:00 2001 From: Daniel Bugl Date: Mon, 25 Jun 2018 13:03:16 +0200 Subject: [PATCH 29/29] web ui: remove debug logs --- .../client/src/components/TreeItem/dialogs/SettingsDialog.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx b/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx index 4990abda7b2..4838dee78d0 100644 --- a/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx +++ b/src/tools/web/client/src/components/TreeItem/dialogs/SettingsDialog.jsx @@ -79,11 +79,9 @@ export default class SettingsDialog extends Component { } ensureDefaultValue = (defaultStr) => { - console.log('ensure default', defaultStr) const { meta } = this.props if (defaultStr) { const err = validateType(meta, defaultStr) - console.log('err', err) if (err) { return this.setState({ defaultError: err, defaultStr }) }