rails_nexus 2.0.6 → 2.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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/rails_nexus/controllers.js +82 -5
- data/app/assets/stylesheets/rails_nexus/application.css +170 -60
- data/app/controllers/rails_nexus/backup_controller.rb +22 -18
- data/app/helpers/rails_nexus/application_helper.rb +5 -0
- data/app/javascript/controllers/rails_nexus_controller.js +6 -2
- data/app/javascript/controllers/rails_nexus_sidebar_controller.js +71 -3
- data/app/jobs/rails_nexus/backup_job.rb +18 -0
- data/app/models/rails_nexus/backup.rb +11 -11
- data/app/models/rails_nexus/backup_config.rb +7 -2
- data/app/services/rails_nexus/backup_service.rb +130 -43
- data/app/views/layouts/rails_nexus/_sidebar_navigation.html.erb +40 -10
- data/app/views/layouts/rails_nexus/application.html.erb +71 -13
- data/app/views/rails_nexus/backup/_form.html.erb +3 -0
- data/app/views/rails_nexus/backup/history.html.erb +17 -2
- data/app/views/rails_nexus/backup/index.html.erb +29 -72
- data/app/views/rails_nexus/logged_exceptions/_exceptions.html.erb +12 -44
- data/app/views/rails_nexus/logged_exceptions/index.html.erb +21 -99
- data/db/seeds/backup_configs.rb +5 -4
- data/lib/generators/rails_nexus/templates/migration.rb +8 -2
- data/lib/rails_nexus/version.rb +1 -1
- data/lib/tasks/rails_nexus.rake +4 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3deddaf8307ffc780ab2c69de90a02a6dc4c8baf8c780af2e107b08ff8798337
|
|
4
|
+
data.tar.gz: 73ab2141acb40a433ca2db21a1dd35b1a2a642ff74a1ad420be2978cfa237821
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7195e03556acf7084d881c264166564a02da3273feff2abbcb6ee56e93b9e5ae2e797309cd82e586ac4310ea083c7fde125e4d492b78268fcefdea19dfac58de
|
|
7
|
+
data.tar.gz: bb61711dba4e993e79ff6fab0185afe29ef4b20021fcf5c2fdc9ca79b11292bfcb005afe35ebb8fc243760e0f5513cb6a7207cefa7c225a7eb37fc43ef545675
|
|
@@ -43,16 +43,89 @@
|
|
|
43
43
|
// ═══════════════════════════════════════════════════════════════
|
|
44
44
|
|
|
45
45
|
class RailsNexusSidebarController {
|
|
46
|
-
static get targets() { return ["drawer", "overlay"] }
|
|
46
|
+
static get targets() { return ["drawer", "overlay", "sidebar", "toggleButton"] }
|
|
47
|
+
|
|
48
|
+
connect() {
|
|
49
|
+
this.handleResize = this.syncButtonState.bind(this)
|
|
50
|
+
window.addEventListener("resize", this.handleResize)
|
|
51
|
+
|
|
52
|
+
if (this.desktop() && this.sidebarHidden() && this.hasSidebarTarget) {
|
|
53
|
+
this.sidebarTarget.classList.add("is-hidden")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.syncButtonState()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
disconnect() {
|
|
60
|
+
window.removeEventListener("resize", this.handleResize)
|
|
61
|
+
}
|
|
47
62
|
|
|
48
63
|
toggle() {
|
|
49
|
-
if (this.
|
|
50
|
-
|
|
64
|
+
if (this.desktop()) {
|
|
65
|
+
if (this.hasSidebarTarget) {
|
|
66
|
+
var hidden = this.sidebarTarget.classList.toggle("is-hidden")
|
|
67
|
+
this.storeSidebarState(hidden)
|
|
68
|
+
}
|
|
69
|
+
} else if (this.hasDrawerTarget) {
|
|
70
|
+
var opening = !this.drawerTarget.classList.contains("open")
|
|
71
|
+
this.drawerTarget.classList.toggle("open", opening)
|
|
72
|
+
if (this.hasOverlayTarget) this.overlayTarget.classList.toggle("open", opening)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.syncButtonState()
|
|
51
76
|
}
|
|
52
77
|
|
|
53
78
|
close() {
|
|
54
79
|
if (this.hasDrawerTarget) this.drawerTarget.classList.remove("open")
|
|
55
80
|
if (this.hasOverlayTarget) this.overlayTarget.classList.remove("open")
|
|
81
|
+
this.syncButtonState()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
desktop() {
|
|
85
|
+
return window.matchMedia("(min-width: 1024px)").matches
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
sidebarHidden() {
|
|
89
|
+
try {
|
|
90
|
+
return localStorage.getItem("rails_nexus-sidebar-hidden") === "true"
|
|
91
|
+
} catch (_error) {
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
storeSidebarState(hidden) {
|
|
97
|
+
try {
|
|
98
|
+
localStorage.setItem("rails_nexus-sidebar-hidden", hidden.toString())
|
|
99
|
+
} catch (_error) {
|
|
100
|
+
// The toggle still works when storage is unavailable.
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
syncButtonState() {
|
|
105
|
+
if (!this.hasToggleButtonTarget) return
|
|
106
|
+
|
|
107
|
+
var expanded
|
|
108
|
+
if (this.desktop()) {
|
|
109
|
+
expanded = this.hasSidebarTarget && !this.sidebarTarget.classList.contains("is-hidden")
|
|
110
|
+
} else {
|
|
111
|
+
expanded = this.hasDrawerTarget && this.drawerTarget.classList.contains("open")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.toggleButtonTarget.setAttribute("aria-expanded", expanded.toString())
|
|
115
|
+
this.toggleButtonTarget.setAttribute("aria-label", expanded ? "Hide navigation" : "Show navigation")
|
|
116
|
+
this.toggleButtonTarget.title = expanded ? "Hide navigation" : "Show navigation"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
closeOtherMenus(event) {
|
|
120
|
+
var currentMenu = event.currentTarget
|
|
121
|
+
if (!currentMenu.open) return
|
|
122
|
+
|
|
123
|
+
var navigation = currentMenu.closest(".rn-sidebar-nav")
|
|
124
|
+
if (!navigation) return
|
|
125
|
+
|
|
126
|
+
navigation.querySelectorAll("details.rn-nav-section[open]").forEach(function(menu) {
|
|
127
|
+
if (menu !== currentMenu) menu.open = false
|
|
128
|
+
})
|
|
56
129
|
}
|
|
57
130
|
|
|
58
131
|
toggleShortcuts(event) {
|
|
@@ -393,11 +466,15 @@
|
|
|
393
466
|
// ─── Loading State ────────────────────────────────────────
|
|
394
467
|
|
|
395
468
|
showActivity() {
|
|
396
|
-
if (this.hasActivityTarget)
|
|
469
|
+
if (!this.hasActivityTarget) return
|
|
470
|
+
this.activityTarget.hidden = false
|
|
471
|
+
this.activityTarget.classList.remove("hidden")
|
|
397
472
|
}
|
|
398
473
|
|
|
399
474
|
hideActivity() {
|
|
400
|
-
if (this.hasActivityTarget)
|
|
475
|
+
if (!this.hasActivityTarget) return
|
|
476
|
+
this.activityTarget.hidden = true
|
|
477
|
+
this.activityTarget.classList.add("hidden")
|
|
401
478
|
}
|
|
402
479
|
}
|
|
403
480
|
|
|
@@ -33,6 +33,7 @@ table { border-collapse: collapse; width: 100%; }
|
|
|
33
33
|
/* ─── Tailwind-like Utility Classes ─────────────────────────── */
|
|
34
34
|
|
|
35
35
|
/* Display */
|
|
36
|
+
[hidden] { display: none !important; }
|
|
36
37
|
.hidden { display: none !important; }
|
|
37
38
|
.block { display: block; }
|
|
38
39
|
.inline-block { display: inline-block; }
|
|
@@ -1026,6 +1027,7 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1026
1027
|
/* ─── Sidebar ───────────────────────────────────────────────── */
|
|
1027
1028
|
.rn-sidebar {
|
|
1028
1029
|
width: 16rem;
|
|
1030
|
+
overflow: hidden;
|
|
1029
1031
|
background: var(--rn-surface);
|
|
1030
1032
|
color: var(--rn-text-secondary);
|
|
1031
1033
|
display: flex;
|
|
@@ -1036,6 +1038,13 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1036
1038
|
top: 0;
|
|
1037
1039
|
height: 100vh;
|
|
1038
1040
|
z-index: 30;
|
|
1041
|
+
transition: width 200ms ease, opacity 150ms ease, border-color 150ms ease;
|
|
1042
|
+
}
|
|
1043
|
+
.rn-sidebar.is-hidden {
|
|
1044
|
+
width: 0;
|
|
1045
|
+
border-right-color: transparent;
|
|
1046
|
+
opacity: 0;
|
|
1047
|
+
pointer-events: none;
|
|
1039
1048
|
}
|
|
1040
1049
|
@media (max-width: 1023px) { .rn-sidebar { display: none; } }
|
|
1041
1050
|
|
|
@@ -1084,41 +1093,104 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1084
1093
|
|
|
1085
1094
|
.rn-sidebar-nav {
|
|
1086
1095
|
flex: 1;
|
|
1087
|
-
padding: 0.75rem;
|
|
1096
|
+
padding: 0.75rem 0.625rem;
|
|
1088
1097
|
display: flex;
|
|
1089
1098
|
flex-direction: column;
|
|
1090
|
-
gap: 0.
|
|
1099
|
+
gap: 0.375rem;
|
|
1091
1100
|
overflow-y: auto;
|
|
1092
1101
|
}
|
|
1093
1102
|
.rn-nav-section {
|
|
1103
|
+
display: block;
|
|
1104
|
+
border: 1px solid transparent;
|
|
1105
|
+
border-radius: 0.625rem;
|
|
1106
|
+
transition: background var(--rn-transition), border-color var(--rn-transition);
|
|
1107
|
+
}
|
|
1108
|
+
.rn-nav-section[open] {
|
|
1109
|
+
border-color: var(--rn-border-light);
|
|
1110
|
+
background: color-mix(in srgb, var(--rn-bg-secondary) 55%, transparent);
|
|
1111
|
+
}
|
|
1112
|
+
.rn-nav-section-toggle {
|
|
1113
|
+
min-height: 2.5rem;
|
|
1114
|
+
padding: 0.375rem 0.5rem;
|
|
1115
|
+
border-radius: 0.5625rem;
|
|
1094
1116
|
display: flex;
|
|
1095
|
-
|
|
1096
|
-
|
|
1117
|
+
align-items: center;
|
|
1118
|
+
justify-content: space-between;
|
|
1119
|
+
gap: 0.5rem;
|
|
1120
|
+
color: var(--rn-text-secondary);
|
|
1121
|
+
cursor: pointer;
|
|
1122
|
+
font-size: 0.75rem;
|
|
1123
|
+
font-weight: 650;
|
|
1124
|
+
letter-spacing: 0.01em;
|
|
1125
|
+
list-style: none;
|
|
1126
|
+
transition: color var(--rn-transition), background var(--rn-transition);
|
|
1127
|
+
user-select: none;
|
|
1097
1128
|
}
|
|
1098
|
-
.rn-nav-section
|
|
1099
|
-
.rn-nav-section-
|
|
1100
|
-
|
|
1101
|
-
|
|
1129
|
+
.rn-nav-section-toggle::-webkit-details-marker { display: none; }
|
|
1130
|
+
.rn-nav-section-toggle:hover,
|
|
1131
|
+
.rn-nav-section-toggle:focus-visible {
|
|
1132
|
+
background: var(--rn-surface-hover);
|
|
1133
|
+
color: var(--rn-text);
|
|
1134
|
+
outline: none;
|
|
1135
|
+
}
|
|
1136
|
+
.rn-nav-section-toggle:focus-visible { box-shadow: inset 0 0 0 2px var(--rn-primary-ring); }
|
|
1137
|
+
.rn-nav-section-title {
|
|
1138
|
+
min-width: 0;
|
|
1139
|
+
display: flex;
|
|
1140
|
+
align-items: center;
|
|
1141
|
+
gap: 0.625rem;
|
|
1142
|
+
}
|
|
1143
|
+
.rn-nav-section-icon {
|
|
1144
|
+
width: 1.625rem;
|
|
1145
|
+
height: 1.625rem;
|
|
1146
|
+
border: 1px solid var(--rn-border);
|
|
1147
|
+
border-radius: 0.5rem;
|
|
1148
|
+
display: inline-flex;
|
|
1149
|
+
align-items: center;
|
|
1150
|
+
justify-content: center;
|
|
1151
|
+
flex-shrink: 0;
|
|
1152
|
+
background: var(--rn-surface);
|
|
1102
1153
|
color: var(--rn-text-muted);
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1154
|
+
box-shadow: var(--rn-shadow-sm);
|
|
1155
|
+
transition: color var(--rn-transition), border-color var(--rn-transition), background var(--rn-transition);
|
|
1156
|
+
}
|
|
1157
|
+
.rn-nav-section-chevron {
|
|
1158
|
+
width: 1.125rem;
|
|
1159
|
+
height: 1.125rem;
|
|
1160
|
+
padding: 0.125rem;
|
|
1161
|
+
border-radius: 999px;
|
|
1162
|
+
flex-shrink: 0;
|
|
1163
|
+
color: var(--rn-text-muted);
|
|
1164
|
+
transition: transform var(--rn-transition);
|
|
1165
|
+
}
|
|
1166
|
+
.rn-nav-section[open] > .rn-nav-section-toggle { color: var(--rn-text); }
|
|
1167
|
+
.rn-nav-section[open] > .rn-nav-section-toggle .rn-nav-section-icon {
|
|
1168
|
+
border-color: color-mix(in srgb, var(--rn-primary) 25%, var(--rn-border));
|
|
1169
|
+
background: var(--rn-primary-light);
|
|
1170
|
+
color: var(--rn-primary);
|
|
1171
|
+
box-shadow: none;
|
|
1172
|
+
}
|
|
1173
|
+
.rn-nav-section[open] > .rn-nav-section-toggle .rn-nav-section-chevron { transform: rotate(90deg); }
|
|
1174
|
+
.rn-nav-section-items {
|
|
1175
|
+
margin: 0.125rem 0.5rem 0.5rem 1.3125rem;
|
|
1176
|
+
padding-left: 0.6875rem;
|
|
1177
|
+
border-left: 1px solid var(--rn-border);
|
|
1178
|
+
display: grid;
|
|
1179
|
+
gap: 0.125rem;
|
|
1108
1180
|
}
|
|
1109
|
-
.rn-sidebar-nav > .rn-nav-section-label:not(:first-child) { margin-top: 1rem; }
|
|
1110
1181
|
|
|
1111
1182
|
.rn-nav-item {
|
|
1112
1183
|
position: relative;
|
|
1184
|
+
min-height: 2.125rem;
|
|
1113
1185
|
display: flex;
|
|
1114
1186
|
align-items: center;
|
|
1115
|
-
gap: 0.
|
|
1116
|
-
padding: 0.
|
|
1117
|
-
border-radius: 0.
|
|
1118
|
-
font-size: 0.
|
|
1187
|
+
gap: 0.5625rem;
|
|
1188
|
+
padding: 0.375rem 0.625rem;
|
|
1189
|
+
border-radius: 0.4375rem;
|
|
1190
|
+
font-size: 0.8125rem;
|
|
1119
1191
|
font-weight: 500;
|
|
1120
1192
|
color: var(--rn-text-secondary);
|
|
1121
|
-
transition:
|
|
1193
|
+
transition: color var(--rn-transition), background var(--rn-transition);
|
|
1122
1194
|
text-decoration: none;
|
|
1123
1195
|
}
|
|
1124
1196
|
.rn-nav-item:hover {
|
|
@@ -1132,8 +1204,8 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1132
1204
|
}
|
|
1133
1205
|
.rn-nav-item.active::before {
|
|
1134
1206
|
position: absolute;
|
|
1135
|
-
top: 0.
|
|
1136
|
-
bottom: 0.
|
|
1207
|
+
top: 0.5rem;
|
|
1208
|
+
bottom: 0.5rem;
|
|
1137
1209
|
left: -0.75rem;
|
|
1138
1210
|
width: 2px;
|
|
1139
1211
|
border-radius: 999px;
|
|
@@ -1142,7 +1214,6 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1142
1214
|
}
|
|
1143
1215
|
.rn-nav-item svg { flex-shrink: 0; }
|
|
1144
1216
|
.rn-nav-item--muted {
|
|
1145
|
-
font-size: 0.8125rem;
|
|
1146
1217
|
opacity: 0.8;
|
|
1147
1218
|
}
|
|
1148
1219
|
.rn-nav-item--muted:hover {
|
|
@@ -1158,6 +1229,10 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1158
1229
|
color: var(--rn-text-secondary);
|
|
1159
1230
|
line-height: 1.25;
|
|
1160
1231
|
}
|
|
1232
|
+
.rn-nav-item.active .rn-nav-badge {
|
|
1233
|
+
background: color-mix(in srgb, var(--rn-primary) 12%, var(--rn-surface));
|
|
1234
|
+
color: var(--rn-primary);
|
|
1235
|
+
}
|
|
1161
1236
|
.rn-sidebar-section-divider {
|
|
1162
1237
|
height: 1px;
|
|
1163
1238
|
background: var(--rn-border);
|
|
@@ -1251,13 +1326,11 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1251
1326
|
font-size: 0.6875rem;
|
|
1252
1327
|
line-height: 1.1;
|
|
1253
1328
|
}
|
|
1254
|
-
.rn-
|
|
1255
|
-
@media (max-width: 1023px) {
|
|
1256
|
-
.rn-mobile-menu-button { display: inline-flex; }
|
|
1257
|
-
}
|
|
1329
|
+
.rn-sidebar-toggle { display: inline-flex; }
|
|
1258
1330
|
|
|
1259
1331
|
/* ─── Shell Footer ─────────────────────────────────────────── */
|
|
1260
1332
|
.rn-footer {
|
|
1333
|
+
position: static;
|
|
1261
1334
|
min-height: 3.25rem;
|
|
1262
1335
|
padding: 0.75rem 1.5rem;
|
|
1263
1336
|
border-top: 1px solid var(--rn-border-light);
|
|
@@ -1713,6 +1786,39 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1713
1786
|
white-space: nowrap;
|
|
1714
1787
|
}
|
|
1715
1788
|
|
|
1789
|
+
.rn-exceptions-panel { overflow: hidden; }
|
|
1790
|
+
.rn-exceptions-main {
|
|
1791
|
+
min-width: 0;
|
|
1792
|
+
display: flex;
|
|
1793
|
+
flex-direction: column;
|
|
1794
|
+
gap: 0;
|
|
1795
|
+
}
|
|
1796
|
+
.rn-exceptions-results { min-width: 0; }
|
|
1797
|
+
.rn-filter-disclosure { overflow: hidden; }
|
|
1798
|
+
.rn-filter-summary {
|
|
1799
|
+
padding: 0.75rem 0.875rem;
|
|
1800
|
+
display: flex;
|
|
1801
|
+
align-items: center;
|
|
1802
|
+
justify-content: space-between;
|
|
1803
|
+
gap: 0.75rem;
|
|
1804
|
+
color: var(--rn-text-muted);
|
|
1805
|
+
cursor: pointer;
|
|
1806
|
+
list-style: none;
|
|
1807
|
+
user-select: none;
|
|
1808
|
+
}
|
|
1809
|
+
.rn-filter-summary::-webkit-details-marker { display: none; }
|
|
1810
|
+
.rn-filter-summary:hover { background: var(--rn-surface-hover); }
|
|
1811
|
+
.rn-filter-count {
|
|
1812
|
+
padding: 0.1rem 0.4rem;
|
|
1813
|
+
border-radius: 999px;
|
|
1814
|
+
background: var(--rn-primary-light);
|
|
1815
|
+
color: var(--rn-primary);
|
|
1816
|
+
font-size: 0.625rem;
|
|
1817
|
+
font-weight: 700;
|
|
1818
|
+
}
|
|
1819
|
+
.rn-filter-chevron { flex-shrink: 0; transition: transform var(--rn-transition); }
|
|
1820
|
+
.rn-filter-disclosure[open] .rn-filter-chevron { transform: rotate(180deg); }
|
|
1821
|
+
.rn-filter-disclosure[open] .rn-filter-summary { border-bottom: 1px solid var(--rn-border-light); }
|
|
1716
1822
|
.rn-exceptions-toolbar {
|
|
1717
1823
|
min-height: 3.25rem;
|
|
1718
1824
|
padding: 0.625rem 1rem;
|
|
@@ -1727,21 +1833,26 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1727
1833
|
align-items: center;
|
|
1728
1834
|
gap: 0.75rem;
|
|
1729
1835
|
}
|
|
1836
|
+
.rn-exceptions-toolbar-actions { margin-left: auto; }
|
|
1730
1837
|
.rn-exceptions-table {
|
|
1731
1838
|
table-layout: fixed;
|
|
1732
|
-
|
|
1839
|
+
width: 100%;
|
|
1840
|
+
min-width: 0;
|
|
1733
1841
|
}
|
|
1734
1842
|
.rn-exceptions-table th,
|
|
1735
1843
|
.rn-exceptions-table td { padding-right: 0.75rem; padding-left: 0.75rem; }
|
|
1736
|
-
.rn-exceptions-table .rn-col-
|
|
1737
|
-
.rn-exceptions-table .rn-col-
|
|
1738
|
-
.rn-exceptions-table .rn-col-platform { width: 5.5rem; }
|
|
1739
|
-
.rn-exceptions-table .rn-col-source { width: 10rem; }
|
|
1844
|
+
.rn-exceptions-table .rn-col-exception { width: 11rem; }
|
|
1845
|
+
.rn-exceptions-table .rn-col-source { width: 9rem; }
|
|
1740
1846
|
.rn-exceptions-table .rn-col-message { width: auto; }
|
|
1741
|
-
.rn-exceptions-table .rn-col-count { width:
|
|
1742
|
-
.rn-exceptions-table .rn-col-
|
|
1743
|
-
.rn-exceptions-table .rn-col-
|
|
1744
|
-
.rn-exceptions-table .rn-
|
|
1847
|
+
.rn-exceptions-table .rn-col-count { width: 4rem; text-align: center; }
|
|
1848
|
+
.rn-exceptions-table .rn-col-time { width: 6rem; }
|
|
1849
|
+
.rn-exceptions-table .rn-col-actions { width: 4rem; }
|
|
1850
|
+
.rn-exceptions-table .rn-exception-cell {
|
|
1851
|
+
min-width: 0;
|
|
1852
|
+
display: flex;
|
|
1853
|
+
align-items: flex-start;
|
|
1854
|
+
gap: 0.625rem;
|
|
1855
|
+
}
|
|
1745
1856
|
.rn-exceptions-table .rn-exception-name {
|
|
1746
1857
|
display: block;
|
|
1747
1858
|
overflow: hidden;
|
|
@@ -1842,6 +1953,9 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1842
1953
|
}
|
|
1843
1954
|
|
|
1844
1955
|
@media (max-width: 1100px) {
|
|
1956
|
+
.rn-exceptions-main { width: 100%; }
|
|
1957
|
+
.rn-exceptions-toolbar { flex-wrap: wrap; }
|
|
1958
|
+
.rn-exceptions-toolbar-actions { flex-wrap: wrap; }
|
|
1845
1959
|
.rn-exceptions-table { display: block; min-width: 0; }
|
|
1846
1960
|
.rn-exceptions-table thead { display: none; }
|
|
1847
1961
|
.rn-exceptions-table tbody {
|
|
@@ -1855,13 +1969,11 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1855
1969
|
border-left: 3px solid transparent;
|
|
1856
1970
|
border-radius: 0.625rem;
|
|
1857
1971
|
display: grid;
|
|
1858
|
-
grid-template-columns:
|
|
1972
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
1859
1973
|
grid-template-areas:
|
|
1860
|
-
"
|
|
1861
|
-
"
|
|
1862
|
-
"
|
|
1863
|
-
"severity platform count time"
|
|
1864
|
-
"severity workflow workflow workflow";
|
|
1974
|
+
"exception exception actions"
|
|
1975
|
+
"message message message"
|
|
1976
|
+
"source count time";
|
|
1865
1977
|
column-gap: 0.75rem;
|
|
1866
1978
|
row-gap: 0.5rem;
|
|
1867
1979
|
background: var(--rn-surface);
|
|
@@ -1879,13 +1991,10 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1879
1991
|
border: 0;
|
|
1880
1992
|
min-width: 0;
|
|
1881
1993
|
}
|
|
1882
|
-
.rn-exceptions-table .rn-col-severity { grid-area: severity; }
|
|
1883
1994
|
.rn-exceptions-table .rn-col-exception { grid-area: exception; }
|
|
1884
|
-
.rn-exceptions-table .rn-col-platform { grid-area: platform; }
|
|
1885
1995
|
.rn-exceptions-table .rn-col-source { grid-area: source; }
|
|
1886
1996
|
.rn-exceptions-table .rn-col-message { grid-area: message; }
|
|
1887
1997
|
.rn-exceptions-table .rn-col-count { grid-area: count; text-align: left; }
|
|
1888
|
-
.rn-exceptions-table .rn-col-workflow { grid-area: workflow; }
|
|
1889
1998
|
.rn-exceptions-table .rn-col-time { grid-area: time; text-align: right; }
|
|
1890
1999
|
.rn-exceptions-table .rn-col-actions { grid-area: actions; }
|
|
1891
2000
|
.rn-exceptions-table .rn-exception-name { font-size: 0.875rem; }
|
|
@@ -1897,25 +2006,21 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
1897
2006
|
font-size: 0.6875rem;
|
|
1898
2007
|
}
|
|
1899
2008
|
.rn-exceptions-table .rn-row-actions { opacity: 1; }
|
|
1900
|
-
.rn-exceptions-table .rn-severity-dot { height: 100%; min-height: 1.5rem; margin-top: 0; }
|
|
1901
|
-
.rn-exceptions-table .rn-workflow-state { align-items: center; }
|
|
1902
2009
|
}
|
|
1903
2010
|
|
|
1904
2011
|
@media (max-width: 639px) {
|
|
1905
2012
|
.rn-exceptions-toolbar { align-items: flex-start; flex-direction: column; }
|
|
1906
2013
|
.rn-exceptions-toolbar-summary { width: 100%; justify-content: space-between; }
|
|
1907
|
-
.rn-exceptions-toolbar-actions { width: 100%; justify-content: flex-end; }
|
|
1908
|
-
.rn-
|
|
2014
|
+
.rn-exceptions-toolbar-actions { width: 100%; margin-left: 0; justify-content: flex-end; }
|
|
2015
|
+
.rn-shortcuts-hint { display: none; }
|
|
1909
2016
|
.rn-exceptions-table tbody { padding: 0.5rem; }
|
|
1910
2017
|
.rn-exceptions-table tbody tr {
|
|
1911
|
-
grid-template-columns:
|
|
2018
|
+
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
1912
2019
|
grid-template-areas:
|
|
1913
|
-
"
|
|
1914
|
-
"
|
|
1915
|
-
"
|
|
1916
|
-
"
|
|
1917
|
-
"severity platform count"
|
|
1918
|
-
"severity time time";
|
|
2020
|
+
"exception actions actions"
|
|
2021
|
+
"message message message"
|
|
2022
|
+
"source source source"
|
|
2023
|
+
"count time time";
|
|
1919
2024
|
}
|
|
1920
2025
|
.rn-exceptions-table .rn-col-time { text-align: left; }
|
|
1921
2026
|
}
|
|
@@ -2222,9 +2327,14 @@ code, pre { font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", mon
|
|
|
2222
2327
|
.rn-font-semibold { font-weight: 600; }
|
|
2223
2328
|
|
|
2224
2329
|
/* ─── Layout (override host app resets) ─────────────────── */
|
|
2225
|
-
html { height: 100%; }
|
|
2226
|
-
body { height:
|
|
2227
|
-
body > div.min-h-full { height: 100vh;
|
|
2330
|
+
html { min-height: 100%; }
|
|
2331
|
+
body { min-height: 100%; height: auto; overflow: visible; margin: 0; }
|
|
2332
|
+
body > div.min-h-full { min-height: 100vh; height: auto; }
|
|
2228
2333
|
body > div.min-h-full > .rn-sidebar { height: 100vh; min-height: 100vh; }
|
|
2229
|
-
body > div.min-h-full > div:last-child {
|
|
2230
|
-
|
|
2334
|
+
body > div.min-h-full > div:last-child {
|
|
2335
|
+
min-height: 100vh;
|
|
2336
|
+
display: flex;
|
|
2337
|
+
flex-direction: column;
|
|
2338
|
+
overflow: visible;
|
|
2339
|
+
}
|
|
2340
|
+
body > div.min-h-full > div:last-child > main { flex: 1; overflow: visible; }
|
|
@@ -9,14 +9,6 @@ module RailsNexus
|
|
|
9
9
|
# GET /rails_nexus/backup
|
|
10
10
|
def index
|
|
11
11
|
@configs = RailsNexus::BackupConfig.order(:name)
|
|
12
|
-
@records = RailsNexus::Backup.order(started_at: :desc).limit(20)
|
|
13
|
-
@stats = {
|
|
14
|
-
total_configs: RailsNexus::BackupConfig.count,
|
|
15
|
-
enabled_configs: RailsNexus::BackupConfig.enabled.count,
|
|
16
|
-
total_backups: RailsNexus::Backup.count,
|
|
17
|
-
successful: RailsNexus::Backup.successful.count,
|
|
18
|
-
failed: RailsNexus::Backup.failed.count
|
|
19
|
-
}
|
|
20
12
|
end
|
|
21
13
|
|
|
22
14
|
# GET /rails_nexus/backup/new
|
|
@@ -47,7 +39,13 @@ module RailsNexus
|
|
|
47
39
|
|
|
48
40
|
# PATCH /rails_nexus/backup/:id
|
|
49
41
|
def update
|
|
50
|
-
|
|
42
|
+
# Strip blank password fields so they don't overwrite stored values
|
|
43
|
+
filtered = config_params
|
|
44
|
+
%w[password encryption_password gpg_password].each do |attr|
|
|
45
|
+
filtered = filtered.except(attr) if filtered[attr].blank?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if @config.update(filtered)
|
|
51
49
|
redirect_to backup_path, notice: "Backup config '#{@config.name}' updated."
|
|
52
50
|
else
|
|
53
51
|
render :edit, status: :unprocessable_entity
|
|
@@ -63,19 +61,24 @@ module RailsNexus
|
|
|
63
61
|
|
|
64
62
|
# POST /rails_nexus/backup/:id/trigger
|
|
65
63
|
def trigger
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
begin
|
|
65
|
+
RailsNexus::BackupJob.perform_later(@config.id)
|
|
66
|
+
redirect_to backup_path, notice: "Backup '#{@config.name}' started in background."
|
|
67
|
+
rescue => e
|
|
68
|
+
# Fallback to synchronous if Active Job is not configured
|
|
69
|
+
result = RailsNexus::BackupService.run(@config)
|
|
70
|
+
if result[:success]
|
|
71
|
+
redirect_to backup_path, notice: "Backup '#{@config.name}' completed successfully."
|
|
72
|
+
else
|
|
73
|
+
redirect_to backup_path, alert: "Backup failed: #{result[:error]}"
|
|
74
|
+
end
|
|
72
75
|
end
|
|
73
76
|
end
|
|
74
77
|
|
|
75
78
|
# GET /rails_nexus/backup/:id/history
|
|
76
79
|
def history
|
|
77
80
|
@config = RailsNexus::BackupConfig.find(params[:id])
|
|
78
|
-
@records = RailsNexus::Backup.where(
|
|
81
|
+
@records = RailsNexus::Backup.where(config_name: @config.name)
|
|
79
82
|
.order(started_at: :desc)
|
|
80
83
|
.limit(50)
|
|
81
84
|
end
|
|
@@ -83,6 +86,7 @@ module RailsNexus
|
|
|
83
86
|
# GET /rails_nexus/backup/history
|
|
84
87
|
def all_history
|
|
85
88
|
@records = RailsNexus::Backup.order(started_at: :desc).limit(100)
|
|
89
|
+
render :history
|
|
86
90
|
end
|
|
87
91
|
|
|
88
92
|
private
|
|
@@ -96,13 +100,13 @@ module RailsNexus
|
|
|
96
100
|
:name, :description, :database_name, :adapter, :host, :port,
|
|
97
101
|
:username, :password, :storage_path, :keep_count,
|
|
98
102
|
:compress, :encrypted, :encryption_password,
|
|
99
|
-
:rsync_enabled, :rsync_host, :rsync_port, :rsync_user, :rsync_path, :rsync_mirror,
|
|
103
|
+
:rsync_enabled, :rsync_host, :rsync_port, :rsync_user, :rsync_path, :rsync_mirror, :rsync_archive, :rsync_directories, :rsync_excludes,
|
|
100
104
|
:notify_command, :notify_on_success, :notify_on_failure,
|
|
101
105
|
:schedule_cron, :enabled,
|
|
102
106
|
:s3_enabled, :s3_access_key, :s3_secret_key, :s3_bucket, :s3_region, :s3_prefix,
|
|
103
107
|
:gpg_enabled, :gpg_password,
|
|
104
108
|
:email_notify, :email_to,
|
|
105
|
-
:archive_enabled, :bzip2_compress, :split_chunks,
|
|
109
|
+
:archive_enabled, :bzip2_compress, :split_chunks, :encrypt_base64, :mysql_additional_options,
|
|
106
110
|
skip_tables: [], archive_paths: [], archive_excludes: []
|
|
107
111
|
)
|
|
108
112
|
end
|
|
@@ -373,10 +373,14 @@ export default class extends Controller {
|
|
|
373
373
|
// ─── Loading State ────────────────────────────────────────
|
|
374
374
|
|
|
375
375
|
showActivity() {
|
|
376
|
-
this.
|
|
376
|
+
if (!this.hasActivityTarget) return
|
|
377
|
+
this.activityTarget.hidden = false
|
|
378
|
+
this.activityTarget.classList.remove("hidden")
|
|
377
379
|
}
|
|
378
380
|
|
|
379
381
|
hideActivity() {
|
|
380
|
-
this.
|
|
382
|
+
if (!this.hasActivityTarget) return
|
|
383
|
+
this.activityTarget.hidden = true
|
|
384
|
+
this.activityTarget.classList.add("hidden")
|
|
381
385
|
}
|
|
382
386
|
}
|