pcelka 1.0.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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/app.rb +95 -0
  3. data/assets/css/styles.css +209 -0
  4. data/assets/js/app.js +16 -0
  5. data/assets/js/hotkey.js +27 -0
  6. data/bin/dev +9 -0
  7. data/bin/dev-puma +8 -0
  8. data/bin/pcelka +16 -0
  9. data/bin/pcelka-console +16 -0
  10. data/config/datastar.rb +16 -0
  11. data/config/falcon_init.rb +14 -0
  12. data/config.ru +30 -0
  13. data/db/migrations/001_create_logs.rb +11 -0
  14. data/db.rb +3 -0
  15. data/examples/procfile/Procfile +7 -0
  16. data/examples/procfile/Procfile.without_colon +2 -0
  17. data/examples/procfile/error +7 -0
  18. data/examples/procfile/log/neverdie.log +4 -0
  19. data/examples/procfile/oneshot +3 -0
  20. data/examples/procfile/single_call +10 -0
  21. data/examples/procfile/spawnee +14 -0
  22. data/examples/procfile/spawner +7 -0
  23. data/examples/procfile/ticker +14 -0
  24. data/examples/procfile/utf8 +11 -0
  25. data/i18n/en.yml +32 -0
  26. data/i18n/ru.yml +32 -0
  27. data/lib/pcelka/async_client.rb +51 -0
  28. data/lib/pcelka/logs_collector.rb +37 -0
  29. data/lib/pcelka/program.rb +79 -0
  30. data/lib/pcelka/server/commandable.rb +57 -0
  31. data/lib/pcelka/server/controllable.rb +45 -0
  32. data/lib/pcelka/server/notifiable.rb +25 -0
  33. data/lib/pcelka/server/reportable.rb +61 -0
  34. data/lib/pcelka/server/writing.rb +28 -0
  35. data/lib/pcelka/server.rb +66 -0
  36. data/lib/pcelka/spec/procfile.rb +46 -0
  37. data/lib/pcelka/spec/program_spec.rb +3 -0
  38. data/lib/pcelka/spec.rb +11 -0
  39. data/lib/pcelka/writer/console_writer.rb +60 -0
  40. data/lib/pcelka/writer/generic_writer.rb +16 -0
  41. data/lib/pcelka/writer.rb +25 -0
  42. data/lib/pcelka.rb +5 -0
  43. data/models.rb +21 -0
  44. data/pcelka.rb +22 -0
  45. data/public/android-chrome-192x192.png +0 -0
  46. data/public/android-chrome-512x512.png +0 -0
  47. data/public/apple-touch-icon.png +0 -0
  48. data/public/favicon-16x16.png +0 -0
  49. data/public/favicon-32x32.png +0 -0
  50. data/public/favicon.ico +0 -0
  51. data/public/site.webmanifest +1 -0
  52. data/public/sound/bee.ogg +0 -0
  53. data/public/vendor/datastar.js +9 -0
  54. data/public/vendor/datastar.js.map +7 -0
  55. data/views/home/action.erb +10 -0
  56. data/views/home/report.erb +36 -0
  57. data/views/home.erb +18 -0
  58. data/views/layout.erb +20 -0
  59. data/views/logs/log_line.erb +13 -0
  60. data/views/logs.erb +16 -0
  61. data/views/nav/arrow.erb +21 -0
  62. metadata +282 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5308b1ba45b7cde58d91393fcd8fc53d33c96da155d3390517aa0ad023566b05
4
+ data.tar.gz: 10a0e41949e6969a919194e9ee6987a6ce12aa7b9cbae031570eea53244af1d8
5
+ SHA512:
6
+ metadata.gz: 41f7faf44501726cbd0c34dc9ef0066d04f72121f420c6192fb331585d03178b891d593790c8c149e8d4cd5be0a7f0a89af1c555556262f71d8d843f13440bd0
7
+ data.tar.gz: 430900b2828d4f3e7903d407323be6ee817d5400fbc9834c7d732ef6dfa447a9f9251fc35fbea2a28fac8d2f6ac36c669ffa88969b8ed88eb0d2b7f3de694f9d
data/app.rb ADDED
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+ require "roda"
3
+ require "datastar"
4
+
5
+ class App < Roda
6
+ plugin :render, escape: true, assume_fixed_locals: true,
7
+ template_opts: {
8
+ scope_class: self,
9
+ freeze: true,
10
+ extract_fixed_locals: true,
11
+ default_fixed_locals: "()",
12
+ chain_appends: true,
13
+ skip_compiled_encoding_detection: true,
14
+ }
15
+ plugin :assets, css: "styles.css", js: %w"app.js hotkey.js"
16
+ plugin :symbol_views
17
+ plugin :public
18
+ plugin :part
19
+ plugin :head
20
+ plugin :i18n, locale: %w"en ru"
21
+
22
+ R18n.set("ru")
23
+
24
+ route do |r|
25
+ r.public
26
+ r.assets
27
+
28
+ ds = Datastar.from_rack_env(env)
29
+
30
+ r.root do
31
+ @report = PCELKA << :report
32
+ if ds.sse?
33
+ ds.stream do |sse|
34
+ sse.patch_elements part("home/report", report: @report)
35
+ while PCELKA.programs_status_changed?
36
+ report = PCELKA << :report
37
+ sse.patch_elements part("home/report", report:)
38
+ end
39
+ end
40
+ else
41
+ :home
42
+ end
43
+ end
44
+
45
+ r.get "logs" do
46
+ last_id = ds.signals["log_last_id"] || 0
47
+ ds.stream do |sse|
48
+ LOGS_COLLECTOR.retrieve_new_logs(last_id) do |log|
49
+ sse.patch_elements \
50
+ part("logs/log_line", log:),
51
+ mode: "append",
52
+ selector: "#logs-area"
53
+ sse.patch_signals log_last_id: log[:id]
54
+ end
55
+ end
56
+ end
57
+
58
+ r.on "programs" do
59
+ program_action_response = lambda do
60
+ if ds.sse?
61
+ ""
62
+ else
63
+ r.redirect "/"
64
+ end
65
+ end
66
+
67
+ r.post "start_all" do
68
+ PCELKA << :start_all
69
+ program_action_response.call
70
+ end
71
+
72
+ r.post "stop_all" do
73
+ PCELKA << :stop_all
74
+ program_action_response.call
75
+ end
76
+
77
+ r.on String do |program_id|
78
+ r.post "start" do
79
+ PCELKA << [:start, program_id]
80
+ program_action_response.call
81
+ end
82
+
83
+ r.post "stop" do
84
+ PCELKA << [:stop, program_id]
85
+ program_action_response.call
86
+ end
87
+
88
+ r.post "restart" do
89
+ PCELKA << [:restart, program_id]
90
+ program_action_response.call
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,209 @@
1
+ @layer base, main, component, utility;
2
+
3
+ @layer base {
4
+ body {
5
+ margin: 0;
6
+ }
7
+ }
8
+
9
+ @layer main {
10
+ :root {
11
+ --s1: .25rem;
12
+ --s2: .5rem;
13
+ --s3: 1rem;
14
+ --s4: 2rem;
15
+ --s5: 4rem;
16
+
17
+ --lch-brown: 10% 0.4 65;
18
+ --lch-yellow: 100% 0.2 55;
19
+ --lch-yellow-light: 100% 0.02 55;
20
+ --lch-orange: 100% 0.4 65;
21
+ --lch-orange-dark: 93% 0.4 65;
22
+
23
+ --color-bg: oklch(var(--lch-yellow));
24
+ --color-text: oklch(var(--lch-brown));
25
+ --color-logs-bg: oklch(var(--lch-yellow-light));
26
+ --color-button-bg: oklch(var(--lch-orange));
27
+ --color-border: oklch(var(--lch-brown));
28
+ --color-scrollbar-thumb: oklch(var(--lch-orange-dark));
29
+ --color-input-bg: oklch(var(--lch-yellow-light));
30
+ --color-input-text: oklch(var(--lch-brown));
31
+ --color-outline: oklch(var(--lch-orange-dark));
32
+
33
+ /* svg.gsub(%("), "'").gsub("<", "%3C").gsub(">", "%3E").gsub(/\n/, "") */
34
+ --icon-checkbox: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-24 5 48 57' fill='white'%3E%3Cellipse cx='0' cy='38' rx='9' ry='14' /%3E%3Cellipse cx='0' cy='20' rx='5' ry='6' /%3E%3Cellipse cx='-14' cy='26' rx='10' ry='4' /%3E%3Cellipse cx='14' cy='26' rx='10' ry='4' /%3E%3Cpolyline points='-2,15 -4,12 -6,12' fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' /%3E%3Cpolyline points='2,15 4,12 6,12' fill='none' stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' /%3E%3C/svg%3E");
35
+
36
+ scroll-behavior: smooth;
37
+ scrollbar-color: var(--color-scrollbar-thumb) var(--color-bg);
38
+ scrollbar-gutter: stable;
39
+ }
40
+
41
+ body {
42
+ background-color: var(--color-bg);
43
+ color: var(--color-text);
44
+ }
45
+
46
+ main {
47
+ margin: 0 0 1rem 1rem;
48
+ width: calc(100% - 6rem);
49
+ }
50
+
51
+ #pcelka-h1 {
52
+ display: inline-block;
53
+ cursor: pointer;
54
+ }
55
+
56
+ #logs {
57
+ --controls-height: min(2rem, 10vh);
58
+
59
+ #logs-controls {
60
+ padding-top: 1rem;
61
+ padding-bottom: .5rem;
62
+ height: var(--controls-height);
63
+ display: flex;
64
+ align-items: center;
65
+ }
66
+ #logs-area {
67
+ height: calc(100vh - var(--controls-height) - 2.5rem);
68
+ overflow-y: scroll;
69
+ width: 100%;
70
+ border: 1px solid var(--color-border);
71
+ font-family: monospace;
72
+ background-color: var(--color-logs-bg);
73
+ scrollbar-color: var(--color-scrollbar-thumb) var(--color-logs-bg);
74
+ }
75
+ }
76
+
77
+ #report {
78
+ .actions {
79
+ display: flex;
80
+ gap: var(--s2);
81
+ }
82
+
83
+ .status-column {
84
+ min-width: 20ch;
85
+ }
86
+ }
87
+
88
+ nav#aside-nav {
89
+ position: fixed;
90
+ right: 1rem;
91
+ top: 0;
92
+ height: 100%;
93
+ display: flex;
94
+ flex-direction: column;
95
+ justify-content: space-around;
96
+
97
+ .nav-arrow {
98
+ width: 3rem;
99
+ cursor: pointer;
100
+
101
+ svg {
102
+ height: 100%;
103
+ width: 100%;
104
+ --color-brown: oklch(var(--lch-brown));
105
+ --color-orange: oklch(var(--lch-orange-dark));
106
+ }
107
+ }
108
+ }
109
+
110
+ *:focus-visible {
111
+ outline: 2px solid var(--color-outline);
112
+ }
113
+ }
114
+
115
+ @layer component {
116
+ input[type=submit], input[type=button], button {
117
+ padding: var(--s2) var(--s3);
118
+ border-radius: 0;
119
+ border: none;
120
+ cursor: pointer;
121
+ font-size: 1em;
122
+ background-color: var(--color-button-bg);
123
+ color: var(--color-text);
124
+
125
+ &:hover {
126
+ background-color: oklch(from var(--color-button-bg) calc(l - 0.07) c h);
127
+ color: oklch(from var(--color-text) calc(l - 0.02) c h);
128
+ }
129
+
130
+ &:active {
131
+ background-color: oklch(from var(--color-button-bg) calc(l - 0.03) c h);
132
+ color: var(--color-text);
133
+ }
134
+
135
+ &:disabled {
136
+ cursor: unset;
137
+ background-color: oklch(from var(--color-button-bg) l c h / 0.5);
138
+ color: oklch(from var(--color-text) l c h / 0.65);
139
+ }
140
+
141
+ &:focus-visible {
142
+ --color-outline: oklch(var(--lch-brown));
143
+ }
144
+ }
145
+
146
+ input[type=text], input[type=search] {
147
+ width: 100%;
148
+ padding: .25rem .5rem;
149
+ border-radius: none;
150
+ border: 1px solid var(--color-border);
151
+ background-color: var(--color-input-bg);
152
+ color: var(--color-input-text);
153
+ }
154
+
155
+ label:has([type=checkbox], [type=radio]) {
156
+ cursor: pointer;
157
+ }
158
+ [type=checkbox],
159
+ [type=radio] {
160
+ appearance: none;
161
+ width: 1.5em;
162
+ height: 1.5em;
163
+ vertical-align: middle;
164
+ cursor: pointer;
165
+ background-color: var(--color-input-bg);
166
+ border: 1px solid var(--color-border);
167
+ }
168
+ [type=checkbox]:checked, [type=checkbox]:checked:active, [type=checkbox]:checked:focus,
169
+ [type=radio]:checked,
170
+ [type=radio]:checked:active,
171
+ [type=radio]:checked:focus {
172
+ background-image: var(--icon-checkbox);
173
+ background-color: oklch(var(--lch-orange));
174
+ background-position: center .15em;
175
+ background-size: 1em auto;
176
+ background-repeat: no-repeat;
177
+ }
178
+
179
+ table {
180
+ border-collapse: collapse;
181
+
182
+ td, th {
183
+ padding: var(--s2);
184
+ border-bottom: 1px solid var(--color-border);
185
+ }
186
+ }
187
+ }
188
+
189
+ @layer utility {
190
+ .ms1 { margin-inline-start: var(--s1); }
191
+ .ms2 { margin-inline-start: var(--s2); }
192
+ .ms3 { margin-inline-start: var(--s3); }
193
+ .ms4 { margin-inline-start: var(--s4); }
194
+ .me1 { margin-inline-end: var(--s1); }
195
+ .me2 { margin-inline-end: var(--s2); }
196
+ .me3 { margin-inline-end: var(--s3); }
197
+ .me4 { margin-inline-end: var(--s4); }
198
+ .mt1 { margin-block-start: var(--s1); }
199
+ .mt2 { margin-block-start: var(--s2); }
200
+ .mt3 { margin-block-start: var(--s3); }
201
+ .mt4 { margin-block-start: var(--s4); }
202
+ .mb1 { margin-block-end: var(--s1); }
203
+ .mb2 { margin-block-end: var(--s2); }
204
+ .mb3 { margin-block-end: var(--s3); }
205
+ .mb4 { margin-block-end: var(--s4); }
206
+ .ms-auto { margin-inline-start: auto; }
207
+ .me-auto { margin-inline-end: auto; }
208
+ .grow1 { flex-grow: 1; }
209
+ }
data/assets/js/app.js ADDED
@@ -0,0 +1,16 @@
1
+ import { addHotkeyListener } from "./hotkey.js";
2
+
3
+ window.$ = (s) => document.querySelector(s);
4
+
5
+ function clickIdIfPresent(id) {
6
+ const elem = document.getElementById(id);
7
+ if (!elem) return;
8
+ elem.click();
9
+ }
10
+
11
+ addHotkeyListener(
12
+ ["s", "ы"],
13
+ () => clickIdIfPresent("scroll-to-bottom-checkbox"),
14
+ );
15
+ addHotkeyListener(["u", "г"], () => clickIdIfPresent("nav-up"));
16
+ addHotkeyListener(["d", "в"], () => clickIdIfPresent("nav-down"));
@@ -0,0 +1,27 @@
1
+ function isEventFiredOnInput(event) {
2
+ const target = event.target;
3
+ const { tagName } = target;
4
+ const isInput = tagName === "INPUT" &&
5
+ ![
6
+ "checkbox",
7
+ "radio",
8
+ "range",
9
+ "button",
10
+ "file",
11
+ "reset",
12
+ "submit",
13
+ "color",
14
+ ].includes(target.type);
15
+
16
+ return target.isContentEditable ||
17
+ ((isInput || tagName === "TEXTAREA" || tagName === "SELECT") &&
18
+ !target.readOnly);
19
+ }
20
+
21
+ export function addHotkeyListener(keys, fn) {
22
+ if (!Array.isArray(keys)) keys = [keys];
23
+ document.addEventListener("keydown", (e) => {
24
+ if (isEventFiredOnInput(e)) return;
25
+ if (keys.includes(e.key)) fn(e);
26
+ });
27
+ }
data/bin/dev ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+ require "bundler/setup"
5
+
6
+ Dir.chdir(File.expand_path("..", __dir__)) do
7
+ Process.exec "fd . -E assets/ | entr -rz env RACK_ENV=development PCELKA_PROCFILE=examples/procfile/Procfile bundle exec falcon serve --bind http://localhost:9292 -n 1"
8
+ end
9
+
data/bin/dev-puma ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+ require "bundler/setup"
5
+
6
+ Dir.chdir(File.expand_path("..", __dir__)) do
7
+ Process.exec "env RACK_ENV=development PCELKA_PROCFILE=examples/procfile/Procfile bundle exec puma"
8
+ end
data/bin/pcelka ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
+
5
+ if ARGV[0]
6
+ ENV["PCELKA_PROCFILE"] = ARGV[0]
7
+ end
8
+ ENV["PCELKA_PROCFILE"] ||= "Procfile" if File.exist?("Procfile")
9
+
10
+ if ENV["PCELKA_PROCFILE"]
11
+ ENV["PCELKA_PROCFILE"] = File.expand_path(ENV["PCELKA_PROCFILE"], Dir.pwd)
12
+ end
13
+
14
+ Dir.chdir(File.expand_path("..", __dir__)) do
15
+ Process.exec "falcon serve --bind http://localhost:9292 -n 1"
16
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
4
+ require "bundler/setup"
5
+
6
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
7
+ require "pcelka/async_client"
8
+
9
+ procfile = ENV.fetch("PCELKA_PROCFILE",
10
+ File.expand_path("../examples/procfile/Procfile", __dir__))
11
+ pcelka = Pcelka::AsyncClient.from_procfile procfile
12
+ pcelka.server.writer.add_console_writer
13
+
14
+ th = Thread.new { pcelka.server.run }
15
+ pcelka << :start_all
16
+ th.join
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require "datastar"
3
+ require "datastar/async_executor"
4
+
5
+ # Do not silence logs!
6
+ Datastar::AsyncExecutor.prepend(Module.new do
7
+ def initialize; end
8
+ end)
9
+
10
+ Datastar.configure do |config|
11
+ config.finalize = lambda do |_view_context, response|
12
+ throw :halt, response.finish
13
+ end
14
+ config.executor = Datastar::AsyncExecutor.new
15
+ config.logger = Console
16
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined? Falcon
4
+ # https://github.com/socketry/falcon/issues/333
5
+ class Falcon::Server
6
+ alias __original_accept accept
7
+
8
+ def accept(...)
9
+ __original_accept(...)
10
+ rescue Errno::EPIPE, Errno::ECONNRESET
11
+ # Client disconnected mid-response — expected.
12
+ end
13
+ end
14
+ end
data/config.ru ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+ dev = ENV["RACK_ENV"] == "development"
3
+
4
+ require "console"
5
+ Console.logger.debug!
6
+
7
+ require_relative "config/falcon_init"
8
+ require_relative "config/datastar"
9
+
10
+ $LOAD_PATH.unshift(File.expand_path("lib", __dir__))
11
+ require_relative "pcelka"
12
+ require_relative "models"
13
+ require_relative "app"
14
+ run App.freeze.app
15
+
16
+ unless dev
17
+ Tilt.finalize!
18
+ RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
19
+ end
20
+
21
+ freeze_core = !dev
22
+ if freeze_core
23
+ begin
24
+ require "refrigerator"
25
+ rescue LoadError
26
+ else
27
+ require "nio" if defined?(Puma)
28
+ Refrigerator.freeze_core
29
+ end
30
+ end
@@ -0,0 +1,11 @@
1
+ Sequel.migration do
2
+ change do
3
+ create_table :logs do
4
+ primary_key :id
5
+ String :app, null: false
6
+ String :message, null: false, text: true
7
+ TrueClass :is_error, null: false, default: false
8
+ DateTime :created_at, null: false, default: Sequel.lit('unixepoch()')
9
+ end
10
+ end
11
+ end
data/db.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+ require "sequel/core"
3
+ DB = Sequel.sqlite
@@ -0,0 +1,7 @@
1
+ # ticker: ruby ./ticker $PORT
2
+ myerror: ruby ./error
3
+ # utf8: ruby ./utf8
4
+ # single_call: ruby ./single_call
5
+ spawner: ./spawner
6
+ # oneshot: ONESHOT=uno ./oneshot
7
+ # non-exist: non-exist
@@ -0,0 +1,2 @@
1
+ ticker ./ticker $PORT
2
+ error ./error
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true
4
+
5
+ puts "will error in 2s"
6
+ sleep 1
7
+ raise "Dying"
@@ -0,0 +1,4 @@
1
+ tick
2
+ tick
3
+ ./never_die:6:in `sleep': Interrupt
4
+ from ./never_die:6
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ echo oneshot: "$ONESHOT"
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true
4
+
5
+ puts "STARTED single_call"
6
+
7
+ while true
8
+ puts "first\nsecond\nthird"
9
+ sleep 1
10
+ end
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ NAME="$1"
4
+
5
+ sigterm() {
6
+ echo "$NAME: got sigterm"
7
+ }
8
+
9
+ #trap sigterm SIGTERM
10
+
11
+ while true; do
12
+ echo "$NAME: ping $$"
13
+ sleep 1
14
+ done
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ ./spawnee A &
4
+ ./spawnee B &
5
+ ./spawnee C &
6
+
7
+ wait
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $stdout.sync = true
4
+
5
+ %w( SIGINT SIGTERM ).each do |signal|
6
+ trap(signal) do
7
+ puts "received #{signal} but i'm ignoring it!"
8
+ end
9
+ end
10
+
11
+ while true
12
+ puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
13
+ sleep 1
14
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: BINARY
3
+
4
+ $stdout.sync = true
5
+
6
+ while true
7
+ puts "\u65e5\u672c\u8a9e\u6587\u5b57\u5217"
8
+ puts "\u0915\u0932\u094d\u0907\u0928\u0643\u0637\u0628\u041a\u0430\u043b\u0438\u043d\u0430"
9
+ puts "\xff\x03"
10
+ sleep 1
11
+ end
data/i18n/en.yml ADDED
@@ -0,0 +1,32 @@
1
+ report:
2
+ action:
3
+ start_all: Start all
4
+ stop_all: Stop all
5
+ start: Start
6
+ restart: Restart
7
+ stop: Stop
8
+ col:
9
+ app: Application
10
+ status: Status
11
+ actions: Actions
12
+ program:
13
+ status:
14
+ not_started: Not started
15
+ alive: Alive
16
+ dead: Dead
17
+ stopping: Stopping
18
+ unknown: Unknown
19
+ logs:
20
+ scroll_to_bottom: Scroll to bottom (s)
21
+ message:
22
+ _starting_all: Starting all...
23
+ _stopping_all: Stopping all...
24
+ _starting: Starting...
25
+ _stopping: Stopping...
26
+ _restarting: Restarting...
27
+ _died: Died :(
28
+ app:
29
+ _all: All
30
+ nav:
31
+ up: Up (u)
32
+ down: Down (d)
data/i18n/ru.yml ADDED
@@ -0,0 +1,32 @@
1
+ report:
2
+ action:
3
+ start_all: Запустить все
4
+ stop_all: Остановить все
5
+ start: Запустить
6
+ restart: Перезапустить
7
+ stop: Остановить
8
+ col:
9
+ app: Приложение
10
+ status: Статус
11
+ actions: Действия
12
+ program:
13
+ status:
14
+ not_started: Не запущено
15
+ alive: Живо
16
+ dead: Мертво
17
+ stopping: Останавливается
18
+ unknown: Неизвестно
19
+ logs:
20
+ scroll_to_bottom: Прокрутить до конца (s)
21
+ message:
22
+ _starting_all: Запускаем все...
23
+ _stopping_all: Останавливаем все...
24
+ _starting: Запускаем...
25
+ _stopping: Останавливаем...
26
+ _restarting: Перезапускаем...
27
+ _died: Умер :(
28
+ app:
29
+ _all: Все
30
+ nav:
31
+ up: Вверх (u)
32
+ down: Вниз (d)