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 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
  }