admission 0.2.1 → 0.2.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2901974e7bb1412084baab7df6877d53bab7e5c9
4
- data.tar.gz: 6a63e26be4ad4e735cd63f02aa248477c4f7ab8f
3
+ metadata.gz: 62f774b23937957f5d4fd0dcabda6c136e393f71
4
+ data.tar.gz: 6d2efadac8eaccc3606a75d85807908ec00b88d1
5
5
  SHA512:
6
- metadata.gz: 202505bda524f5a51710f57878e12b7c3f7fe0e1e2336d2da180abace6bdd7c76e31d13adb11f808378cfaac13a838d0100ed21274fc54916ce05cd8126c7e99
7
- data.tar.gz: 9389efca659f448e58051bd6f6bbae9c68faf59ca0096144b903ffc3c0bd7ceab6ba9138046fa095bc6f00dbc130bc39bcfe1d9fa9edbc6a5bad334ef3421c8c
6
+ metadata.gz: c3f1a68687bf1f029c272e9451dadb3aad77b50b5fa35228595541fb3eb861f1da1f803b4eeb79f8fd84e33b73b530c1ec4d2393223b6986e430a29eca6b10a5
7
+ data.tar.gz: 4851ad4ad9dcb1f6e61538829749127c79ae16a413532e4da99e7dc599f86103b1001fc94618f9f276277edf779b8959520b6b2bf2b03c2a7e8cf642a06560e1
data/Gemfile CHANGED
@@ -2,9 +2,5 @@
2
2
  source "https://rubygems.org"
3
3
 
4
4
  gem 'rspec'
5
- # gem 'mutant-rspec'
6
5
  gem 'byebug'
7
-
8
- gem 'sinatra', '~> 2.0'
9
- gem 'sinatra-contrib'
10
- gem 'haml'
6
+ gem 'rack'
data/bin/build ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bash
2
+
3
+ cd visualisation
4
+ yarn run build
5
+
6
+ cd ..
7
+ git add -f visualisation/dist/app.js
8
+
9
+ rm *.gem
10
+ gem build admission.gemspec
@@ -120,6 +120,7 @@ class Admission::Arbitration
120
120
  index
121
121
  end
122
122
 
123
+ index_instance.values.each &:freeze
123
124
  index_instance.freeze
124
125
  end
125
126
 
@@ -109,6 +109,10 @@ class Admission::ResourceArbitration < Admission::Arbitration
109
109
  index
110
110
  end
111
111
 
112
+ index_instance.values.each do |h|
113
+ h.values.each &:freeze
114
+ h.freeze
115
+ end
112
116
  index_instance.freeze
113
117
  end
114
118
 
@@ -1,3 +1,3 @@
1
1
  module Admission
2
- VERSION = '0.2.1'
2
+ VERSION = '0.2.7'
3
3
  end
@@ -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 = props.app.debounce(this.onKeyDown.bind(this), 200);
17
- this.toggleList = props.app.debounce(this.toggleList.bind(this), 400, true);
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 ({app, placeholder}, {text, matching}) {
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="controls-select">
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 = filter_items(this.props.all_items, text);
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.props.all_items});
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 ({app, items, toSelect}, {selected}) {
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) this.props.toSelect(this.props.items[selected]);
139
- return true;
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-row">
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
- let level = this.state.level;
40
- if (level !== 'base') level = 'base';
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
  }