icfs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
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
+ require 'yaml'
14
+ require 'json'
15
+ require 'faraday'
16
+ require 'fileutils'
17
+
18
+ require_relative '../lib/icfs'
19
+ require_relative '../lib/icfs/cache_elastic'
20
+ require_relative '../lib/icfs/store_fs'
21
+ require_relative '../lib/icfs/users_fs'
22
+
23
+ # load the config file
24
+ cfg = YAML.load_file(ARGV[0])
25
+ map = {}
26
+ cfg['cache']['map'].each do |key, val|
27
+ map[key.to_sym] = val
28
+ end
29
+
30
+ es = Faraday.new(cfg['elastic']['base'])
31
+ cache = ICFS::CacheElastic.new(map, es)
32
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
33
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
34
+
35
+ # clear out the store
36
+ if File.exists?(cfg['store']['dir'])
37
+ FileUtils.rm_rf(cfg['store']['dir'])
38
+ puts "Deleted store directory"
39
+ end
40
+ FileUtils.mkdir_p(cfg['store']['dir'])
41
+ puts "Created store directory: %s" % cfg['store']['dir']
42
+
43
+ # clear out the users
44
+ if File.exists?(cfg['users']['dir'])
45
+ FileUtils.rm_rf(cfg['users']['dir'])
46
+ puts "Deleted users directory"
47
+ end
48
+ FileUtils.mkdir_p(cfg['users']['dir'])
49
+ puts "Created users directory: %s" % cfg['users']['dir']
50
+
51
+ # delete the indexes
52
+ map.each do |sym, name|
53
+ resp = es.run_request(:delete, name, '', {})
54
+ if resp.success?
55
+ puts 'Deleted index: %s' % name
56
+ else
57
+ puts 'Failed to delete index: %s' % name
58
+ end
59
+ end
60
+
61
+ # add the users
62
+ cfg['init']['urg'].each do |usr|
63
+ users.write(usr)
64
+ puts "Added user/role/group: %s" % usr['name']
65
+ end
66
+
67
+ # create the indexes
68
+ cache.create(ICFS::CacheElastic::Maps)
69
+ puts "Indexes created"
70
+
71
+ api = ICFS::Api.new([], users, cache, store)
72
+ api.user = cfg['init']['user']
73
+
74
+ # add the templates
75
+ cfg['init']['templates'].each do |tmpl|
76
+ tp = {
77
+ 'template' => true,
78
+ 'status' => true,
79
+ 'title' => tmpl['template'],
80
+ 'access' => tmpl['access'],
81
+ }
82
+ ent = {
83
+ 'caseid' => tmpl['caseid'],
84
+ 'title' => tmpl['entry'],
85
+ 'content' => tmpl['content']
86
+ }
87
+ api.case_create(ent, tp)
88
+ puts "Created template: %s" % tmpl['caseid']
89
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
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
+ require 'yaml'
14
+ require 'faraday'
15
+
16
+ require_relative '../lib/icfs'
17
+ require_relative '../lib/icfs/cache_elastic'
18
+ require_relative '../lib/icfs/store_fs'
19
+ require_relative '../lib/icfs/users_fs'
20
+ require_relative '../lib/icfs/web/client'
21
+ require_relative '../lib/icfs/web/auth_ssl'
22
+ require_relative '../lib/icfs/demo/timezone'
23
+
24
+ # load the config file
25
+ cfg = YAML.load_file(ARGV[0])
26
+ map = {}
27
+ cfg['cache']['map'].each do |key, val|
28
+ map[key.to_sym] = val
29
+ end
30
+
31
+ es = Faraday.new(cfg['elastic']['base'])
32
+ cache = ICFS::CacheElastic.new(map, es)
33
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
34
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
35
+ api = ICFS::Api.new([], users, cache, store)
36
+ web = ICFS::Web::Client.new(cfg['web']['css'], cfg['web']['script'])
37
+
38
+ user_map = {
39
+ 'CN=client 1,OU=Test Client,OU=example,OU=org' => 'user1',
40
+ 'CN=client 2,OU=Test Client,OU=example,OU=org' => 'user2',
41
+ 'CN=client 3,OU=Test Client,OU=example,OU=org' => 'user3'
42
+ }
43
+
44
+ app = Rack::Builder.new do
45
+ use(ICFS::Web::AuthSsl, user_map, api)
46
+ use(ICFS::Demo::Timezone, cfg['web']['tz'])
47
+ run web
48
+ end
49
+
50
+ puts 'try to run'
51
+ Rack::Handler::FastCGI.run(app, {Host: '127.0.0.1', Port: 9000} )
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
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
+ require 'openssl'
14
+
15
+
16
+ # make a CA key
17
+ ca_key = OpenSSL::PKey::RSA.new 2048
18
+ ca_cert = OpenSSL::X509::Certificate.new
19
+ ca_cert.version = 2
20
+ ca_cert.serial = 1
21
+ ca_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/CN=Test Root CA')
22
+ ca_cert.issuer = ca_cert.subject
23
+ ca_cert.public_key = ca_key.public_key
24
+ ca_cert.not_before = Time.now
25
+ ca_cert.not_after = ca_cert.not_before + (30 * 24 * 60 * 60) # 30 days
26
+ ef = OpenSSL::X509::ExtensionFactory.new
27
+ ef.subject_certificate = ca_cert
28
+ ef.issuer_certificate = ca_cert
29
+ ca_cert.add_extension(ef.create_extension("basicConstraints","CA:TRUE",true))
30
+ ca_cert.add_extension(ef.create_extension("keyUsage","keyCertSign, cRLSign", true))
31
+ ca_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
32
+ ca_cert.add_extension(ef.create_extension("authorityKeyIdentifier","keyid:always",false))
33
+ ca_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
34
+
35
+ # save CA cert
36
+ File.open("ca_cert.pem", "wb"){|fi| fi.write ca_cert.to_pem }
37
+
38
+
39
+ # make a server key
40
+ srv_key = OpenSSL::PKey::RSA.new 2048
41
+ srv_cert = OpenSSL::X509::Certificate.new
42
+ srv_cert.version = 2
43
+ srv_cert.serial = 2
44
+ srv_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/OU=Test Server/CN=localhost')
45
+ srv_cert.issuer = ca_cert.subject
46
+ srv_cert.public_key = srv_key.public_key
47
+ srv_cert.not_before = Time.now
48
+ srv_cert.not_after = srv_cert.not_before + (30 * 24 * 60 * 60) # 30 days
49
+ ef = OpenSSL::X509::ExtensionFactory.new
50
+ ef.subject_certificate = srv_cert
51
+ ef.issuer_certificate = ca_cert
52
+ srv_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
53
+ srv_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
54
+ srv_cert.add_extension(ef.create_extension("subjectKeyIdentifier","hash",false))
55
+ srv_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
56
+
57
+ # save server key
58
+ File.open("srv_cert.pem", "wb"){|fi| fi.write srv_cert.to_pem }
59
+ File.open("srv_key.pem", "wb"){|fi| fi.write srv_key.to_pem }
60
+
61
+ # make client certs
62
+ 5.times do |ix|
63
+ clt_key = OpenSSL::PKey::RSA.new 2048
64
+ clt_cert = OpenSSL::X509::Certificate.new
65
+ clt_cert.version = 2
66
+ clt_cert.serial = ix+3
67
+ clt_cert.subject = OpenSSL::X509::Name.parse('/OU=org/OU=example/OU=Test Client/CN=client %d' % ix)
68
+ clt_cert.issuer = ca_cert.subject
69
+ clt_cert.public_key = clt_key.public_key
70
+ clt_cert.not_before = Time.now
71
+ clt_cert.not_after = clt_cert.not_before + (30 * 24 * 60 * 60) # 30 days
72
+ ef = OpenSSL::X509::ExtensionFactory.new
73
+ ef.subject_certificate = clt_cert
74
+ ef.issuer_certificate = ca_cert
75
+ clt_cert.add_extension(ef.create_extension("basicConstraints", "CA:FALSE"))
76
+ clt_cert.add_extension(ef.create_extension("keyUsage", "keyEncipherment,dataEncipherment,digitalSignature"))
77
+ clt_cert.sign(ca_key, OpenSSL::Digest::SHA256.new)
78
+
79
+ # pkcs12
80
+ clt_pkcs12 = OpenSSL::PKCS12.create('demo', 'client-%d' % ix, clt_key, clt_cert)
81
+
82
+ # save cert
83
+ File.open('clt_%d.pfx' % ix, 'wb'){|fi| fi.write clt_pkcs12.to_der }
84
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
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
+ require 'yaml'
14
+ require 'faraday'
15
+
16
+ require_relative '../lib/icfs'
17
+ require_relative '../lib/icfs/cache_elastic'
18
+ require_relative '../lib/icfs/store_fs'
19
+ require_relative '../lib/icfs/users_fs'
20
+ require_relative '../lib/icfs/web/client'
21
+ require_relative '../lib/icfs/demo/auth'
22
+ require_relative '../lib/icfs/demo/static'
23
+ require_relative '../lib/icfs/demo/timezone'
24
+
25
+ # load the config file
26
+ cfg = YAML.load_file(ARGV[0])
27
+ map = {}
28
+ cfg['cache']['map'].each do |key, val|
29
+ map[key.to_sym] = val
30
+ end
31
+
32
+ es = Faraday.new(cfg['elastic']['base'])
33
+ cache = ICFS::CacheElastic.new(map, es)
34
+ store = ICFS::StoreFs.new(cfg['store']['dir'])
35
+ users = ICFS::UsersFs.new(cfg['users']['dir'])
36
+ api = ICFS::Api.new([], users, cache, store)
37
+ web = ICFS::Web::Client.new(cfg['web']['css'], cfg['web']['script'])
38
+
39
+ app = Rack::Builder.new do
40
+ use(ICFS::Demo::Auth, api)
41
+ use(ICFS::Demo::Static, cfg['web']['static'])
42
+ use(ICFS::Demo::Timezone, cfg['web']['tz'])
43
+ run web
44
+ end
45
+
46
+ opts = {}
47
+ opts[:Host] = cfg['web']['host'] if cfg['web']['host']
48
+ opts[:Port] = cfg['web']['port'] if cfg['web']['port']
49
+
50
+ Rack::Handler::WEBrick.run(app, opts)
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
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
+ require 'yard'
15
+
16
+ YARD::Registry.load!.all.each do |o|
17
+ todo = o.tags(:todo)
18
+ next if todo.empty?
19
+ todo.each{|tg| puts "%s\n %s\n\n" % [o.path, tg.text] }
20
+ end
@@ -0,0 +1,94 @@
1
+ #
2
+ # Investigative Case File System
3
+ #
4
+ # Copyright 2019 by Graham A. Field
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License version 3.
8
+ #
9
+ # This program is distributed WITHOUT ANY WARRANTY; without even the
10
+ # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
+
12
+ cache:
13
+ map:
14
+ entry: entry
15
+ case: case
16
+ action: action
17
+ index: index
18
+ log: log
19
+ lock: lock
20
+ current: current
21
+
22
+ elastic:
23
+ base: "http://localhost:9200"
24
+
25
+ store:
26
+ dir: work/store
27
+
28
+ users:
29
+ dir: work/users
30
+
31
+ init:
32
+ user: user1
33
+ urg:
34
+ - name: role1
35
+ type: role
36
+
37
+ - name: role2
38
+ type: role
39
+
40
+ - name: role3
41
+ type: role
42
+
43
+ - name: group1
44
+ type: group
45
+
46
+ - name: group2
47
+ type: group
48
+
49
+ - name: user1
50
+ type: user
51
+ roles:
52
+ - role2
53
+ - role3
54
+ groups:
55
+ - group2
56
+ perms:
57
+ - "{perm_a}"
58
+ - "{perm_b}"
59
+
60
+ - name: user2
61
+ type: user
62
+ roles:
63
+ - role1
64
+ - role2
65
+ groups:
66
+ - group1
67
+ perms:
68
+ - "{perm_b}"
69
+
70
+ templates:
71
+ - caseid: template1
72
+ template: "New Template"
73
+ access:
74
+ - perm: "[manage]"
75
+ grant:
76
+ - user1
77
+ - perm: "[write]"
78
+ grant:
79
+ - group1
80
+ entry: "Create new template"
81
+ content: "New template being created"
82
+
83
+
84
+ web:
85
+ css: "/static/icfs.css"
86
+ script: "/static/icfs.js"
87
+ tz: "-04:00"
88
+ static:
89
+ "/static/icfs.css":
90
+ path: "data/icfs.css"
91
+ mime: "text/css; charset=utf-8"
92
+ "/static/icfs.js":
93
+ path: "data/icfs.js"
94
+ mime: "application/javascript; charset=utf-8"
data/data/icfs.css ADDED
@@ -0,0 +1,475 @@
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
+ a {
20
+ color: black;
21
+ text-decoration: none;
22
+ }
23
+
24
+ a:hover {
25
+ text-decoration: underline;
26
+ }
27
+
28
+
29
+ input {
30
+ border: 1px solid black;
31
+ border-radius: 3px;
32
+ font-family: serif;
33
+ font-size: 12pt;
34
+ margin: 0.1em;
35
+ padding: 0.2em;
36
+ }
37
+
38
+ textarea {
39
+ border: 1px solid black;
40
+ border-radius: 3px;
41
+ font-family: serif;
42
+ font-size: 12pt;
43
+ margin: 0.1em;
44
+ padding: 0.2em;
45
+ }
46
+
47
+
48
+ /*************************************************************************
49
+ Navbar
50
+ */
51
+
52
+ div.nav {
53
+ border-width: 3px;
54
+ border-style: none none solid none;
55
+ border-color: green;
56
+ display: flex;
57
+ }
58
+
59
+ div.nav-icfs {
60
+ color: green;
61
+ width: 3em;
62
+ }
63
+
64
+ div.nav-icfs a {
65
+ color: green;
66
+ }
67
+
68
+ div.nav-case {
69
+ color: blue;
70
+ width: 15em;
71
+ }
72
+
73
+ div.nav-case a {
74
+ color: blue;
75
+ }
76
+
77
+ div.nav-tab {
78
+ text-align: center;
79
+ border: 1px;
80
+ width: 7em;
81
+ border-style: solid solid none solid;
82
+ }
83
+
84
+
85
+ /*************************************************************************
86
+ Sidebar layout
87
+ */
88
+
89
+ div.sbar {
90
+ border-bottom: thin solid;
91
+ display: flex;
92
+ align-content: stretch;
93
+ }
94
+
95
+ div.sbar-main {
96
+ flex: 1 0 0;
97
+ vertical-align: top;
98
+ }
99
+
100
+ div.sbar-side {
101
+ flex: 0 0 20em;
102
+ border-right: thin solid;
103
+ vertical-align: top;
104
+ }
105
+
106
+ div.sbar-side-head {
107
+ text-align: center;
108
+ border-bottom: thin solid;
109
+ color: darkblue;
110
+ }
111
+
112
+ div.sbar-main-head {
113
+ font-size: 120%;
114
+ padding: 0.25em;
115
+ }
116
+
117
+ div.sbar-main-sub {
118
+ font-weight: bold;
119
+ font-size: 75%;
120
+ }
121
+
122
+ pre.sbar-main-content {
123
+ font-family: serif;
124
+ font-size: 12pt;
125
+ border-top: thin solid;
126
+ border-bottom: thin solid;
127
+ margin: 0px;
128
+ padding: 0.25em;
129
+ white-space: pre-wrap;
130
+ }
131
+
132
+
133
+ /*************************************************************************
134
+ Sections
135
+ */
136
+
137
+ div.sect {
138
+ padding: 0.25em;
139
+ }
140
+
141
+ div.sect-head {
142
+ display: flex;
143
+ flex-direction: row;
144
+ justify-content: space-between;
145
+ padding: 0.2em;
146
+ background: #d8d8d8;
147
+ }
148
+
149
+ div.sect-main {
150
+ display: flex;
151
+ flex-direction: row;
152
+ justify-content: space-between;
153
+ padding: 0.2em;
154
+ background: #99bbff;
155
+ }
156
+
157
+
158
+ div.sect-label {
159
+ flex: 0 1 auto;
160
+ }
161
+
162
+
163
+ div.sect-fill {
164
+ flex: 1 1 auto;
165
+ }
166
+
167
+
168
+ div.sect-head-right {
169
+ flex: 0 1 auto;
170
+ }
171
+
172
+
173
+ /*************************************************************************
174
+ List
175
+ */
176
+
177
+ div.list-head {
178
+ display: flex;
179
+ flex-direction: row;
180
+ border-bottom: solid;
181
+ border-width: 1px;
182
+ font-style: italic;
183
+ }
184
+
185
+
186
+ div.list-row {
187
+ display: flex;
188
+ flex-direction: row;
189
+ flex-wrap: wrap;
190
+ border-bottom: dotted;
191
+ border-width: 1px;
192
+ align-items: flex-start;
193
+ }
194
+
195
+
196
+ div.list-int {
197
+ width: 4em;
198
+ padding: 0 0.15em 0 0.15em;
199
+ }
200
+
201
+
202
+ div.list-perm {
203
+ width: 15em;
204
+ padding: 0 0.15em 0 0.15em;
205
+ }
206
+
207
+
208
+ div.list-usergrp {
209
+ width: 15em;
210
+ padding: 0 0.15em 0 0.15em;
211
+ }
212
+
213
+
214
+ div.list-label {
215
+ width: 7em;
216
+ padding: 0 0.15em 0 0.15em;
217
+ text-align: right;
218
+ }
219
+
220
+
221
+ div.list-caseid {
222
+ width: 10em;
223
+ padding: 0 0.15em 0 0.15em;
224
+ }
225
+
226
+
227
+ div.list-text-s {
228
+ width: 8em;
229
+ padding: 0 0.15em 0 0.15em;
230
+ }
231
+
232
+
233
+ div.list-text-m {
234
+ width: 12em;
235
+ padding: 0 0.15em 0 0.15em;
236
+ }
237
+
238
+
239
+ div.list-time {
240
+ width: 12em;
241
+ padding: 0 0.15em 0 0.15em;
242
+ }
243
+
244
+
245
+ div.list-tag {
246
+ width: 12em;
247
+ padding: 0 0.15em 0 0.15em;
248
+ }
249
+
250
+ div.list-hash {
251
+ font-family: monospace;
252
+ font-size: 12pt;
253
+ padding: 0 0.15em 0 0.15em;
254
+ }
255
+
256
+ div.list-stat {
257
+ width: 12em;
258
+ padding: 0 0.15em 0 0.15em;
259
+ }
260
+
261
+ div.list-snip {
262
+ flex: 0 1 100%;
263
+ padding: 0.1em;
264
+ margin: 0.1em 1em 0.1em 1em;
265
+ background: #f0f0f0;
266
+ }
267
+
268
+ /*************************************************************************
269
+ Tasks
270
+ */
271
+
272
+ div.task {
273
+ border-bottom: thin solid;
274
+ }
275
+
276
+
277
+ div.task-ed {
278
+ background: #99b3ff;
279
+ }
280
+
281
+
282
+ div.task-ro {
283
+ background: #ff6666;
284
+ }
285
+
286
+
287
+ /*************************************************************************
288
+ Forms
289
+ */
290
+
291
+ div.form-row {
292
+ min-height: 2em;
293
+ display: flex;
294
+ flex-direction: row;
295
+ align-items: baseline;
296
+ }
297
+
298
+
299
+ input.form-index {
300
+ width: 20em;
301
+ }
302
+
303
+
304
+ input.form-stat {
305
+ width: 15em;
306
+ }
307
+
308
+
309
+ input.form-usergrp {
310
+ width: 10em;
311
+ }
312
+
313
+
314
+ input.form-caseid {
315
+ width: 12em;
316
+ }
317
+
318
+
319
+ input.form-sort {
320
+ width: 10em;
321
+ }
322
+
323
+
324
+ input.form-content {
325
+ width: 50em;
326
+ }
327
+
328
+
329
+ input.form-title {
330
+ width: 40em;
331
+ }
332
+
333
+
334
+ input.form-file-name {
335
+ width: 30em;
336
+ }
337
+
338
+
339
+ input.form-file-upl {
340
+ }
341
+
342
+
343
+ input.form-float {
344
+ width: 5em;
345
+ }
346
+
347
+
348
+ input.form-check {
349
+ }
350
+
351
+
352
+ input.form-time {
353
+ width: 15em;
354
+ }
355
+
356
+
357
+ input.form-perm {
358
+ width: 15em;
359
+ }
360
+
361
+
362
+ input.form-int {
363
+ width: 5em;
364
+ }
365
+
366
+
367
+ input.form-tag {
368
+ width: 20em;
369
+ }
370
+
371
+
372
+ input.form-boolean {
373
+ width: 5em;
374
+ }
375
+
376
+
377
+ textarea.form-content {
378
+ width: 50em;
379
+ height: 20em;
380
+ }
381
+
382
+
383
+ /*************************************************************************
384
+ Other divs
385
+ */
386
+
387
+ div.desc {
388
+ padding: 0.25em;
389
+ background: #fff5e6;
390
+ margin-bottom: 1em;
391
+ }
392
+
393
+ div.desc-head {
394
+ font-size: 120%;
395
+ text-align: center;
396
+ }
397
+
398
+ span.desc-query {
399
+ text-decoration: underline;
400
+
401
+ }
402
+
403
+ div.query-form {
404
+ border-bottom: solid;
405
+ border-width: 1px;
406
+ padding: 0.5em;
407
+ }
408
+
409
+
410
+
411
+ div.hidden {
412
+ display: none;
413
+ }
414
+
415
+ div.invisible {
416
+ visibility: hidden;
417
+ }
418
+
419
+
420
+ /*************************************************************************
421
+ Tooltips
422
+ */
423
+
424
+ div.tip {
425
+ display: inline-block;
426
+ }
427
+
428
+ div.tip div.tip-disp {
429
+ border: thin solid;
430
+ border-radius: 50%;
431
+ font-size: 60%;
432
+ text-align: center;
433
+ width: 1.1em;
434
+ height: 1.1em;
435
+ margin: 0 0.1em;
436
+ }
437
+
438
+
439
+ div.tip div.tip-disp::before {
440
+ content: "?";
441
+ }
442
+
443
+
444
+ /* status bar tooltips */
445
+ div.tip div.tip-info {
446
+ visibility: hidden;
447
+ background-color: black;
448
+ color: #fff;
449
+ position: fixed;
450
+ padding: 0.5em;
451
+ bottom: 0;
452
+ left: 0;
453
+ width: 100vw;
454
+ }
455
+
456
+
457
+ /* floating tooltips
458
+ div.tip div.tip-info {
459
+ visibility: hidden;
460
+ background-color: black;
461
+ color: #fff;
462
+ border-radius: 0.5em;
463
+ padding: 0.5em;
464
+ margin: 0.25em;
465
+ width: 25em;
466
+ margin-left: 0em;
467
+ position: absolute;
468
+ z-index: 1;
469
+ }
470
+ */
471
+
472
+
473
+ div.tip:hover div.tip-info {
474
+ visibility: visible;
475
+ }