litestream_rails 0.1.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.
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html class="h-full">
3
+ <head>
4
+ <title>Litestream Rails</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= render "layouts/litestream_rails/style" %>
9
+ </head>
10
+ <body class="h-full flex flex-col pb-4">
11
+ <main class="grow container mx-auto mt-4">
12
+ <%= content_for?(:content) ? yield(:content) : yield %>
13
+ </main>
14
+
15
+ <footer class="container mx-auto py-4 mt-24 text-base flex justify-between items-center border-t">
16
+ <p>
17
+ <code><strong>Litestream</strong> <em>Rails</em></code>&nbsp;&nbsp;|&nbsp;
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>!
19
+ </p>
20
+ </footer>
21
+
22
+ <div class="fixed top-0 left-0 right-0 text-center py-2">
23
+ <% if notice.present? %>
24
+ <p id="notice"
25
+ class="py-2 px-3 bg-green-50 text-green-500 font-medium rounded-lg inline-block"
26
+ data-controller="fade">
27
+ <%= notice %>
28
+ </p>
29
+ <% end %>
30
+
31
+ <% if alert.present? %>
32
+ <p id="alert"
33
+ class="py-2 px-3 bg-red-50 text-red-500 font-medium rounded-lg inline-block"
34
+ data-controller="fade">
35
+ <%= alert %>
36
+ </p>
37
+ <% end %>
38
+ </div>
39
+
40
+ <script nonce="<%= content_security_policy_nonce %>">
41
+ function fadeOut(element) {
42
+ element.classList.add('transition-opacity')
43
+ setTimeout(
44
+ () => {
45
+ element.classList.add('opacity-0')
46
+ element.remove()
47
+ },
48
+ 2000
49
+ )
50
+ }
51
+ document.querySelectorAll('[data-controller="fade"]').forEach(element => {
52
+ fadeOut(element);
53
+ });
54
+ </script>
55
+ </body>
56
+ </html>
@@ -0,0 +1,85 @@
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>
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ LitestreamRails::Engine.routes.draw do
2
+ get "/" => "databases#index", :as => :root
3
+
4
+ resources :databases, only: [:index], path: ""
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LitestreamRails
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace LitestreamRails
6
+
7
+ config.litestream_rails = ActiveSupport::OrderedOptions.new
8
+
9
+ initializer "litestream_rails.config" do
10
+ config.litestream_rails.each do |name, value|
11
+ LitestreamRails.public_send(:"#{name}=", value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LitestreamRails
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "litestream_rails/version"
4
+ require_relative "litestream_rails/engine"
5
+ require "litestream"
6
+
7
+ module LitestreamRails
8
+ mattr_writer :username
9
+ mattr_writer :password
10
+
11
+ class << self
12
+ # use method instead of attr_accessor to ensure
13
+ # this works if variable set after SolidErrors is loaded
14
+ def username
15
+ @username ||= ENV["LITESTREAMRAILS_USERNAME"] || @@username
16
+ end
17
+
18
+ # use method instead of attr_accessor to ensure
19
+ # this works if variable set after SolidErrors is loaded
20
+ def password
21
+ @password ||= ENV["LITESTREAMRAILS_PASSWORD"] || @@password
22
+ end
23
+
24
+ def databases
25
+ databases = text_table_to_hashes(Litestream::Commands.databases(async: false))
26
+
27
+ databases.each do |db|
28
+ generations = text_table_to_hashes(Litestream::Commands.generations(db["path"], async: false))
29
+ snapshots = text_table_to_hashes(Litestream::Commands.snapshots(db["path"], async: false))
30
+
31
+ db['generations'] = generations.map do |generation|
32
+ id = generation["generation"]
33
+ replica = generation["name"]
34
+ generation["snapshots"] = snapshots.select { |snapshot| snapshot["generation"] == id && snapshot["replica"] == replica }
35
+ .map { |s| s.slice("index", "size", "created") }
36
+ generation.slice("generation", "name", "lag", "start", "end", "snapshots")
37
+ end
38
+ end
39
+ 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
+ end
48
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: litestream_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Margheim
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: litestream
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: actionpack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '7.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '7.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionview
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: railties
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '7.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '7.0'
83
+ description:
84
+ email:
85
+ - stephen.margheim@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - README.md
91
+ - Rakefile
92
+ - app/controllers/litestream_rails/application_controller.rb
93
+ - app/controllers/litestream_rails/databases_controller.rb
94
+ - app/views/layouts/litestream_rails/_style.html
95
+ - app/views/layouts/litestream_rails/application.html.erb
96
+ - app/views/litestream_rails/databases/index.html.erb
97
+ - config/routes.rb
98
+ - lib/litestream_rails.rb
99
+ - lib/litestream_rails/engine.rb
100
+ - lib/litestream_rails/version.rb
101
+ homepage: https://github.com/fractaledmind/litestream_rails
102
+ licenses:
103
+ - MIT
104
+ metadata:
105
+ homepage_uri: https://github.com/fractaledmind/litestream_rails
106
+ source_code_uri: https://github.com/fractaledmind/litestream_rails
107
+ changelog_uri: https://github.com/fractaledmind/litestream_rails/blob/main/CHANGELOG.md
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 3.0.0
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubygems_version: 3.5.1
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Rails integration for Litestream.
127
+ test_files: []