icfs 0.3.0 → 0.3.1

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