s3arch 0.0.5 → 0.0.6
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
- checksums.yaml.gz.sig +0 -0
- data/lambda/controllers/s3arch_controller.rb +3 -15
- data/lib/s3arch/version.rb +1 -1
- data/lib/s3arch/web.rb +1 -2
- data/lib/s3arch.rb +0 -4
- data.tar.gz.sig +0 -0
- metadata +1 -5
- metadata.gz.sig +0 -0
- data/lib/s3arch/dashboard/application.rb +0 -86
- data/lib/s3arch/dashboard/views/index.html.erb +0 -150
- data/lib/s3arch/dashboard.rb +0 -13
- data/lib/s3arch/holster.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f15baec2213fb6a5cee584447843884e1b9aa5e1bf650d500c53cd6c17e69539
|
|
4
|
+
data.tar.gz: d00b7d3345ebf462c5ef0a450452502a818c2dd6969c34e82bfc5d763dc9cd7f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3fd7108161529a4d4d23ffcd49e483d944064ea6364438470f65859c86382f43a611e6867a4d69981772c76bc6e244d306a256dbb1ac6607622cf787d78551e9
|
|
7
|
+
data.tar.gz: a9895ba3ef76d2720330633cc6eeb44e6d70d9a2461d4602eea136d8de19f4032397e1195dba91c1d0160a95a19bc69ec8123d2dd25581683da47a547717cd3e
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'erb'
|
|
4
3
|
require 'aws-sdk-dynamodb'
|
|
5
4
|
|
|
6
5
|
class S3archController < BeltController::Base
|
|
7
|
-
VIEWS_PATH = File.expand_path('../../lib/s3arch/dashboard/views', __dir__)
|
|
8
|
-
|
|
9
6
|
def index
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
html_response(html)
|
|
7
|
+
owners = fetch_owners
|
|
8
|
+
success_response(owners: owners)
|
|
13
9
|
rescue StandardError => e
|
|
14
|
-
|
|
15
|
-
@owners = []
|
|
16
|
-
html = render_erb('index')
|
|
17
|
-
html_response(html, 500)
|
|
10
|
+
error_response("Failed to load owners: #{e.message}", 500)
|
|
18
11
|
end
|
|
19
12
|
|
|
20
13
|
def rebuild
|
|
@@ -58,9 +51,4 @@ class S3archController < BeltController::Base
|
|
|
58
51
|
end
|
|
59
52
|
owners.sort_by { |o| o[:updated_at].to_s }.reverse
|
|
60
53
|
end
|
|
61
|
-
|
|
62
|
-
def render_erb(template)
|
|
63
|
-
path = File.join(VIEWS_PATH, "#{template}.html.erb")
|
|
64
|
-
ERB.new(File.read(path), trim_mode: '-').result(binding)
|
|
65
|
-
end
|
|
66
54
|
end
|
data/lib/s3arch/version.rb
CHANGED
data/lib/s3arch/web.rb
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Lightweight S3arch load for web/API contexts (no SQLite dependency).
|
|
4
|
-
# Provides configuration
|
|
4
|
+
# Provides configuration and routes only.
|
|
5
5
|
require_relative 'version'
|
|
6
6
|
require_relative 'configuration'
|
|
7
7
|
require_relative 'routes'
|
|
8
|
-
require_relative 'holster'
|
|
9
8
|
|
|
10
9
|
module S3arch
|
|
11
10
|
class Error < StandardError; end
|
data/lib/s3arch.rb
CHANGED
|
@@ -7,10 +7,6 @@ require_relative 's3arch/indexer'
|
|
|
7
7
|
require_relative 's3arch/searcher'
|
|
8
8
|
require_relative 's3arch/handler'
|
|
9
9
|
require_relative 's3arch/routes'
|
|
10
|
-
require_relative 's3arch/dashboard'
|
|
11
|
-
|
|
12
|
-
# Register as a Belt holster when Belt is loaded
|
|
13
|
-
require_relative 's3arch/holster' if defined?(Belt::Holster)
|
|
14
10
|
|
|
15
11
|
module S3arch
|
|
16
12
|
class Error < StandardError; end
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: s3arch
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adam Dalton
|
|
@@ -109,11 +109,7 @@ files:
|
|
|
109
109
|
- lambda/controllers/s3arch_controller.rb
|
|
110
110
|
- lib/s3arch.rb
|
|
111
111
|
- lib/s3arch/configuration.rb
|
|
112
|
-
- lib/s3arch/dashboard.rb
|
|
113
|
-
- lib/s3arch/dashboard/application.rb
|
|
114
|
-
- lib/s3arch/dashboard/views/index.html.erb
|
|
115
112
|
- lib/s3arch/handler.rb
|
|
116
|
-
- lib/s3arch/holster.rb
|
|
117
113
|
- lib/s3arch/indexer.rb
|
|
118
114
|
- lib/s3arch/routes.rb
|
|
119
115
|
- lib/s3arch/searcher.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'erb'
|
|
4
|
-
require 'json'
|
|
5
|
-
require 'aws-sdk-dynamodb'
|
|
6
|
-
|
|
7
|
-
module S3arch
|
|
8
|
-
module Dashboard
|
|
9
|
-
class Application
|
|
10
|
-
VIEWS_PATH = File.expand_path('views', __dir__)
|
|
11
|
-
|
|
12
|
-
# Dispatcher mount DSL contract — keep in sync with #call dispatch below.
|
|
13
|
-
def routes
|
|
14
|
-
[
|
|
15
|
-
{ method: :get, path: '/' },
|
|
16
|
-
{ method: :post, path: '/rebuild' }
|
|
17
|
-
]
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def call(env)
|
|
21
|
-
req = Rack::Request.new(env)
|
|
22
|
-
path = req.path_info.sub(%r{^/}, '')
|
|
23
|
-
|
|
24
|
-
case [req.request_method, path]
|
|
25
|
-
when ['GET', ''], %w[GET index]
|
|
26
|
-
index(req)
|
|
27
|
-
when %w[POST rebuild]
|
|
28
|
-
rebuild(req)
|
|
29
|
-
else
|
|
30
|
-
[404, { 'content-type' => 'text/plain' }, ['Not Found']]
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def index(req)
|
|
37
|
-
@env = req.env
|
|
38
|
-
@owners = fetch_owners
|
|
39
|
-
html = render('index')
|
|
40
|
-
[200, { 'content-type' => 'text/html' }, [html]]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def rebuild(req)
|
|
44
|
-
owner_id = req.params['owner_id']&.strip
|
|
45
|
-
if owner_id && !owner_id.empty?
|
|
46
|
-
indexer = S3arch::Indexer.new
|
|
47
|
-
indexer.rebuild(owner_id)
|
|
48
|
-
end
|
|
49
|
-
[303, { 'location' => "#{req.script_name}/" }, []]
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def fetch_owners
|
|
53
|
-
config = S3arch.configuration
|
|
54
|
-
dynamodb = Aws::DynamoDB::Client.new
|
|
55
|
-
items = []
|
|
56
|
-
params = { table_name: config.version_table }
|
|
57
|
-
|
|
58
|
-
loop do
|
|
59
|
-
result = dynamodb.scan(params)
|
|
60
|
-
items.concat(result.items)
|
|
61
|
-
break unless result.last_evaluated_key
|
|
62
|
-
|
|
63
|
-
params[:exclusive_start_key] = result.last_evaluated_key
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
owners = items.map do |item|
|
|
67
|
-
{
|
|
68
|
-
owner_id: item[config.owner_key] || item.values.first,
|
|
69
|
-
version: item['version'],
|
|
70
|
-
record_count: item['record_count'],
|
|
71
|
-
updated_at: item['updated_at']
|
|
72
|
-
}
|
|
73
|
-
end
|
|
74
|
-
owners.sort_by { |o| o[:updated_at].to_s }.reverse
|
|
75
|
-
rescue StandardError => e
|
|
76
|
-
@error = e.message
|
|
77
|
-
[]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def render(template)
|
|
81
|
-
path = File.join(VIEWS_PATH, "#{template}.html.erb")
|
|
82
|
-
ERB.new(File.read(path), trim_mode: '-').result(binding)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>S3arch Dashboard</title>
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
-
<script>
|
|
7
|
-
(function() {
|
|
8
|
-
function getIdToken() {
|
|
9
|
-
try {
|
|
10
|
-
for (var i = 0; i < localStorage.length; i++) {
|
|
11
|
-
var key = localStorage.key(i);
|
|
12
|
-
if (key && key.indexOf('.idToken') !== -1) return localStorage.getItem(key);
|
|
13
|
-
}
|
|
14
|
-
} catch(e) {}
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
function isTokenValid(token) {
|
|
18
|
-
try {
|
|
19
|
-
var payload = JSON.parse(atob(token.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')));
|
|
20
|
-
return payload.exp && payload.exp > Date.now() / 1000;
|
|
21
|
-
} catch(e) { return false; }
|
|
22
|
-
}
|
|
23
|
-
var token = getIdToken();
|
|
24
|
-
if (!token || !isTokenValid(token)) {
|
|
25
|
-
window.location.replace('/login');
|
|
26
|
-
}
|
|
27
|
-
})();
|
|
28
|
-
</script>
|
|
29
|
-
<style>
|
|
30
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
31
|
-
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 1.5rem; }
|
|
32
|
-
h1 { margin-bottom: 1rem; font-size: 1.5rem; }
|
|
33
|
-
.error { background: #fee; border: 1px solid #fcc; padding: 0.75rem; border-radius: 4px; margin-bottom: 1rem; color: #c00; }
|
|
34
|
-
.success { background: #efe; border: 1px solid #cfc; padding: 0.75rem; border-radius: 4px; margin-bottom: 1rem; color: #060; display: none; }
|
|
35
|
-
.meta { font-size: 0.85rem; color: #666; margin-bottom: 1rem; }
|
|
36
|
-
.empty { padding: 2rem; text-align: center; color: #666; }
|
|
37
|
-
|
|
38
|
-
/* Desktop: table */
|
|
39
|
-
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 6px; overflow: hidden; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
40
|
-
th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid #eee; }
|
|
41
|
-
th { background: #fafafa; font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; color: #555; }
|
|
42
|
-
tr:last-child td { border-bottom: none; }
|
|
43
|
-
tr:hover td { background: #f9fafb; }
|
|
44
|
-
.btn { border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: background 0.15s; }
|
|
45
|
-
.btn-rebuild { background: #2563eb; color: #fff; }
|
|
46
|
-
.btn-rebuild:hover { background: #1d4ed8; }
|
|
47
|
-
.btn-rebuild:disabled { background: #93c5fd; cursor: not-allowed; }
|
|
48
|
-
.owner-id { font-family: "SF Mono", SFMono-Regular, Consolas, monospace; font-size: 0.8rem; }
|
|
49
|
-
|
|
50
|
-
/* Mobile: card layout */
|
|
51
|
-
@media (max-width: 640px) {
|
|
52
|
-
body { padding: 1rem; }
|
|
53
|
-
table, thead, tbody, th, tr, td { display: block; }
|
|
54
|
-
thead { display: none; }
|
|
55
|
-
tr { background: #fff; border-radius: 6px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 0.75rem; padding: 0.75rem; }
|
|
56
|
-
tr:hover td { background: transparent; }
|
|
57
|
-
td { padding: 0.25rem 0; border: none; display: flex; justify-content: space-between; align-items: center; }
|
|
58
|
-
td::before { content: attr(data-label); font-weight: 600; font-size: 0.75rem; text-transform: uppercase; color: #555; margin-right: 0.5rem; }
|
|
59
|
-
td:last-child { margin-top: 0.5rem; justify-content: flex-start; }
|
|
60
|
-
td:last-child::before { display: none; }
|
|
61
|
-
.btn { width: 100%; text-align: center; padding: 0.6rem; }
|
|
62
|
-
}
|
|
63
|
-
</style>
|
|
64
|
-
</head>
|
|
65
|
-
<body>
|
|
66
|
-
<h1>S3arch Dashboard</h1>
|
|
67
|
-
<p class="meta"><%= @owners.size %> indexed owner<%= @owners.size == 1 ? '' : 's' %></p>
|
|
68
|
-
|
|
69
|
-
<div id="flash" class="success"></div>
|
|
70
|
-
|
|
71
|
-
<%- if @error -%>
|
|
72
|
-
<div class="error"><strong>Error:</strong> <%= @error %></div>
|
|
73
|
-
<%- end -%>
|
|
74
|
-
|
|
75
|
-
<%- if @owners.any? -%>
|
|
76
|
-
<table>
|
|
77
|
-
<thead>
|
|
78
|
-
<tr>
|
|
79
|
-
<th>Owner</th>
|
|
80
|
-
<th>Version</th>
|
|
81
|
-
<th>Records</th>
|
|
82
|
-
<th>Last Updated</th>
|
|
83
|
-
<th>Actions</th>
|
|
84
|
-
</tr>
|
|
85
|
-
</thead>
|
|
86
|
-
<tbody>
|
|
87
|
-
<%- @owners.each do |owner| -%>
|
|
88
|
-
<tr>
|
|
89
|
-
<td class="owner-id" data-label="Owner"><%= owner[:owner_id] %></td>
|
|
90
|
-
<td data-label="Version"><%= owner[:version].to_i %></td>
|
|
91
|
-
<td data-label="Records"><%= owner[:record_count] ? owner[:record_count].to_i.to_s.gsub(/(\d)(?=(\d{3})+$)/, '\1,') : '—' %></td>
|
|
92
|
-
<td data-label="Updated"><%= owner[:updated_at] || '—' %></td>
|
|
93
|
-
<td>
|
|
94
|
-
<button class="btn btn-rebuild" onclick="rebuild('<%= owner[:owner_id] %>', this)">Recreate SQLite</button>
|
|
95
|
-
</td>
|
|
96
|
-
</tr>
|
|
97
|
-
<%- end -%>
|
|
98
|
-
</tbody>
|
|
99
|
-
</table>
|
|
100
|
-
<%- else -%>
|
|
101
|
-
<div class="empty">No indexed owners found.</div>
|
|
102
|
-
<%- end -%>
|
|
103
|
-
|
|
104
|
-
<script>
|
|
105
|
-
function getIdToken() {
|
|
106
|
-
try {
|
|
107
|
-
for (var i = 0; i < localStorage.length; i++) {
|
|
108
|
-
var key = localStorage.key(i);
|
|
109
|
-
if (key && key.indexOf('.idToken') !== -1) return localStorage.getItem(key);
|
|
110
|
-
}
|
|
111
|
-
} catch(e) {}
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function rebuild(ownerId, btn) {
|
|
116
|
-
btn.disabled = true;
|
|
117
|
-
btn.textContent = 'Rebuilding\u2026';
|
|
118
|
-
var headers = { 'Content-Type': 'application/json' };
|
|
119
|
-
var token = getIdToken();
|
|
120
|
-
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
121
|
-
|
|
122
|
-
fetch(window.location.pathname.replace(/\/$/, '') + '/rebuild', {
|
|
123
|
-
method: 'POST',
|
|
124
|
-
headers: headers,
|
|
125
|
-
body: JSON.stringify({ owner_id: ownerId })
|
|
126
|
-
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
127
|
-
btn.textContent = 'Recreate SQLite';
|
|
128
|
-
btn.disabled = false;
|
|
129
|
-
var flash = document.getElementById('flash');
|
|
130
|
-
if (data.status === 'ok') {
|
|
131
|
-
flash.className = 'success';
|
|
132
|
-
flash.textContent = 'Rebuild triggered for ' + ownerId;
|
|
133
|
-
flash.style.display = 'block';
|
|
134
|
-
} else {
|
|
135
|
-
flash.className = 'error';
|
|
136
|
-
flash.textContent = data.error || 'Rebuild failed';
|
|
137
|
-
flash.style.display = 'block';
|
|
138
|
-
}
|
|
139
|
-
}).catch(function(e) {
|
|
140
|
-
btn.textContent = 'Recreate SQLite';
|
|
141
|
-
btn.disabled = false;
|
|
142
|
-
var flash = document.getElementById('flash');
|
|
143
|
-
flash.className = 'error';
|
|
144
|
-
flash.textContent = 'Request failed: ' + e.message;
|
|
145
|
-
flash.style.display = 'block';
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
</script>
|
|
149
|
-
</body>
|
|
150
|
-
</html>
|
data/lib/s3arch/dashboard.rb
DELETED