icfs 0.3.0 → 0.3.1

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
  SHA256:
3
- metadata.gz: 6ba64d02837a0591aee2cab20c11795beffa59f3ec3dce2014ac7dd0b55e89e5
4
- data.tar.gz: 1b452562fa4df0cf870eb0ed5bbbf0f686b4ba78579e47e7ce075e84e18b1954
3
+ metadata.gz: a1a35a2e0403c01c2e6aa54db7014b1f41fc2769525afa66e2af066ae5b523fe
4
+ data.tar.gz: c9d77e1e72454fda263561f2640cbf14c0d24887038520868ddac3d273bc9c23
5
5
  SHA512:
6
- metadata.gz: '02909a341866abc4eb1eaa844317dd02b72ed55c59b0df518f3e54af06a024f6cb4ac2ea8f14a2b64b99e60bdd9dc2bc87ceee421ed591c498db1f0e5c4305c4'
7
- data.tar.gz: 562c4de6f8605b2ab7b414101642488a93b98fa94157ba4408ed640b6944f8cee33ecb3f710be87570760fd7d0fc6249c2b353163173f64ea53d52624b7b91fe
6
+ metadata.gz: 51dab31789c7fd5b1d3ce257b2bff25706727d555a6afab5d1a71e199eb8258a5ac13ab9b0471bdfcf8ffd3d8ba753f8d1d063c291ff2fc6b0759c6881ab779c
7
+ data.tar.gz: 00ac3104db418aa2f9bc07268299742605c71a49e572e677fc2572e506a079dd04b6d752ea0ea59537eef9254d11a83251da5667e05a66a6fb04040d55eedd77
@@ -0,0 +1,504 @@
1
+ /*************************************************************************
2
+ #
3
+ # Investigative Case File System
4
+ #
5
+ # Copyright 2019 by Graham A. Field
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License version 3.
9
+ #
10
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
11
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
+ #
13
+ **************************************************************************/
14
+
15
+ /*************************************************************************
16
+ General
17
+ */
18
+
19
+
20
+ html {
21
+ background-color: #212121;
22
+ }
23
+
24
+ div {
25
+ color: #ffffff;
26
+ background-color: #212121;
27
+ }
28
+
29
+ a {
30
+ color: #ffffff;
31
+ text-decoration: none;
32
+ }
33
+
34
+ a:hover {
35
+ text-decoration: underline;
36
+ }
37
+
38
+
39
+ input {
40
+ border: 1px solid black;
41
+ border-radius: 3px;
42
+ font-family: serif;
43
+ font-size: 12pt;
44
+ margin: 0.1em;
45
+ padding: 0.2em;
46
+ }
47
+
48
+ textarea {
49
+ border: 1px solid black;
50
+ border-radius: 3px;
51
+ font-family: serif;
52
+ font-size: 12pt;
53
+ margin: 0.1em;
54
+ padding: 0.2em;
55
+ }
56
+
57
+
58
+ /*************************************************************************
59
+ Navbar
60
+ */
61
+
62
+ div.nav {
63
+ border-width: 3px;
64
+ border-style: none none solid none;
65
+ border-color: green;
66
+ display: flex;
67
+ }
68
+
69
+ div.nav-icfs {
70
+ color: green;
71
+ width: 3em;
72
+ }
73
+
74
+ div.nav-icfs a {
75
+ color: green;
76
+ }
77
+
78
+ div.nav-case {
79
+ color: blue;
80
+ width: 15em;
81
+ }
82
+
83
+ div.nav-case a {
84
+ color: blue;
85
+ }
86
+
87
+ div.nav-tab {
88
+ text-align: center;
89
+ border: 1px;
90
+ width: 7em;
91
+ border-style: solid solid none solid;
92
+ border-radius: 5px 5px 0 0;
93
+ }
94
+
95
+
96
+ /*************************************************************************
97
+ Sidebar layout
98
+ */
99
+
100
+ div.sbar {
101
+ border-bottom: thin solid;
102
+ display: flex;
103
+ align-content: stretch;
104
+ }
105
+
106
+ div.sbar-main {
107
+ flex: 1 0 0;
108
+ vertical-align: top;
109
+ }
110
+
111
+ div.sbar-side {
112
+ flex: 0 0 20em;
113
+ border-right: thin solid;
114
+ vertical-align: top;
115
+ }
116
+
117
+ div.sbar-side-head {
118
+ text-align: center;
119
+ border-bottom: thin solid;
120
+ color: rgba(0, 0, 255, 89);
121
+ }
122
+
123
+ div.sbar-main-head {
124
+ font-size: 120%;
125
+ padding: 0.25em;
126
+ }
127
+
128
+ div.sbar-main-sub {
129
+ font-weight: bold;
130
+ font-size: 75%;
131
+ }
132
+
133
+ pre.sbar-main-content {
134
+ font-family: serif;
135
+ font-size: 12pt;
136
+ border-top: thin solid;
137
+ border-bottom: thin solid;
138
+ margin: 0px;
139
+ padding: 0.25em;
140
+ white-space: pre-wrap;
141
+ }
142
+
143
+
144
+ /*************************************************************************
145
+ Sections
146
+ */
147
+
148
+ div.sect {
149
+ padding: 0.25em;
150
+ }
151
+
152
+ div.sect-head {
153
+ display: flex;
154
+ flex-direction: row;
155
+ justify-content: space-between;
156
+ padding: 0.2em;
157
+ background: #446600;
158
+ }
159
+
160
+ div.sect-head div {
161
+ background: #446600;
162
+ }
163
+
164
+ div.sect-main {
165
+ display: flex;
166
+ flex-direction: row;
167
+ justify-content: space-between;
168
+ padding: 0.2em;
169
+ background: #99bbff;
170
+ }
171
+
172
+ div.sect-main div {
173
+ background: #99bbff;
174
+ }
175
+
176
+
177
+ div.sect-label {
178
+ flex: 0 1 auto;
179
+ }
180
+
181
+
182
+ div.sect-fill {
183
+ flex: 1 1 auto;
184
+ }
185
+
186
+
187
+ div.sect-head-right {
188
+ flex: 0 1 auto;
189
+ }
190
+
191
+
192
+ /*************************************************************************
193
+ List
194
+ */
195
+
196
+ div.list-head {
197
+ display: flex;
198
+ flex-direction: row;
199
+ border-bottom: solid;
200
+ border-width: 1px;
201
+ font-style: italic;
202
+ }
203
+
204
+
205
+ div.list-row {
206
+ display: flex;
207
+ flex-direction: row;
208
+ flex-wrap: wrap;
209
+ border-bottom: dotted;
210
+ border-width: 1px;
211
+ align-items: flex-start;
212
+ }
213
+
214
+
215
+ div.list-int {
216
+ width: 4em;
217
+ padding: 0 0.15em 0 0.15em;
218
+ }
219
+
220
+
221
+ div.list-int-sm {
222
+ width: 2em;
223
+ padding: 0 0.15em 0 0.15em;
224
+ }
225
+
226
+
227
+ div.list-perm {
228
+ width: 15em;
229
+ padding: 0 0.15em 0 0.15em;
230
+ }
231
+
232
+
233
+ div.list-usergrp {
234
+ width: 15em;
235
+ padding: 0 0.15em 0 0.15em;
236
+ }
237
+
238
+
239
+ div.list-label {
240
+ width: 7em;
241
+ padding: 0 0.15em 0 0.15em;
242
+ text-align: right;
243
+ }
244
+
245
+
246
+ div.list-caseid {
247
+ width: 10em;
248
+ padding: 0 0.15em 0 0.15em;
249
+ }
250
+
251
+
252
+ div.list-text-s {
253
+ width: 8em;
254
+ padding: 0 0.15em 0 0.15em;
255
+ }
256
+
257
+
258
+ div.list-text-m {
259
+ width: 12em;
260
+ padding: 0 0.15em 0 0.15em;
261
+ }
262
+
263
+
264
+ div.list-time {
265
+ width: 15em;
266
+ padding: 0 0.15em 0 0.15em;
267
+ }
268
+
269
+
270
+ div.list-tag {
271
+ width: 12em;
272
+ padding: 0 0.15em 0 0.15em;
273
+ }
274
+
275
+ div.list-hash {
276
+ font-family: monospace;
277
+ font-size: 12pt;
278
+ padding: 0 0.15em 0 0.15em;
279
+ }
280
+
281
+ div.list-stat {
282
+ width: 12em;
283
+ padding: 0 0.15em 0 0.15em;
284
+ }
285
+
286
+ div.list-snip {
287
+ flex: 0 1 100%;
288
+ padding: 0.1em;
289
+ margin: 0.1em 1em 0.1em 1em;
290
+ background: #f0f0f0;
291
+ }
292
+
293
+ /*************************************************************************
294
+ Tasks
295
+ */
296
+
297
+ div.task {
298
+ border-bottom: thin solid;
299
+ }
300
+
301
+
302
+ div.task-ed {
303
+ background: #99b3ff;
304
+ }
305
+
306
+
307
+ div.task-ro {
308
+ background: #ff6666;
309
+ }
310
+
311
+
312
+ /*************************************************************************
313
+ Forms
314
+ */
315
+
316
+ div.form-row {
317
+ min-height: 2em;
318
+ display: flex;
319
+ flex-direction: row;
320
+ align-items: baseline;
321
+ }
322
+
323
+
324
+ input.form-index {
325
+ width: 20em;
326
+ }
327
+
328
+
329
+ input.form-stat {
330
+ width: 15em;
331
+ }
332
+
333
+
334
+ input.form-usergrp {
335
+ width: 10em;
336
+ }
337
+
338
+
339
+ input.form-caseid {
340
+ width: 12em;
341
+ }
342
+
343
+
344
+ input.form-sort {
345
+ width: 10em;
346
+ }
347
+
348
+
349
+ input.form-content {
350
+ width: 50em;
351
+ }
352
+
353
+
354
+ input.form-title {
355
+ width: 40em;
356
+ }
357
+
358
+
359
+ input.form-file-name {
360
+ width: 30em;
361
+ }
362
+
363
+
364
+ input.form-file-upl {
365
+ }
366
+
367
+
368
+ input.form-float {
369
+ width: 5em;
370
+ }
371
+
372
+
373
+ input.form-check {
374
+ }
375
+
376
+
377
+ input.form-time {
378
+ width: 15em;
379
+ }
380
+
381
+
382
+ input.form-perm {
383
+ width: 15em;
384
+ }
385
+
386
+
387
+ input.form-int {
388
+ width: 5em;
389
+ }
390
+
391
+
392
+ input.form-tag {
393
+ width: 20em;
394
+ }
395
+
396
+
397
+ input.form-boolean {
398
+ width: 5em;
399
+ }
400
+
401
+
402
+ textarea.form-content {
403
+ width: 50em;
404
+ height: 20em;
405
+ }
406
+
407
+
408
+ /*************************************************************************
409
+ Other divs
410
+ */
411
+
412
+ div.desc {
413
+ padding: 0.25em;
414
+ background: #663d00;
415
+ margin-bottom: 1em;
416
+ }
417
+
418
+ div.desc div {
419
+ background: #663d00;
420
+ }
421
+
422
+ div.desc-head {
423
+ font-size: 120%;
424
+ text-align: center;
425
+ }
426
+
427
+ span.desc-query {
428
+ text-decoration: underline;
429
+
430
+ }
431
+
432
+ div.query-form {
433
+ border-bottom: solid;
434
+ border-width: 1px;
435
+ padding: 0.5em;
436
+ }
437
+
438
+
439
+
440
+ div.hidden {
441
+ display: none;
442
+ }
443
+
444
+ div.invisible {
445
+ visibility: hidden;
446
+ }
447
+
448
+
449
+ /*************************************************************************
450
+ Tooltips
451
+ */
452
+
453
+ div.tip {
454
+ display: inline-block;
455
+ }
456
+
457
+ div.tip div.tip-disp {
458
+ border: thin solid;
459
+ border-radius: 50%;
460
+ font-size: 60%;
461
+ text-align: center;
462
+ width: 1.1em;
463
+ height: 1.1em;
464
+ margin: 0 0.1em;
465
+ }
466
+
467
+
468
+ div.tip div.tip-disp::before {
469
+ content: "?";
470
+ }
471
+
472
+
473
+ /* status bar tooltips */
474
+ div.tip div.tip-info {
475
+ visibility: hidden;
476
+ background-color: black;
477
+ color: #fff;
478
+ position: fixed;
479
+ padding: 0.5em;
480
+ bottom: 0;
481
+ left: 0;
482
+ width: 100vw;
483
+ }
484
+
485
+
486
+ /* floating tooltips
487
+ div.tip div.tip-info {
488
+ visibility: hidden;
489
+ background-color: black;
490
+ color: #fff;
491
+ border-radius: 0.5em;
492
+ padding: 0.5em;
493
+ margin: 0.25em;
494
+ width: 25em;
495
+ margin-left: 0em;
496
+ position: absolute;
497
+ z-index: 1;
498
+ }
499
+ */
500
+
501
+
502
+ div.tip:hover div.tip-info {
503
+ visibility: visible;
504
+ }
@@ -59,11 +59,12 @@ def get_base
59
59
  current: 'current',
60
60
  }.freeze
61
61
 
62
- # default config
63
- defaults = {
64
- 'tz' => '-04:00',
65
- 'rel_time' => true,
66
- }
62
+ # config setup
63
+ setup = [
64
+ ['tz', ICFS::Config::SetupTimezone].freeze,
65
+ ['rel_time', ICFS::Config::SetupRelTime].freeze,
66
+ ['css', ICFS::Config::SetupCss].freeze,
67
+ ].freeze
67
68
 
68
69
  # base objects
69
70
  cache = ICFS::CacheElastic.new(map, es)
@@ -74,7 +75,7 @@ def get_base
74
75
  expires: 60, # one minute cache for testing
75
76
  log: log,
76
77
  })
77
- config_base = ICFS::ConfigS3.new(defaults, s3, 'icfs', 'config/')
78
+ config_base = ICFS::ConfigS3.new(setup, s3, 'icfs', 'config/')
78
79
  config = ICFS::ConfigRedis.new(redis, config_base, {
79
80
  prefix: 'config/',
80
81
  expires: 60, # debug, only cache for one minute
@@ -20,7 +20,7 @@ require_relative '../../lib/icfs/demo/static'
20
20
  base = get_base()
21
21
  api = base[:api]
22
22
 
23
- web = ICFS::Web::Client.new('/static/icfs.css', '/static/icfs.js')
23
+ web = ICFS::Web::Client.new('/static/icfs.js')
24
24
 
25
25
  # static files
26
26
  static = {
@@ -28,6 +28,10 @@ static = {
28
28
  'path' => '/icfs/data/icfs.css',
29
29
  'mime' => 'text/css; charset=utf-8'
30
30
  },
31
+ '/static/icfs-dark.css' => {
32
+ 'path' => '/icfs/data/icfs-dark.css',
33
+ 'mime' => 'text/css; charset=utf-8'
34
+ },
31
35
  '/static/icfs.js' => {
32
36
  'path' => '/icfs/data/icfs.js',
33
37
  'mime' => 'application/javascript; charset=utf-8'
@@ -22,7 +22,7 @@ require_relative 'icfs/validate'
22
22
  module ICFS
23
23
 
24
24
  # version: major, minor, patch
25
- Version = [0, 3, 0].freeze
25
+ Version = [0, 3, 1].freeze
26
26
 
27
27
  # version pre-release
28
28
  VersionPre = nil
@@ -811,7 +811,7 @@ class CacheElastic < Cache
811
811
  # build the query
812
812
  filter = [
813
813
  _query_term('caseid', query[:caseid]),
814
- _query_times('times', query[:after], query[:before]),
814
+ _query_times('time', query[:after], query[:before]),
815
815
  _query_term('user', query[:user]),
816
816
  _query_exists('case.set', query[:case_edit]),
817
817
  _query_term('entry.num', query[:entry]),
@@ -11,7 +11,8 @@
11
11
 
12
12
  # frozen_string_literal: true
13
13
 
14
- #
14
+ require 'set'
15
+
15
16
  module ICFS
16
17
 
17
18
  ##########################################################################
@@ -21,32 +22,96 @@ module ICFS
21
22
  #
22
23
  class Config
23
24
 
24
- ###############################################
25
- # Valid config options
26
- ValConfig = {
27
- method: :hash,
28
- optional: {
29
- 'tz' => {
30
- method: :string,
31
- valid: /[+\-](0[0-9]|1[0-2]):[0-5][0-9]/.freeze,
32
- whitelist: true,
33
- }.freeze,
34
- 'rel_time' => Validate::IsBoolean,
35
- }.freeze
25
+ # Default setup for 'tz'
26
+ SetupTimezone = {
27
+ name: 'Timezone',
28
+ default: '+00:00',
29
+ validate: {
30
+ method: :string,
31
+ valid: /[+\-](0[0-9]|1[0-2]):[0-5][0-9]/.freeze,
32
+ whitelist: true,
33
+ }.freeze,
34
+ label: 'cfg-tz',
35
+ input: [:text, 'form-tz'].freeze,
36
+ parse: :text,
37
+ tip: 'Timezone to display date/times, format as +/-HH:MM.',
38
+ display: 'list-text-m',
39
+ }.freeze
40
+
41
+
42
+ # default setup for 'rel_time'
43
+ SetupRelTime = {
44
+ name: 'Rel. Time',
45
+ default: true,
46
+ validate: Validate::IsBoolean,
47
+ label: 'cfg-reltime',
48
+ input: [:boolean].freeze,
49
+ parse: :boolean,
50
+ tip: 'Display relative times e.g. 3 days ago.',
51
+ display: 'list-text-s',
36
52
  }.freeze
37
53
 
54
+
55
+ # default setup for 'css'
56
+ SetupCss = {
57
+ name: 'Style',
58
+ default: '/static/icfs.css',
59
+ validate: {
60
+ method: :string,
61
+ allowed: Set[
62
+ '/static/icfs.css',
63
+ '/static/icfs-dark.css'
64
+ ].freeze,
65
+ whitelist: true,
66
+ }.freeze,
67
+ label: 'cfg-css',
68
+ input: [
69
+ :select,
70
+ 'form-css',
71
+ 'cfg-css',
72
+ [
73
+ ['/static/icfs.css', 'Light'].freeze,
74
+ ['/static/icfs-dark.css', 'Dark'].freeze,
75
+ ].freeze
76
+ ].freeze,
77
+ parse: :text,
78
+ tip: 'Display settings for web interface.',
79
+ display: 'list-text-l',
80
+ }.freeze
81
+
82
+
38
83
  ###############################################
39
84
  # New instance
40
85
  #
41
- # @param defaults [Hash] The default options
86
+ # @param setup [Array<Array>] The setup array
87
+ #
88
+ # Each item is a \[key, hash\], each hash should contain:
89
+ # - :name The name of the config setting
90
+ # - :default The default value
91
+ # - :validate A Validator
92
+ # - :label The HTML label
93
+ # - :input Array used by {Web::Client#_form_config}
94
+ # - :parse
95
+ # - :tip Text of the tup
96
+ # - :display Array to pass to {Web::Client#_div_config}
42
97
  #
43
- def initialize(defaults={})
98
+ def initialize(setup)
44
99
  @data = {}
45
100
  @unam = nil
46
- @defaults = defaults
101
+ @order = []
102
+ @setup = {}
103
+ setup.each do |ary|
104
+ @order << ary[0]
105
+ @setup[ary[0]] = ary[1]
106
+ end
47
107
  end # def initialize()
48
108
 
49
109
 
110
+ ###############################################
111
+ # Clear data
112
+ def clear; @data = {}; end
113
+
114
+
50
115
  ###############################################
51
116
  # The configuration values hash
52
117
  #
@@ -59,13 +124,25 @@ class Config
59
124
  attr_reader :defaults
60
125
 
61
126
 
127
+ ###############################################
128
+ # Get the option for this key
129
+ #
130
+ def _opt(key)
131
+ opt = @setup[key]
132
+ raise(ArgumentError, 'Invalid config option') unless opt
133
+ return opt
134
+ end # def _opt()
135
+ private :_opt
136
+
137
+
62
138
  ###############################################
63
139
  # Get a value
64
140
  #
65
141
  # @param key [String] The name of the config setting
66
142
  #
67
143
  def get(key)
68
- @data.key?(key) ? @data[key] : @defaults[key]
144
+ opt = _opt(key)
145
+ @data.key?(key) ? @data[key] : opt[:default]
69
146
  end
70
147
 
71
148
 
@@ -76,10 +153,45 @@ class Config
76
153
  # @param val [Object] The value of the config setting
77
154
  #
78
155
  def set(key, val)
156
+ opt = _opt(key)
157
+ Items.validate(val, opt[:name], opt[:validate])
79
158
  @data[key] = val
80
159
  end
81
160
 
82
161
 
162
+ ###############################################
163
+ # Get the default value
164
+ #
165
+ # @param key [String] The name of the config setting
166
+ def default(key)
167
+ opt = _opt(key)
168
+ opt[:default]
169
+ end
170
+
171
+
172
+ ###############################################
173
+ # Is the value set?
174
+ #
175
+ # @param key [String] The name of the config setting
176
+ #
177
+ def set?(key); @data.key?(key); end
178
+
179
+
180
+ ###############################################
181
+ # Get setup
182
+ # @param key [String] the specific key to get
183
+ # @return [Hash, Array] the setup for the key or
184
+ # an array of \[key, setup\]
185
+ #
186
+ def setup(key=nil)
187
+ return _opt(key) if key
188
+
189
+ return @order.map do |key|
190
+ [key, @setup[key]]
191
+ end
192
+ end # def setup
193
+
194
+
83
195
  ###############################################
84
196
  # Where to store objects
85
197
  #
@@ -89,6 +201,43 @@ class Config
89
201
  private :_key
90
202
 
91
203
 
204
+ ###############################################
205
+ # Parse JSON encoded config settings
206
+ def _parse(json)
207
+
208
+ if json.nil?
209
+ raise(Error::NotFound, 'Config not found')
210
+ end
211
+
212
+ begin
213
+ itm = JSON.parse(json)
214
+ rescue
215
+ raise(Error::Value, 'JSON parsing failed')
216
+ end
217
+
218
+ errs = {}
219
+ itm.each do |key, val|
220
+ opt = @setup[key]
221
+ raise(Error::Value, 'Unsupported config option %s' % key) if !opt
222
+ err = Validate.check(val, opt[:validate])
223
+ errs[key] = err if err
224
+ end
225
+ unless errs.empty?
226
+ raise(Error::Value, 'Config has bad settings: %s' % errs.inspect)
227
+ end
228
+
229
+ @data = itm
230
+ end
231
+ private :_parse
232
+
233
+
234
+ ###############################################
235
+ # Generate a JSON encoded string
236
+ def _generate()
237
+ JSON.pretty_generate(@data)
238
+ end
239
+
240
+
92
241
  ###############################################
93
242
  # Load a user configuration
94
243
  #
@@ -31,7 +31,7 @@ class ConfigRedis < Config
31
31
  # @option opts [Integer] :expires Expiration time in seconds
32
32
  #
33
33
  def initialize(redis, base, opts={})
34
- super(base.defaults)
34
+ super(base.setup)
35
35
  @redis = redis
36
36
  @base = base
37
37
  @pre = opts[:prefix] || ''
@@ -50,7 +50,7 @@ class ConfigRedis < Config
50
50
  # try cache
51
51
  json = @redis.get(key)
52
52
  if json
53
- @data = Items.parse(json, 'Config values', Config::ValConfig)
53
+ _parse(json)
54
54
  return true
55
55
  end
56
56
 
@@ -66,7 +66,7 @@ class ConfigRedis < Config
66
66
  #
67
67
  def save()
68
68
  raise(RuntimeError, 'Save requires a user name') if !@unam
69
- json = Items.generate(@data, 'Config values', Config::ValConfig)
69
+ json = _generate()
70
70
  @redis.del(_key(@unam))
71
71
  @base.data = @data
72
72
  @base.save
@@ -24,13 +24,13 @@ class ConfigS3 < Config
24
24
  ###############################################
25
25
  # New instance
26
26
  #
27
- # @param defaults [Hash] The default options
27
+ # @param setup [Array] The setup
28
28
  # @param s3 [Aws::S3::Client] the configured S3 client
29
29
  # @param bucket [String] The bucket name
30
30
  # @param prefix [String] Prefix to use for object keys
31
31
  #
32
- def initialize(defaults, s3, bucket, prefix=nil)
33
- super(defaults)
32
+ def initialize(setup, s3, bucket, prefix=nil)
33
+ super(setup)
34
34
  @s3 = s3
35
35
  @bck = bucket
36
36
  @pre = prefix || ''
@@ -44,7 +44,7 @@ class ConfigS3 < Config
44
44
  Items.validate(unam, 'User/Role/Group name', Items::FieldUsergrp)
45
45
  @unam = unam.dup
46
46
  json = @s3.get_object( bucket: @bck, key: _key(unam) ).body.read
47
- @data = Items.parse(json, 'Config values', Config::ValConfig)
47
+ _parse(json)
48
48
  return true
49
49
  rescue
50
50
  @data = {}
@@ -57,7 +57,7 @@ class ConfigS3 < Config
57
57
  #
58
58
  def save()
59
59
  raise(RuntimeError, 'Save requires a user name') if !@unam
60
- json = Items.generate(@data, 'Config values', Config::ValConfig)
60
+ json = _generate()
61
61
  @s3.put_object( bucket: @bck, key: _key(@unam), body: json )
62
62
  end # def save()
63
63
 
@@ -28,11 +28,9 @@ class Client
28
28
  ###############################################
29
29
  # New instance
30
30
  #
31
- # @param css [String] the URL for the stylesheet
32
31
  # @param js [String] the URL for the javascript
33
32
  #
34
- def initialize(css, js)
35
- @css = css.freeze
33
+ def initialize(js)
36
34
  @js = js.freeze
37
35
  end
38
36
 
@@ -226,8 +224,10 @@ class Client
226
224
  _verb_get(env)
227
225
  body = [
228
226
  _div_nav(env),
229
- _div_desc('Info', ''),
230
- _div_info(env)
227
+ _div_desc('User Info', ''),
228
+ _div_info(env),
229
+ _div_desc('Config Info', ''),
230
+ _div_config(env),
231
231
  ].join('')
232
232
  return _resp_success(env, body)
233
233
  end # def _call_info()
@@ -547,7 +547,7 @@ class Client
547
547
  parts = [
548
548
  _form_create(env),
549
549
  _form_case(env, tpl),
550
- _form_entry(env, tid, nil),
550
+ _form_entry(env, tid, nil, {no_index: true}),
551
551
  ]
552
552
  body = [
553
553
  _div_nav(env),
@@ -569,7 +569,7 @@ class Client
569
569
  cse['template'] = (para['create_tmpl'] == 'true') ? true : false
570
570
 
571
571
  # process entry
572
- ent = _post_entry(env, para)
572
+ ent = _post_entry(env, para, {no_index: true})
573
573
  Items.validate(tid, 'Template ID', Items::FieldCaseid)
574
574
  ent['caseid'] = cid
575
575
 
@@ -828,7 +828,9 @@ class Client
828
828
  # post the form
829
829
  elsif env['REQUEST_METHOD'] == 'POST'
830
830
  para = _util_post(env)
831
- _post_config(env, para).each{|key, val| cfg.set(key,val) }
831
+ vals = _post_config(env, para)
832
+ cfg.clear
833
+ vals.each{|key, val| cfg.set(key,val) }
832
834
  cfg.save
833
835
  api.user_flush()
834
836
 
@@ -836,7 +838,7 @@ class Client
836
838
  body = [
837
839
  _div_nav(env),
838
840
  _div_desc('Edit Configuration', 'Settings saved'),
839
- _div_info(env),
841
+ _div_config(env),
840
842
  ].join('')
841
843
  return _resp_success(env, body)
842
844
  end
@@ -1163,6 +1165,39 @@ class Client
1163
1165
  <div class="message">%s
1164
1166
  </div>'
1165
1167
 
1168
+ ###############################################
1169
+ # Config div
1170
+ #
1171
+ def _div_config(env)
1172
+ api = env['icfs']
1173
+ cfg = api.config
1174
+
1175
+ items = cfg.setup.map do |key, opt|
1176
+ DivConfigItem % [
1177
+ opt[:name],
1178
+ opt[:display],
1179
+ _util_config(cfg, key, cfg.get(key))
1180
+ ]
1181
+ end
1182
+ return DivConfig % items.join('')
1183
+ end # def _div_config
1184
+
1185
+
1186
+ # The config settings div
1187
+ DivConfig = '
1188
+ <div class="config">
1189
+ <div class="list">%s
1190
+ </div>
1191
+ </div>'
1192
+
1193
+
1194
+ # each config setting
1195
+ DivConfigItem = '
1196
+ <div class="list-row">
1197
+ <div class="list-label">%s:</div>
1198
+ <div class="%s">%s</div>
1199
+ </div>'
1200
+
1166
1201
 
1167
1202
  ###############################################
1168
1203
  # Info div
@@ -1180,7 +1215,6 @@ class Client
1180
1215
  gstats = api.gstats.map{|st| DivInfoList % Rack::Utils.escape_html(st)}
1181
1216
 
1182
1217
  return DivInfo % [
1183
- Rack::Utils.escape_html(tz),
1184
1218
  Rack::Utils.escape_html(api.user),
1185
1219
  roles.join(''),
1186
1220
  grps.join(''),
@@ -1194,10 +1228,6 @@ class Client
1194
1228
  DivInfo = '
1195
1229
  <div class="info">
1196
1230
  <div class="list">
1197
- <div class="list-row">
1198
- <div class="list-label">Timezone:</div>
1199
- <div class="list-text-s">%s</div>
1200
- </div>
1201
1231
  <div class="list-row">
1202
1232
  <div class="list-label">User:</div>
1203
1233
  <div class="list-text-m">%s</div>
@@ -3065,6 +3095,8 @@ class Client
3065
3095
  # @param cid [String] caseid
3066
3096
  # @param ent [Hash] the Entry
3067
3097
  # @param opts [Hash] options
3098
+ # @option opts [Integer] The action num
3099
+ # @option opts [Boolean] :no_index Does not display indexe
3068
3100
  #
3069
3101
  #
3070
3102
  def _form_entry(env, cid, ent=nil, opts={})
@@ -3131,7 +3163,9 @@ class Client
3131
3163
 
3132
3164
  # indexes
3133
3165
  index_cnt = 0
3134
- if ent && ent['index']
3166
+ if opts[:no_index]
3167
+ index = ''
3168
+ elsif ent && ent['index']
3135
3169
  idx_list = ent['index'].map do |xnum|
3136
3170
  index_cnt += 1
3137
3171
  idx = api.index_read(cid, xnum, 0)
@@ -3200,6 +3234,17 @@ class Client
3200
3234
  perms = ''
3201
3235
  end
3202
3236
 
3237
+ # do index
3238
+ if opts[:no_index]
3239
+ index = ''
3240
+ else
3241
+ index = FormEntryIndex % [
3242
+ env['SCRIPT_NAME'],
3243
+ Rack::Utils.escape(cid),
3244
+ index_cnt, index,
3245
+ ]
3246
+ end
3247
+
3203
3248
  return FormEntry % [
3204
3249
  opts[:enable] ? 'true' : 'false',
3205
3250
  ent ? ent['entry'] : 0,
@@ -3209,9 +3254,7 @@ class Client
3209
3254
  title, time, content,
3210
3255
  tags_cnt, tags,
3211
3256
  files_cnt, files,
3212
- env['SCRIPT_NAME'],
3213
- Rack::Utils.escape(cid),
3214
- index_cnt, index,
3257
+ index,
3215
3258
  stats_sel, stats_cnt, stats,
3216
3259
  perms_sel, perms_cnt, perms
3217
3260
  ]
@@ -3300,28 +3343,7 @@ class Client
3300
3343
  <input type="hidden" id="ent-file-cnt" name="ent-file-cnt" value="%d">
3301
3344
  <div class="files-list" id="ent-file-list">%s
3302
3345
  </div>
3303
- </div>
3304
-
3305
- <div class="sect">
3306
- <div class="sect-head">
3307
- <div class="sect-label">Indexes</div>
3308
- <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3309
- Real-world factors that appear in a case multiple times.
3310
- </div></div>
3311
- <div class="sect-fill"> </div>
3312
- <div class="sect-right">
3313
- <input class="form-index" type="text" id="ent-idx-lu"
3314
- name="ent-idx-lu">
3315
- <button class="index-add" type="button"
3316
- onclick="entAddIndex()">?</button>
3317
- </div>
3318
- </div>
3319
- <input type="hidden" id="ent-idx-script" value="%s">
3320
- <input type="hidden" id="ent-idx-caseid" value="%s">
3321
- <input type="hidden" id="ent-idx-cnt" name="ent-idx-cnt" value="%d">
3322
- <div class="index-list" id="ent-idx-list">%s
3323
- </div>
3324
- </div>
3346
+ </div>%s
3325
3347
 
3326
3348
  <div class="sect">
3327
3349
  <div class="sect-head">
@@ -3362,6 +3384,31 @@ class Client
3362
3384
  </div>'
3363
3385
 
3364
3386
 
3387
+ #Entry form for indexes
3388
+ FormEntryIndex = '
3389
+
3390
+ <div class="sect">
3391
+ <div class="sect-head">
3392
+ <div class="sect-label">Indexes</div>
3393
+ <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3394
+ Real-world factors that appear in a case multiple times.
3395
+ </div></div>
3396
+ <div class="sect-fill"> </div>
3397
+ <div class="sect-right">
3398
+ <input class="form-index" type="text" id="ent-idx-lu"
3399
+ name="ent-idx-lu">
3400
+ <button class="index-add" type="button"
3401
+ onclick="entAddIndex()">?</button>
3402
+ </div>
3403
+ </div>
3404
+ <input type="hidden" id="ent-idx-script" value="%s">
3405
+ <input type="hidden" id="ent-idx-caseid" value="%s">
3406
+ <input type="hidden" id="ent-idx-cnt" name="ent-idx-cnt" value="%d">
3407
+ <div class="index-list" id="ent-idx-list">%s
3408
+ </div>
3409
+ </div>'
3410
+
3411
+
3365
3412
  # Entry form tag each
3366
3413
  FormEntryTagEach = '
3367
3414
  <div>
@@ -3816,12 +3863,61 @@ class Client
3816
3863
  #
3817
3864
  def _form_config(env)
3818
3865
  cfg = env['icfs'].config
3819
- tz = cfg.get('tz')
3820
- rel_time = cfg.get('rel_time') ? 'true' : 'false'
3821
- return FormConfig % [tz, rel_time]
3866
+
3867
+ items = cfg.setup.map do |key, opt|
3868
+ input = opt[:input]
3869
+ input_string = case input[0]
3870
+ when :text
3871
+ '<input class="%s" name="%s" type="text" value="%s">' % [
3872
+ input[1],
3873
+ opt[:label],
3874
+ cfg.set?(key) ? Rack::Utils.escape_html(cfg.get(key)) : ''
3875
+ ]
3876
+ when :boolean
3877
+ '<input class="form-boolean" name="%s" type="text" value="%s">' % [
3878
+ opt[:label],
3879
+ cfg.set?(key) ? (cfg.get(key) ? 'true' : 'false') : ''
3880
+ ]
3881
+ when :select
3882
+ items = [ '<option value=""></option>' ]
3883
+ cur = cfg.set?(key) ? cfg.get(key) : false
3884
+ input[3].each do |it|
3885
+ sel = (it[0] == cur) ? ' selected' : ''
3886
+ items << '<option value="%s"%s>%s</option>' % [it[0], sel, it[1]]
3887
+ end
3888
+ '<select class="%s" name="%s">%s</select>' % [
3889
+ input[1],
3890
+ input[2],
3891
+ items.join('')
3892
+ ]
3893
+ else
3894
+ raise(ArgumentError, 'Invalid Config setup')
3895
+ end
3896
+
3897
+ FormConfigItem % [
3898
+ opt[:name],
3899
+ input_string,
3900
+ opt[:tip],
3901
+ _util_config(cfg, key, cfg.default(key))
3902
+ ]
3903
+ end
3904
+
3905
+ return FormConfig % items.join('')
3822
3906
  end # def _form_config()
3823
3907
 
3824
3908
 
3909
+ # Item on the config form
3910
+ FormConfigItem = '
3911
+ <div class="form-row">
3912
+ <div class="list-label">%s:</div>
3913
+ %s
3914
+ <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3915
+ %s
3916
+ Default is: %s
3917
+ </div></div>
3918
+ </div>'.freeze
3919
+
3920
+
3825
3921
  # Config form
3826
3922
  FormConfig = '
3827
3923
  <div class="sect">
@@ -3830,22 +3926,8 @@ class Client
3830
3926
  <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3831
3927
  Configuration settings.
3832
3928
  </div></div>
3833
- <div class="sect-fill"> </div>
3834
- </div>
3835
- <div class="form-row">
3836
- <div class="list-label">Timezone:</div>
3837
- <input class="form-tz" name="cfg-tz" type="text" value="%s">
3838
- <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3839
- Timezone to display date/times, format as +/-HH:MM.
3840
- </div></div>
3841
- </div>
3842
- <div class="form-row">
3843
- <div class="list-label">Rel. Time:</div>
3844
- <input class="form-boolean" name="cfg-reltime" type="text" value="%s">
3845
- <div class="tip"><div class="tip-disp"></div><div class="tip-info">
3846
- Display relative times e.g. 3 days ago.
3847
- </div></div>
3848
- </div>
3929
+ <div class="sect-fill"></div>
3930
+ </div>%s
3849
3931
  </div>'
3850
3932
 
3851
3933
 
@@ -3938,7 +4020,7 @@ class Client
3938
4020
  ###############################################
3939
4021
  # Entry edit
3940
4022
  #
3941
- def _post_entry(env, para)
4023
+ def _post_entry(env, para, opts={})
3942
4024
  return nil unless para['ent-ena'] == 'true'
3943
4025
 
3944
4026
  api = env['icfs']
@@ -3975,15 +4057,17 @@ class Client
3975
4057
  ent['tags'] = tags.uniq.sort unless tags.empty?
3976
4058
 
3977
4059
  # indexes
3978
- index = []
3979
- icnt = para['ent-idx-cnt'].to_i
3980
- raise(Error::Interface, 'Too many indexes') if(icnt > 100)
3981
- icnt.times do |ix|
3982
- tx = 'ent-idx-%d' % (ix + 1)
3983
- xnum = para[tx].to_i
3984
- index << xnum unless xnum == 0
4060
+ unless opts[:no_index]
4061
+ index = []
4062
+ icnt = para['ent-idx-cnt'].to_i
4063
+ raise(Error::Interface, 'Too many indexes') if(icnt > 100)
4064
+ icnt.times do |ix|
4065
+ tx = 'ent-idx-%d' % (ix + 1)
4066
+ xnum = para[tx].to_i
4067
+ index << xnum unless xnum == 0
4068
+ end
4069
+ ent['index'] = index.uniq.sort unless index.empty?
3985
4070
  end
3986
- ent['index'] = index.uniq.sort unless index.empty?
3987
4071
 
3988
4072
  # perms
3989
4073
  perms = []
@@ -4161,11 +4245,25 @@ class Client
4161
4245
  # Config edit
4162
4246
  #
4163
4247
  def _post_config(env, para)
4164
- cfg = {
4165
- 'tz' => para['cfg-tz'],
4166
- 'rel_time' => (para['cfg-reltime'].downcase == 'true' ? true : false),
4167
- }
4168
- return cfg
4248
+ api = env['icfs']
4249
+ cfg = api.config
4250
+
4251
+ vals = {}
4252
+ cfg.setup.each do |key, opt|
4253
+ val = para[opt[:label]]
4254
+ next if val.nil? || val.empty?
4255
+
4256
+ case opt[:parse]
4257
+ when :text
4258
+ vals[key] = val
4259
+ when :boolean
4260
+ vals[key] = (val.downcase == 'true') ? true : false
4261
+ else
4262
+ raise(ArgumentError, 'Invalid config parser')
4263
+ end
4264
+ end
4265
+
4266
+ return vals
4169
4267
  end # def _post_config()
4170
4268
 
4171
4269
 
@@ -4491,6 +4589,26 @@ class Client
4491
4589
  end # def _verb_getpost()
4492
4590
 
4493
4591
 
4592
+ ###############################################
4593
+ # Display the config value
4594
+ #
4595
+ def _util_config(cfg, key, val)
4596
+ opt = cfg.setup(key)
4597
+
4598
+ case opt[:input][0]
4599
+ when :text
4600
+ return val
4601
+ when :boolean
4602
+ return val ? 'true' : 'false'
4603
+ when :select
4604
+ opt[:input][3].each{|ary| return ary[1] if ary[0] == val }
4605
+ return 'Invalid select option'
4606
+ else
4607
+ raise(ArgumentError, 'Unsupported config display type')
4608
+ end
4609
+ end # def _util_config
4610
+
4611
+
4494
4612
  ###############################################
4495
4613
  # Process the POST
4496
4614
  #
@@ -4608,7 +4726,7 @@ class Client
4608
4726
  def _resp(env, res, body)
4609
4727
  html = Page % [
4610
4728
  env['icfs.page'],
4611
- @css,
4729
+ env['icfs'].config.get('css'),
4612
4730
  @js,
4613
4731
  body
4614
4732
  ]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: icfs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Graham A. Field
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-04 00:00:00.000000000 Z
11
+ date: 2019-07-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2-
14
14
 
@@ -24,6 +24,7 @@ extensions: []
24
24
  extra_rdoc_files: []
25
25
  files:
26
26
  - LICENSE.txt
27
+ - data/icfs-dark.css
27
28
  - data/icfs.css
28
29
  - data/icfs.js
29
30
  - devel/demo/ssl_gen.rb
@@ -89,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
90
  version: '0'
90
91
  requirements: []
91
92
  rubyforge_project:
92
- rubygems_version: 2.7.6.2
93
+ rubygems_version: 2.7.6
93
94
  signing_key:
94
95
  specification_version: 4
95
96
  summary: Investigative Case File System