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