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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4210d9b64f2039e57601450447c13d86c04267be0e256936a29362d2c1a55cf0
4
- data.tar.gz: 454c4e8a1dcf8ca764e92ebb27de1d3f9fd5111cfe428c291d07bcaae797c69a
3
+ metadata.gz: 3deddaf8307ffc780ab2c69de90a02a6dc4c8baf8c780af2e107b08ff8798337
4
+ data.tar.gz: 73ab2141acb40a433ca2db21a1dd35b1a2a642ff74a1ad420be2978cfa237821
5
5
  SHA512:
6
- metadata.gz: a8238683c943a267cd49ad1fd9fce34735e056969396148a6cb91640afb73bfaafc7cd9f46e325a4735f9b751465f60bd74ad5be9b9ec322dfc391f016ee8e37
7
- data.tar.gz: 3b47e65343f08ef0a63932af7619a61a9c629c40a5d961e5b5ee033b6c1c662b44e3650a69c5e1644afab4da24f415b70621f0ecdf3fab7788276530f75ad5d6
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.hasDrawerTarget) this.drawerTarget.classList.toggle("open")
50
- if (this.hasOverlayTarget) this.overlayTarget.classList.toggle("open")
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) this.activityTarget.classList.remove("hidden")
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) this.activityTarget.classList.add("hidden")
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.125rem;
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
- flex-direction: column;
1096
- gap: 0.125rem;
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 + .rn-nav-section { margin-top: 0.75rem; }
1099
- .rn-nav-section-label {
1100
- display: block;
1101
- margin: 0.625rem 0.75rem 0.25rem;
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
- font-size: 0.625rem;
1104
- font-weight: 700;
1105
- letter-spacing: 0.1em;
1106
- line-height: 1;
1107
- text-transform: uppercase;
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.625rem;
1116
- padding: 0.5rem 0.75rem;
1117
- border-radius: 0.5rem;
1118
- font-size: 0.875rem;
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: all var(--rn-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.625rem;
1136
- bottom: 0.625rem;
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-mobile-menu-button { display: none; }
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
- min-width: 64rem;
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-severity { width: 1.5rem; padding-right: 0; }
1737
- .rn-exceptions-table .rn-col-exception { width: 10.5rem; }
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: 4.5rem; text-align: center; }
1742
- .rn-exceptions-table .rn-col-workflow { width: 10.5rem; }
1743
- .rn-exceptions-table .rn-col-time { width: 6.5rem; }
1744
- .rn-exceptions-table .rn-col-actions { width: 4.5rem; }
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: 0.5rem minmax(0, 1fr) auto auto;
1972
+ grid-template-columns: minmax(0, 1fr) auto auto;
1859
1973
  grid-template-areas:
1860
- "severity exception exception actions"
1861
- "severity message message message"
1862
- "severity source source source"
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-exceptions-toolbar-actions > span { display: none; }
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: 0.5rem minmax(0, 1fr) auto;
2018
+ grid-template-columns: minmax(0, 1fr) auto auto;
1912
2019
  grid-template-areas:
1913
- "severity exception actions"
1914
- "severity message message"
1915
- "severity source source"
1916
- "severity workflow workflow"
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: 100vh; overflow: hidden; margin: 0; }
2227
- body > div.min-h-full { height: 100vh; min-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 { display: flex; flex-direction: column; overflow: hidden; }
2230
- body > div.min-h-full > div:last-child > main { flex: 1; overflow-y: auto; }
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
- if @config.update(config_params)
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
- result = RailsNexus::BackupService.run(@config)
67
-
68
- if result[:success]
69
- redirect_to backup_path, notice: "Backup '#{@config.name}' completed successfully."
70
- else
71
- redirect_to backup_path, alert: "Backup failed: #{result[:error]}"
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(model_name: @config.name)
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
@@ -1,5 +1,10 @@
1
1
  module RailsNexus
2
2
  module ApplicationHelper
3
3
  include Pagy::Frontend
4
+
5
+ def page_title(text)
6
+ title = [RailsNexus.application_name.presence, text].compact.join(" :: ")
7
+ content_for(:title, title)
8
+ end
4
9
  end
5
10
  end
@@ -373,10 +373,14 @@ export default class extends Controller {
373
373
  // ─── Loading State ────────────────────────────────────────
374
374
 
375
375
  showActivity() {
376
- this.activityTarget?.classList.remove("hidden")
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.activityTarget?.classList.add("hidden")
382
+ if (!this.hasActivityTarget) return
383
+ this.activityTarget.hidden = true
384
+ this.activityTarget.classList.add("hidden")
381
385
  }
382
386
  }