litestream_rails 0.1.0 → 0.3.0
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/README.md +172 -13
- data/app/controllers/litestream_rails/application_controller.rb +7 -0
- data/app/controllers/litestream_rails/processes_controller.rb +9 -0
- data/app/views/layouts/litestream_rails/_style.html +269 -612
- data/app/views/layouts/litestream_rails/application.html.erb +3 -3
- data/app/views/litestream_rails/processes/show.html.erb +121 -0
- data/config/routes.rb +2 -2
- data/lib/litestream_rails/version.rb +1 -1
- data/lib/litestream_rails.rb +58 -13
- metadata +8 -8
- data/app/controllers/litestream_rails/databases_controller.rb +0 -32
- data/app/views/litestream_rails/databases/index.html.erb +0 -85
@@ -7,12 +7,12 @@
|
|
7
7
|
|
8
8
|
<%= render "layouts/litestream_rails/style" %>
|
9
9
|
</head>
|
10
|
-
<body class="h-full flex flex-col
|
11
|
-
<main class="
|
10
|
+
<body class="h-full flex flex-col">
|
11
|
+
<main class="container mx-auto max-w-4xl mt-4 px-2 grow">
|
12
12
|
<%= content_for?(:content) ? yield(:content) : yield %>
|
13
13
|
</main>
|
14
14
|
|
15
|
-
<footer class="container mx-auto
|
15
|
+
<footer class="container mx-auto mt-24 flex items-center justify-between border-t px-2 py-4 text-base">
|
16
16
|
<p>
|
17
17
|
<code><strong>Litestream</strong> <em>Rails</em></code> |
|
18
18
|
Made by <a href="https://twitter.com/fractaledmind" class="text-blue-500 hover:underline decoration-blue-500">@fractaledmind</a> and <a href="https://github.com/fractaledmind/litestream_rails/graphs/contributors" class="text-blue-500 hover:underline decoration-blue-500">friends</a>! Want to help? It's <a href="https://github.com/fractaledmind/litestream_rails" class="text-blue-500 hover:underline decoration-blue-500">open source</a>!
|
@@ -0,0 +1,121 @@
|
|
1
|
+
<section id="process_<%= @process[:pid] %>" class="space-y-6">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<h1 class="flex items-baseline gap-2 text-2xl font-bold">
|
4
|
+
Litestream
|
5
|
+
|
6
|
+
<% if @process[:status] == "sleeping" %>
|
7
|
+
<small class="inline-flex rounded-full px-2 text-sm font-semibold bg-yellow-100 text-yellow-800">
|
8
|
+
<%= @process[:status] %>
|
9
|
+
</small>
|
10
|
+
<% elsif @process[:status] %>
|
11
|
+
<small class="inline-flex rounded-full px-2 text-sm font-semibold bg-green-100 text-green-800">
|
12
|
+
<%= @process[:status] %>
|
13
|
+
</small>
|
14
|
+
<% else %>
|
15
|
+
<small class="inline-flex rounded-full px-2 text-sm font-semibold bg-red-100 text-red-800">
|
16
|
+
not running
|
17
|
+
</small>
|
18
|
+
<% end %>
|
19
|
+
</h1>
|
20
|
+
|
21
|
+
<% if @process[:status] %>
|
22
|
+
<small class="text-base">
|
23
|
+
#<code><%= @process[:pid] %></code>
|
24
|
+
</small>
|
25
|
+
<% end %>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<% if @process[:status] %>
|
29
|
+
<dl class="grid grid-cols-[fit-content(100%)_1fr] gap-x-4">
|
30
|
+
<dt class="font-bold">Started at</dt>
|
31
|
+
<dd class="">
|
32
|
+
<abbr title="<%= @process[:started] %>" class="underline decoration-dashed decoration-gray-500 cursor-help">
|
33
|
+
<time datetime="<%= @process[:started] %>"><%= @process[:started].to_formatted_s(:db) %></time>
|
34
|
+
</abbr>
|
35
|
+
</dd>
|
36
|
+
</dl>
|
37
|
+
<% end %>
|
38
|
+
</section>
|
39
|
+
<br>
|
40
|
+
<br>
|
41
|
+
|
42
|
+
<section id="generations" class="">
|
43
|
+
<div class="mb-3 flex items-center justify-between border-b">
|
44
|
+
<h2 class="text-2xl font-bold">Databases</h2>
|
45
|
+
<p class="text-right">Total: <strong><%= @databases.size %></strong></p>
|
46
|
+
</div>
|
47
|
+
|
48
|
+
<ul class="list-[square] list-outside ml-4">
|
49
|
+
<% @databases.each do |database| %>
|
50
|
+
<li>
|
51
|
+
<div class="flex items-center justify-between">
|
52
|
+
<h2 class="text-lg font-bold">
|
53
|
+
<code><%= database['path'] %></code>
|
54
|
+
</h2>
|
55
|
+
<button type="button" class="rounded-md bg-slate-800 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-slate-700">Restore</button>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<br />
|
59
|
+
<section id="generations" class="ml-6">
|
60
|
+
<% database['generations'].each do |generation| %>
|
61
|
+
<details id="<%= generation['generation'] %>" open="open">
|
62
|
+
<summary class="cursor-pointer rounded p-2 hover:bg-gray-50">
|
63
|
+
<code><%= generation['generation'] %></code>
|
64
|
+
(<em><%= generation['lag'] %> lag</em>)
|
65
|
+
</summary>
|
66
|
+
|
67
|
+
<dl class="ml-7 grid grid-cols-[fit-content(100%)_1fr] gap-x-4">
|
68
|
+
<dt class="font-bold">Start</dt>
|
69
|
+
<dd class="">
|
70
|
+
<abbr title="<%= generation['start'] %>" class="underline decoration-dashed decoration-gray-500 cursor-help">
|
71
|
+
<time datetime="<%= generation['start'] %>"><%= DateTime.parse(generation['start']).to_formatted_s(:db) %></time>
|
72
|
+
</abbr>
|
73
|
+
</dd>
|
74
|
+
|
75
|
+
<dt class="font-bold">End</dt>
|
76
|
+
<dd class="">
|
77
|
+
<abbr title="<%= generation['end'] %>" class="underline decoration-dashed decoration-gray-500 cursor-help">
|
78
|
+
<time datetime="<%= generation['end'] %>"><%= DateTime.parse(generation['end']).to_formatted_s(:db) %></time>
|
79
|
+
</abbr>
|
80
|
+
</dd>
|
81
|
+
|
82
|
+
<div class="col-span-2">
|
83
|
+
<dt class="font-bold">Snapshots</dt>
|
84
|
+
<dd class="">
|
85
|
+
<table class="min-w-full divide-y divide-gray-300">
|
86
|
+
<thead>
|
87
|
+
<tr>
|
88
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-left text-sm font-semibold text-gray-900">Created at</th>
|
89
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900">Index</th>
|
90
|
+
<th scope="col" class="whitespace-nowrap px-2 py-2 text-right text-sm font-semibold text-gray-900">Size</th>
|
91
|
+
</tr>
|
92
|
+
</thead>
|
93
|
+
|
94
|
+
<tbody class="bg-white">
|
95
|
+
<% generation['snapshots'].each do |snapshot| %>
|
96
|
+
<tr class="align-top even:bg-gray-50">
|
97
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900">
|
98
|
+
<abbr title="<%= snapshot['created'] %>" class="underline decoration-dashed decoration-gray-500 cursor-help">
|
99
|
+
<time datetime="<%= snapshot['created'] %>"><%= DateTime.parse(snapshot['created']).to_formatted_s(:db) %></time>
|
100
|
+
</abbr>
|
101
|
+
</td>
|
102
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 text-right">
|
103
|
+
<%= snapshot['index'] %>
|
104
|
+
</td>
|
105
|
+
<td scope="col" class="whitespace-nowrap px-2 py-2 text-sm text-gray-900 text-right">
|
106
|
+
<%= number_to_human_size snapshot['size'] %>
|
107
|
+
</td>
|
108
|
+
</tr>
|
109
|
+
<% end %>
|
110
|
+
</tbody>
|
111
|
+
</table>
|
112
|
+
</dd>
|
113
|
+
</div>
|
114
|
+
</dl>
|
115
|
+
</details>
|
116
|
+
<% end %>
|
117
|
+
</section>
|
118
|
+
</li>
|
119
|
+
<% end %>
|
120
|
+
</ul>
|
121
|
+
</section>
|
data/config/routes.rb
CHANGED
data/lib/litestream_rails.rb
CHANGED
@@ -10,25 +10,77 @@ module LitestreamRails
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
# use method instead of attr_accessor to ensure
|
13
|
-
# this works if variable set after
|
13
|
+
# this works if variable set after LitestreamRails is loaded
|
14
14
|
def username
|
15
15
|
@username ||= ENV["LITESTREAMRAILS_USERNAME"] || @@username
|
16
16
|
end
|
17
17
|
|
18
18
|
# use method instead of attr_accessor to ensure
|
19
|
-
# this works if variable set after
|
19
|
+
# this works if variable set after LitestreamRails is loaded
|
20
20
|
def password
|
21
21
|
@password ||= ENV["LITESTREAMRAILS_PASSWORD"] || @@password
|
22
22
|
end
|
23
23
|
|
24
|
+
def replicate_process
|
25
|
+
info = {}
|
26
|
+
if !`which systemctl`.empty?
|
27
|
+
systemctl_status = `systemctl status litestream`.chomp
|
28
|
+
# ["● litestream.service - Litestream",
|
29
|
+
# " Loaded: loaded (/lib/systemd/system/litestream.service; enabled; vendor preset: enabled)",
|
30
|
+
# " Active: active (running) since Tue 2023-07-25 13:49:43 UTC; 8 months 24 days ago",
|
31
|
+
# " Main PID: 1179656 (litestream)",
|
32
|
+
# " Tasks: 9 (limit: 1115)",
|
33
|
+
# " Memory: 22.9M",
|
34
|
+
# " CPU: 10h 49.843s",
|
35
|
+
# " CGroup: /system.slice/litestream.service",
|
36
|
+
# " └─1179656 /usr/bin/litestream replicate",
|
37
|
+
# "",
|
38
|
+
# "Warning: some journal files were not opened due to insufficient permissions."]
|
39
|
+
systemctl_status.split("\n").each do |line|
|
40
|
+
line.strip!
|
41
|
+
if line.start_with?("Main PID:")
|
42
|
+
_key, value = line.split(":")
|
43
|
+
pid, _name = value.strip.split(" ")
|
44
|
+
info[:pid] = pid
|
45
|
+
elsif line.start_with?("Active:")
|
46
|
+
_key, value = line.split(":")
|
47
|
+
value, _ago = value.split(";")
|
48
|
+
status, timestamp = value.split(" since ")
|
49
|
+
info[:started] = DateTime.strptime(timestamp.strip, "%Y-%m-%d %H:%M:%S %Z")
|
50
|
+
info[:status] = status.split("(").first.strip
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
litestream_replicate_ps = `ps -a | grep litestream | grep replicate`.chomp
|
55
|
+
litestream_replicate_ps.split("\n").each do |line|
|
56
|
+
next unless line.include?("litestream replicate")
|
57
|
+
pid, * = line.split(" ")
|
58
|
+
info[:pid] = pid
|
59
|
+
state, _, lstart = `ps -o "state,lstart" #{pid}`.chomp.split("\n").last.partition(/\s+/)
|
60
|
+
|
61
|
+
info[:status] = case state[0]
|
62
|
+
when "I" then "idle"
|
63
|
+
when "R" then "running"
|
64
|
+
when "S" then "sleeping"
|
65
|
+
when "T" then "stopped"
|
66
|
+
when "U" then "uninterruptible"
|
67
|
+
when "Z" then "zombie"
|
68
|
+
end
|
69
|
+
info[:started] = DateTime.strptime(lstart.strip, "%a %b %d %H:%M:%S %Y")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
info
|
73
|
+
end
|
74
|
+
|
24
75
|
def databases
|
25
|
-
databases =
|
76
|
+
databases = Litestream::Commands.databases
|
26
77
|
|
27
78
|
databases.each do |db|
|
28
|
-
generations =
|
29
|
-
snapshots =
|
79
|
+
generations = Litestream::Commands.generations(db["path"])
|
80
|
+
snapshots = Litestream::Commands.snapshots(db["path"])
|
81
|
+
db["path"] = db["path"].gsub(Rails.root.to_s, "[ROOT]")
|
30
82
|
|
31
|
-
db[
|
83
|
+
db["generations"] = generations.map do |generation|
|
32
84
|
id = generation["generation"]
|
33
85
|
replica = generation["name"]
|
34
86
|
generation["snapshots"] = snapshots.select { |snapshot| snapshot["generation"] == id && snapshot["replica"] == replica }
|
@@ -37,12 +89,5 @@ module LitestreamRails
|
|
37
89
|
end
|
38
90
|
end
|
39
91
|
end
|
40
|
-
|
41
|
-
private
|
42
|
-
|
43
|
-
def text_table_to_hashes(string)
|
44
|
-
keys, *rows = string.split("\n").map { _1.split(/\s+/) }
|
45
|
-
rows.map { keys.zip(_1).to_h }
|
46
|
-
end
|
47
92
|
end
|
48
93
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: litestream_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Margheim
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: litestream
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.6.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.6.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: actionpack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,10 +90,10 @@ files:
|
|
90
90
|
- README.md
|
91
91
|
- Rakefile
|
92
92
|
- app/controllers/litestream_rails/application_controller.rb
|
93
|
-
- app/controllers/litestream_rails/
|
93
|
+
- app/controllers/litestream_rails/processes_controller.rb
|
94
94
|
- app/views/layouts/litestream_rails/_style.html
|
95
95
|
- app/views/layouts/litestream_rails/application.html.erb
|
96
|
-
- app/views/litestream_rails/
|
96
|
+
- app/views/litestream_rails/processes/show.html.erb
|
97
97
|
- config/routes.rb
|
98
98
|
- lib/litestream_rails.rb
|
99
99
|
- lib/litestream_rails/engine.rb
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module LitestreamRails
|
2
|
-
class DatabasesController < ApplicationController
|
3
|
-
around_action :force_english_locale!
|
4
|
-
|
5
|
-
# GET /databases
|
6
|
-
def index
|
7
|
-
@databases = LitestreamRails.databases
|
8
|
-
[{"path"=>"/Users/fractaled/Code/Playgrounds/wrocloverb/storage/production.sqlite3",
|
9
|
-
"replicas"=>"s3",
|
10
|
-
"generations"=>
|
11
|
-
[{"generation"=>"5f4341bc3d22d615",
|
12
|
-
"name"=>"s3",
|
13
|
-
"lag"=>"3s",
|
14
|
-
"start"=>"2024-04-17T19:48:09Z",
|
15
|
-
"end"=>"2024-04-17T19:48:09Z",
|
16
|
-
"snapshots"=>[{"index"=>"0", "size"=>"4645465", "created"=>"2024-04-17T19:48:09Z"}]},
|
17
|
-
{"generation"=>"a184b05a685589f3",
|
18
|
-
"name"=>"s3",
|
19
|
-
"lag"=>"19h46m52s",
|
20
|
-
"start"=>"2024-04-17T00:01:19Z",
|
21
|
-
"end"=>"2024-04-17T00:01:19Z",
|
22
|
-
"snapshots"=>[{"index"=>"1", "size"=>"4645465", "created"=>"2024-04-17T00:01:19Z"}]}]}]
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def force_english_locale!(&action)
|
28
|
-
I18n.with_locale(:en, &action)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
@@ -1,85 +0,0 @@
|
|
1
|
-
<ul>
|
2
|
-
<% @databases.each do |database| %>
|
3
|
-
<li>
|
4
|
-
<section id="error_12" class="space-y-6">
|
5
|
-
<div class="flex items-center justify-between">
|
6
|
-
<h1 class="flex items-center gap-2 text-2xl font-bold">
|
7
|
-
<code><%= database['path'] %></code>
|
8
|
-
</h1>
|
9
|
-
</div>
|
10
|
-
</section>
|
11
|
-
<br />
|
12
|
-
<section id="generations" class="-mx-2 mt-4 sm:mx-0 sm:rounded-md">
|
13
|
-
<div class="mb-3 flex items-center justify-between border-b">
|
14
|
-
<h2 class="text-2xl font-bold">Generations</h2>
|
15
|
-
<p class="text-right">Total: <strong><%= database['generations'].size %></strong></p>
|
16
|
-
</div>
|
17
|
-
|
18
|
-
<div class="min-w-full">
|
19
|
-
<% database['generations'].each do |generation| %>
|
20
|
-
<section id="<%= generation['generation'] %>" class="">
|
21
|
-
<details open="open">
|
22
|
-
<summary class="cursor-pointer rounded p-2 hover:bg-gray-50">
|
23
|
-
<code><%= generation['generation'] %></code>
|
24
|
-
(<em><%= generation['lag'] %> lag</em>)
|
25
|
-
</summary>
|
26
|
-
<div class="">
|
27
|
-
<dl class="grid flex-1 grid-cols-2 gap-x-4">
|
28
|
-
<div class="flex flex-wrap items-center justify-between gap-x-2">
|
29
|
-
<dt class="font-bold">Start</dt>
|
30
|
-
<dd class="inline-flex items-center gap-1">
|
31
|
-
<abbr title="<%= generation['start'] %>" class="cursor-help">
|
32
|
-
<time datetime="<%= generation['start'] %>"><%= DateTime.parse(generation['start']).to_formatted_s(:db) %></time>
|
33
|
-
</abbr>
|
34
|
-
</dd>
|
35
|
-
</div>
|
36
|
-
<div class="flex flex-wrap items-center justify-between gap-x-2">
|
37
|
-
<dt class="font-bold">End</dt>
|
38
|
-
<dd class="inline-flex items-center gap-1">
|
39
|
-
<abbr title="<%= generation['end'] %>" class="cursor-help">
|
40
|
-
<time datetime="<%= generation['end'] %>"><%= DateTime.parse(generation['end']).to_formatted_s(:db) %></time>
|
41
|
-
</abbr>
|
42
|
-
</dd>
|
43
|
-
</div>
|
44
|
-
<div class="col-span-2">
|
45
|
-
<dt class="font-bold">Snapshots</dt>
|
46
|
-
<dd class="">
|
47
|
-
<table class="min-w-full divide-y divide-gray-300">
|
48
|
-
<thead>
|
49
|
-
<tr>
|
50
|
-
<th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-3">Created</th>
|
51
|
-
<th scope="col" class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900">Index</th>
|
52
|
-
<th scope="col" class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900">Size</th>
|
53
|
-
</tr>
|
54
|
-
</thead>
|
55
|
-
|
56
|
-
<tbody class="bg-white">
|
57
|
-
<% generation['snapshots'].each do |snapshot| %>
|
58
|
-
<tr class="align-top even:bg-gray-50">
|
59
|
-
<td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-left text-gray-500">
|
60
|
-
<abbr title="<%= snapshot['created'] %>" class="cursor-help">
|
61
|
-
<time datetime="<%= snapshot['created'] %>"><%= DateTime.parse(snapshot['created']).to_formatted_s(:db) %></time>
|
62
|
-
</abbr>
|
63
|
-
</td>
|
64
|
-
<td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-right text-gray-500">
|
65
|
-
<%= snapshot['index'] %>
|
66
|
-
</td>
|
67
|
-
<td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-right text-gray-500">
|
68
|
-
<%= number_to_human_size snapshot['size'] %>
|
69
|
-
</td>
|
70
|
-
</tr>
|
71
|
-
<% end %>
|
72
|
-
</tbody>
|
73
|
-
</table>
|
74
|
-
</dd>
|
75
|
-
</div>
|
76
|
-
</dl>
|
77
|
-
</div>
|
78
|
-
</details>
|
79
|
-
</section>
|
80
|
-
<% end %>
|
81
|
-
</div>
|
82
|
-
</section>
|
83
|
-
</li>
|
84
|
-
<% end %>
|
85
|
-
</ul>
|