admission 0.2.1 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -5
- data/bin/build +10 -0
- data/lib/admission/arbitration.rb +1 -0
- data/lib/admission/resource_arbitration.rb +4 -0
- data/lib/admission/version.rb +1 -1
- data/lib/admission/visualisation_app.rb +125 -0
- data/visualisation/components/app_container.jsx +7 -2
- data/visualisation/components/input_with_select.jsx +30 -17
- data/visualisation/components/nested_list_row.jsx +48 -0
- data/visualisation/components/privilege_select.jsx +28 -9
- data/visualisation/components/privileges_panel.jsx +62 -3
- data/visualisation/components/rules_panel.jsx +124 -0
- data/visualisation/dist/app.js +6 -0
- data/visualisation/helpers.js +69 -0
- data/visualisation/index.jsx +17 -60
- data/visualisation/package.json +4 -1
- data/visualisation/reducers/index.js +9 -4
- data/visualisation/server.rb +23 -0
- data/visualisation/style.scss +124 -19
- data/visualisation/webpack.config.js +13 -3
- data/visualisation/yarn.lock +28 -0
- metadata +9 -4
- data/bin/server.rb +0 -26
- data/lib/admission/visualisation.rb +0 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 62f774b23937957f5d4fd0dcabda6c136e393f71
|
4
|
+
data.tar.gz: 6d2efadac8eaccc3606a75d85807908ec00b88d1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c3f1a68687bf1f029c272e9451dadb3aad77b50b5fa35228595541fb3eb861f1da1f803b4eeb79f8fd84e33b73b530c1ec4d2393223b6986e430a29eca6b10a5
|
7
|
+
data.tar.gz: 4851ad4ad9dcb1f6e61538829749127c79ae16a413532e4da99e7dc599f86103b1001fc94618f9f276277edf779b8959520b6b2bf2b03c2a7e8cf642a06560e1
|
data/Gemfile
CHANGED
data/bin/build
ADDED
data/lib/admission/version.rb
CHANGED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Admission::VisualisationApp
|
5
|
+
|
6
|
+
def initialize **settings
|
7
|
+
order = settings[:order] || (raise 'order not defined, cannot visualise data')
|
8
|
+
raise 'order must be a Proc' unless Proc === order
|
9
|
+
|
10
|
+
settings[:js_entry] ||= Pathname.new(__FILE__).join('..', '..', '..',
|
11
|
+
'visualisation', 'dist', 'app.js')
|
12
|
+
|
13
|
+
@settings = settings
|
14
|
+
end
|
15
|
+
|
16
|
+
def call env
|
17
|
+
case env['PATH_INFO'].to_s
|
18
|
+
when %r~^/(\.html)?$~
|
19
|
+
[
|
20
|
+
200,
|
21
|
+
{'Content-Type' => 'text/html'},
|
22
|
+
[render_page]
|
23
|
+
]
|
24
|
+
|
25
|
+
when %r~/app\.js~
|
26
|
+
[
|
27
|
+
200,
|
28
|
+
{'Content-Type' => 'application/js'},
|
29
|
+
[File.read(@settings[:js_entry])]
|
30
|
+
]
|
31
|
+
|
32
|
+
when %r~/data\.json~
|
33
|
+
[
|
34
|
+
200,
|
35
|
+
{'Content-Type' => 'application/json'},
|
36
|
+
[render_data(@settings[:order].call)]
|
37
|
+
]
|
38
|
+
|
39
|
+
else
|
40
|
+
[
|
41
|
+
404,
|
42
|
+
{'Content-Type' => 'text/html'},
|
43
|
+
['Admission::VisualisationApp : page not found']
|
44
|
+
]
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def render_page
|
50
|
+
data_url = "#{@settings[:url_prefix]}/data.json"
|
51
|
+
script_url = "#{@settings[:url_prefix]}/app.js"
|
52
|
+
|
53
|
+
<<-HEREDOC
|
54
|
+
<!DOCTYPE html>
|
55
|
+
<html>
|
56
|
+
<head>
|
57
|
+
<title>Admission</title>
|
58
|
+
</head>
|
59
|
+
<body class='flex-column'>
|
60
|
+
|
61
|
+
<div data-url="#{data_url}" id="admission-visualisation"></div>
|
62
|
+
<script src="#{script_url}"></script>
|
63
|
+
|
64
|
+
</body>
|
65
|
+
</html>
|
66
|
+
HEREDOC
|
67
|
+
end
|
68
|
+
|
69
|
+
def render_data privileges:, rules:, arbitrator: Admission::ResourceArbitration
|
70
|
+
js_data = {}
|
71
|
+
|
72
|
+
top_levels = []
|
73
|
+
privileges = privileges.values.inject Array.new do |arr, levels|
|
74
|
+
tops, others = levels.to_a.partition{|key, _| key == :'^'}
|
75
|
+
tops.first[1].tap{|privilege| top_levels << privilege.text_key}
|
76
|
+
|
77
|
+
others.each do |_, privilege|
|
78
|
+
arr << {name: privilege.name, level: privilege.level,
|
79
|
+
inherits: privilege.inherited && privilege.inherited.map(&:text_key)}
|
80
|
+
end
|
81
|
+
|
82
|
+
arr
|
83
|
+
end
|
84
|
+
|
85
|
+
js_data[:privileges] = privileges
|
86
|
+
js_data[:top_levels] = top_levels
|
87
|
+
js_data[:levels] = privileges.inject Hash.new do |hash, p|
|
88
|
+
(hash[p[:name]] ||= []) << p[:level]
|
89
|
+
hash
|
90
|
+
end
|
91
|
+
|
92
|
+
actions_reduce = -> (index) {
|
93
|
+
index.to_a.map do |action, action_index|
|
94
|
+
action_index = action_index.to_a.map do |privilege, rule|
|
95
|
+
if rule.is_a? Proc
|
96
|
+
rule = 'proc'
|
97
|
+
end
|
98
|
+
|
99
|
+
[privilege.text_key, rule]
|
100
|
+
end
|
101
|
+
|
102
|
+
[action, Hash[action_index]]
|
103
|
+
end
|
104
|
+
}
|
105
|
+
|
106
|
+
rules = if arbitrator == Admission::Arbitration
|
107
|
+
single_scope = actions_reduce[rules]
|
108
|
+
[['-non-scoped-', Hash[single_scope]]]
|
109
|
+
|
110
|
+
elsif arbitrator == Admission::ResourceArbitration
|
111
|
+
rules.to_a.map do |scope, scope_index|
|
112
|
+
scope_index = actions_reduce[scope_index]
|
113
|
+
[scope, Hash[scope_index]]
|
114
|
+
end
|
115
|
+
|
116
|
+
else
|
117
|
+
raise "not implemented for #{arbitrator.name}"
|
118
|
+
|
119
|
+
end
|
120
|
+
js_data[:rules] = Hash[rules]
|
121
|
+
|
122
|
+
JSON.generate js_data
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
import preact from 'preact';
|
2
|
-
import PrivilegesPanel from './privileges_panel';
|
3
2
|
import classnames from 'classnames';
|
4
3
|
|
4
|
+
import PrivilegesPanel from './privileges_panel';
|
5
|
+
import RulesPanel from "./rules_panel";
|
6
|
+
|
5
7
|
export default class AppContainer extends preact.Component {
|
6
8
|
|
7
9
|
constructor (props) {
|
@@ -21,7 +23,7 @@ export default class AppContainer extends preact.Component {
|
|
21
23
|
<code>{load_fail}</code>
|
22
24
|
</div>;
|
23
25
|
|
24
|
-
return <div>
|
26
|
+
return <div className="admission-app-container">
|
25
27
|
<ul className="panels-list">
|
26
28
|
|
27
29
|
<li
|
@@ -68,6 +70,9 @@ export default class AppContainer extends preact.Component {
|
|
68
70
|
return <PrivilegesPanel app={app} />;
|
69
71
|
break;
|
70
72
|
|
73
|
+
case 'rules':
|
74
|
+
return <RulesPanel app={app}/>;
|
75
|
+
break;
|
71
76
|
}
|
72
77
|
}
|
73
78
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import preact from 'preact';
|
2
2
|
import classnames from 'classnames';
|
3
|
+
import helpers from '../helpers';
|
3
4
|
|
4
5
|
export default class InputWithSelect extends preact.Component {
|
5
6
|
|
@@ -13,18 +14,19 @@ export default class InputWithSelect extends preact.Component {
|
|
13
14
|
|
14
15
|
this.setParentRef = ref => this.element = ref;
|
15
16
|
this.setListRef = ref => this.list = ref;
|
16
|
-
this.onKeyDown =
|
17
|
-
this.
|
17
|
+
this.onKeyDown = this.onKeyDown.bind(this);
|
18
|
+
this.onTextChange = helpers.debounce(this.onTextChange.bind(this), 400);
|
19
|
+
this.toggleList = helpers.debounce(this.toggleList.bind(this), 400, true);
|
18
20
|
this.closeList = this.closeList.bind(this);
|
19
21
|
this.onSelected = this.onSelected.bind(this);
|
20
22
|
}
|
21
23
|
|
22
|
-
render ({
|
24
|
+
render ({placeholder}, {text, matching}) {
|
23
25
|
if (!matching) this.list = null;
|
24
26
|
|
25
27
|
return <div
|
26
28
|
ref={this.setParentRef}
|
27
|
-
className="
|
29
|
+
className="select_box">
|
28
30
|
|
29
31
|
<div className="_inputs">
|
30
32
|
<input
|
@@ -35,6 +37,7 @@ export default class InputWithSelect extends preact.Component {
|
|
35
37
|
value={text}/>
|
36
38
|
|
37
39
|
<button
|
40
|
+
type="button"
|
38
41
|
tabIndex="-1"
|
39
42
|
className="button"
|
40
43
|
onClick={this.toggleList}>
|
@@ -44,7 +47,6 @@ export default class InputWithSelect extends preact.Component {
|
|
44
47
|
|
45
48
|
{matching && <DropdownList
|
46
49
|
ref={this.setListRef}
|
47
|
-
app={app}
|
48
50
|
items={matching}
|
49
51
|
toSelect={this.onSelected}
|
50
52
|
toClose={this.closeList}
|
@@ -72,11 +74,18 @@ export default class InputWithSelect extends preact.Component {
|
|
72
74
|
|
73
75
|
onKeyDown (e) {
|
74
76
|
if (this.list && this.list.onKeyDown(e)) return;
|
77
|
+
if (e.keyCode === 13 && this.props.enterable) {
|
78
|
+
this.onSelected(e.target.value.trim());
|
79
|
+
return;
|
80
|
+
}
|
81
|
+
this.onTextChange(e);
|
82
|
+
}
|
75
83
|
|
84
|
+
onTextChange (e) {
|
76
85
|
const text = e.target.value.trim();
|
77
86
|
let matching = null;
|
78
87
|
if ((text && text !==this.state.text) || e.keyCode === 40) {
|
79
|
-
matching =
|
88
|
+
matching = this.filtered_items(text);
|
80
89
|
}
|
81
90
|
this.setState({matching, text});
|
82
91
|
}
|
@@ -86,7 +95,7 @@ export default class InputWithSelect extends preact.Component {
|
|
86
95
|
this.closeList();
|
87
96
|
|
88
97
|
} else {
|
89
|
-
this.setState({matching: this.
|
98
|
+
this.setState({matching: this.filtered_items()});
|
90
99
|
}
|
91
100
|
}
|
92
101
|
|
@@ -99,6 +108,14 @@ export default class InputWithSelect extends preact.Component {
|
|
99
108
|
this.props.onSelect(value);
|
100
109
|
}
|
101
110
|
|
111
|
+
filtered_items (text) {
|
112
|
+
let items = this.props.all_items;
|
113
|
+
if (text) items = items.filter(value => value.startsWith(text));
|
114
|
+
if (this.props.nullable) items.unshift(null);
|
115
|
+
|
116
|
+
return items.length === 0 ? null : items;
|
117
|
+
}
|
118
|
+
|
102
119
|
}
|
103
120
|
|
104
121
|
class DropdownList extends preact.Component {
|
@@ -108,14 +125,14 @@ class DropdownList extends preact.Component {
|
|
108
125
|
this.state = {selected: -1};
|
109
126
|
}
|
110
127
|
|
111
|
-
render ({
|
128
|
+
render ({items, toSelect}, {selected}) {
|
112
129
|
return <div
|
113
130
|
className="_dropdown">
|
114
131
|
<ul>
|
115
132
|
{items.map((name, i) => <li
|
116
133
|
className={classnames({'selected': selected === i})}
|
117
134
|
onClick={() => toSelect(name)}>
|
118
|
-
{name}
|
135
|
+
{name === null ? '\u00A0' : name}
|
119
136
|
</li>)}
|
120
137
|
</ul>
|
121
138
|
</div>;
|
@@ -135,8 +152,10 @@ class DropdownList extends preact.Component {
|
|
135
152
|
|
136
153
|
case 13: // enter
|
137
154
|
const selected = this.state.selected;
|
138
|
-
if (selected !== -1)
|
139
|
-
|
155
|
+
if (selected !== -1) {
|
156
|
+
this.props.toSelect(this.props.items[selected]);
|
157
|
+
return true;
|
158
|
+
}
|
140
159
|
break;
|
141
160
|
|
142
161
|
case 27: // escape
|
@@ -155,10 +174,4 @@ class DropdownList extends preact.Component {
|
|
155
174
|
if (selected >= this.props.items.length) selected = this.props.items.length -1;
|
156
175
|
this.setState({selected});
|
157
176
|
}
|
158
|
-
}
|
159
|
-
|
160
|
-
function filter_items (all, input_text) {
|
161
|
-
let items = all.filter(value => value.startsWith(input_text));
|
162
|
-
if (items.length === 0) items = null;
|
163
|
-
return items;
|
164
177
|
}
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import preact from 'preact';
|
2
|
+
|
3
|
+
export default class NestedListRow extends preact.Component {
|
4
|
+
|
5
|
+
constructor (props) {
|
6
|
+
super(props);
|
7
|
+
|
8
|
+
this.state = {
|
9
|
+
unrolled: !!props.defaultUnrolled
|
10
|
+
};
|
11
|
+
|
12
|
+
this.toggleRollOut = this.toggleRollOut.bind(this);
|
13
|
+
}
|
14
|
+
|
15
|
+
render ({app, content, nestedRows}, {unrolled}) {
|
16
|
+
return <li>
|
17
|
+
<div onClick={this.toggleRollOut} className="nested-list-content">
|
18
|
+
{nestedRows && <span className="icon">
|
19
|
+
{unrolled ? '\u25B6' : '\u25BC'}
|
20
|
+
</span>
|
21
|
+
}
|
22
|
+
<span className="content">{content}</span>
|
23
|
+
</div>
|
24
|
+
{unrolled && nestedRows && <ul className="nested-list">{
|
25
|
+
nestedRows.map(row =>
|
26
|
+
<NestedListRow
|
27
|
+
app={app}
|
28
|
+
content={row.content}
|
29
|
+
nestedRows={row.nested_rows}
|
30
|
+
defaultUnrolled={this.props.defaultUnrolled}
|
31
|
+
/>
|
32
|
+
)
|
33
|
+
}</ul>}
|
34
|
+
</li>;
|
35
|
+
}
|
36
|
+
|
37
|
+
componentWillReceiveProps ({defaultUnrolled}) {
|
38
|
+
if (defaultUnrolled !== this.props.defaultUnrolled) {
|
39
|
+
this.setState({unrolled: !!defaultUnrolled});
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
toggleRollOut () {
|
44
|
+
this.setState({unrolled: !this.state.unrolled});
|
45
|
+
}
|
46
|
+
|
47
|
+
}
|
48
|
+
|
@@ -6,19 +6,16 @@ export default class PrivilegeSelect extends preact.Component {
|
|
6
6
|
constructor (props) {
|
7
7
|
super(props);
|
8
8
|
|
9
|
-
this.state =
|
10
|
-
name: '',
|
11
|
-
level: ''
|
12
|
-
};
|
9
|
+
this.state = props.app.keyToPrivilege(props.defaultValue);
|
13
10
|
|
14
11
|
this.onNameSelected = this.onNameSelected.bind(this);
|
15
12
|
this.onLevelSelected = this.onLevelSelected.bind(this);
|
13
|
+
this.onClearSelection = this.onClearSelection.bind(this);
|
16
14
|
}
|
17
15
|
|
18
16
|
render ({app}, {name, level}) {
|
19
|
-
return <div className="controls-
|
17
|
+
return <div className="controls-group">
|
20
18
|
<InputWithSelect
|
21
|
-
app={app}
|
22
19
|
defaultText={name}
|
23
20
|
placeholder="name"
|
24
21
|
all_items={app.listPrivilegesNames()}
|
@@ -26,18 +23,34 @@ export default class PrivilegeSelect extends preact.Component {
|
|
26
23
|
/>
|
27
24
|
|
28
25
|
<InputWithSelect
|
29
|
-
app={app}
|
30
26
|
defaultText={level}
|
31
27
|
placeholder="level"
|
32
28
|
all_items={app.listPrivilegeLevels(name)}
|
33
29
|
onSelect={this.onLevelSelected}
|
34
30
|
/>
|
31
|
+
|
32
|
+
<button
|
33
|
+
type="button"
|
34
|
+
tabIndex="-1"
|
35
|
+
className="button"
|
36
|
+
onClick={this.onClearSelection}>
|
37
|
+
Clear
|
38
|
+
</button>
|
35
39
|
</div>;
|
36
40
|
}
|
37
41
|
|
42
|
+
componentWillReceiveProps ({defaultValue}) {
|
43
|
+
if (defaultValue !== this.props.defaultValue) {
|
44
|
+
this.setState(this.props.app.keyToPrivilege(defaultValue));
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
38
48
|
onNameSelected (name) {
|
39
|
-
|
40
|
-
|
49
|
+
const levels = this.props.app.admission.levels[name];
|
50
|
+
let level;
|
51
|
+
if (!levels) level = 'base';
|
52
|
+
else level = levels[levels.length - 1];
|
53
|
+
|
41
54
|
this.setState({name, level});
|
42
55
|
this.props.onChanged({name, level});
|
43
56
|
}
|
@@ -48,4 +61,10 @@ export default class PrivilegeSelect extends preact.Component {
|
|
48
61
|
this.props.onChanged({name, level});
|
49
62
|
}
|
50
63
|
|
64
|
+
onClearSelection () {
|
65
|
+
const selection = {name: '', level: ''};
|
66
|
+
this.setState(selection);
|
67
|
+
this.props.onChanged(selection);
|
68
|
+
}
|
69
|
+
|
51
70
|
}
|