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 +4 -4
- data/data/icfs-dark.css +504 -0
- data/devel/run/base.rb +7 -6
- data/devel/run/webrick.rb +5 -1
- data/lib/icfs.rb +1 -1
- data/lib/icfs/cache_elastic.rb +1 -1
- data/lib/icfs/config.rb +166 -17
- data/lib/icfs/config_redis.rb +3 -3
- data/lib/icfs/config_s3.rb +5 -5
- data/lib/icfs/web/client.rb +192 -74
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1a35a2e0403c01c2e6aa54db7014b1f41fc2769525afa66e2af066ae5b523fe
|
4
|
+
data.tar.gz: c9d77e1e72454fda263561f2640cbf14c0d24887038520868ddac3d273bc9c23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51dab31789c7fd5b1d3ce257b2bff25706727d555a6afab5d1a71e199eb8258a5ac13ab9b0471bdfcf8ffd3d8ba753f8d1d063c291ff2fc6b0759c6881ab779c
|
7
|
+
data.tar.gz: 00ac3104db418aa2f9bc07268299742605c71a49e572e677fc2572e506a079dd04b6d752ea0ea59537eef9254d11a83251da5667e05a66a6fb04040d55eedd77
|
data/data/icfs-dark.css
ADDED
@@ -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
|
+
}
|
data/devel/run/base.rb
CHANGED
@@ -59,11 +59,12 @@ def get_base
|
|
59
59
|
current: 'current',
|
60
60
|
}.freeze
|
61
61
|
|
62
|
-
#
|
63
|
-
|
64
|
-
'tz'
|
65
|
-
'rel_time'
|
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(
|
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
|
data/devel/run/webrick.rb
CHANGED
@@ -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.
|
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'
|
data/lib/icfs.rb
CHANGED
data/lib/icfs/cache_elastic.rb
CHANGED
@@ -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('
|
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]),
|
data/lib/icfs/config.rb
CHANGED
@@ -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
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
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(
|
98
|
+
def initialize(setup)
|
44
99
|
@data = {}
|
45
100
|
@unam = nil
|
46
|
-
@
|
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
|
-
|
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
|
#
|
data/lib/icfs/config_redis.rb
CHANGED
@@ -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.
|
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
|
-
|
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 =
|
69
|
+
json = _generate()
|
70
70
|
@redis.del(_key(@unam))
|
71
71
|
@base.data = @data
|
72
72
|
@base.save
|
data/lib/icfs/config_s3.rb
CHANGED
@@ -24,13 +24,13 @@ class ConfigS3 < Config
|
|
24
24
|
###############################################
|
25
25
|
# New instance
|
26
26
|
#
|
27
|
-
# @param
|
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(
|
33
|
-
super(
|
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
|
-
|
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 =
|
60
|
+
json = _generate()
|
61
61
|
@s3.put_object( bucket: @bck, key: _key(@unam), body: json )
|
62
62
|
end # def save()
|
63
63
|
|
data/lib/icfs/web/client.rb
CHANGED
@@ -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(
|
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)
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
3820
|
-
|
3821
|
-
|
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"
|
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
|
-
|
3979
|
-
|
3980
|
-
|
3981
|
-
|
3982
|
-
|
3983
|
-
|
3984
|
-
|
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
|
-
|
4165
|
-
|
4166
|
-
|
4167
|
-
}
|
4168
|
-
|
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
|
-
|
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.
|
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-
|
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
|
93
|
+
rubygems_version: 2.7.6
|
93
94
|
signing_key:
|
94
95
|
specification_version: 4
|
95
96
|
summary: Investigative Case File System
|