influx-local-cli 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.
- package/LICENSE +21 -0
- package/README.md +265 -0
- package/bin/influx-local.js +5 -0
- package/package.json +42 -0
- package/src/cli.js +1092 -0
- package/src/config.js +349 -0
- package/src/manager.js +1794 -0
- package/src/versions.js +270 -0
- package/src/web.js +387 -0
- package/web/app.js +816 -0
- package/web/index.html +136 -0
- package/web/style.css +208 -0
package/web/index.html
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>influx-local โ dashboard</title>
|
|
7
|
+
<link rel="stylesheet" href="/style.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="topbar">
|
|
11
|
+
<div class="brand">
|
|
12
|
+
<span class="logo">๐</span>
|
|
13
|
+
<div>
|
|
14
|
+
<h1>influx-local</h1>
|
|
15
|
+
<p class="tag">local InfluxDB instances (1.x / 2.x / 3.x) ยท powered by mise</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="top-actions">
|
|
19
|
+
<button id="btn-refresh" class="ghost" title="Refresh">โป Refresh</button>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<main class="layout">
|
|
24
|
+
<aside class="sidebar">
|
|
25
|
+
<div class="side-head">
|
|
26
|
+
<h2>Instances</h2>
|
|
27
|
+
<button id="btn-new" class="primary small">+ New</button>
|
|
28
|
+
</div>
|
|
29
|
+
<ul id="instance-list" class="instance-list"></ul>
|
|
30
|
+
<p id="empty-hint" class="empty-hint hidden">
|
|
31
|
+
No instances yet. Create one, pick the InfluxDB version, then run <code>setup</code>.
|
|
32
|
+
</p>
|
|
33
|
+
</aside>
|
|
34
|
+
|
|
35
|
+
<section id="detail" class="detail">
|
|
36
|
+
<div id="detail-body"></div>
|
|
37
|
+
</section>
|
|
38
|
+
|
|
39
|
+
<section class="console-wrap">
|
|
40
|
+
<div class="console-head">
|
|
41
|
+
<h2>Operation log</h2>
|
|
42
|
+
<span id="op-state" class="op-state"></span>
|
|
43
|
+
<button id="btn-console-toggle" class="ghost small" title="Collapse / expand the operation log">โ</button>
|
|
44
|
+
</div>
|
|
45
|
+
<pre id="op-console" class="console">No operation running.</pre>
|
|
46
|
+
</section>
|
|
47
|
+
</main>
|
|
48
|
+
|
|
49
|
+
<!-- New instance modal -->
|
|
50
|
+
<dialog id="modal-new">
|
|
51
|
+
<form method="dialog" class="modal-form" id="form-new">
|
|
52
|
+
<h3>New instance</h3>
|
|
53
|
+
<label>Name <input name="name" required pattern="[A-Za-z0-9][A-Za-z0-9._-]{0,63}" placeholder="my_app" /></label>
|
|
54
|
+
<label>InfluxDB version
|
|
55
|
+
<select name="version" id="new-version"></select>
|
|
56
|
+
</label>
|
|
57
|
+
<p class="hint" id="new-version-hint" style="margin:0"></p>
|
|
58
|
+
<div class="grid2">
|
|
59
|
+
<label>Port <input name="port" type="number" min="1" max="65535" id="new-port" /></label>
|
|
60
|
+
<label>Host / bind IP <input name="host" value="127.0.0.1" /></label>
|
|
61
|
+
<label>Database / bucket <input name="database" placeholder="(instance name)" /></label>
|
|
62
|
+
<label id="new-measurement-wrap">Initial measurement <input name="measurement" value="items" /></label>
|
|
63
|
+
<label id="new-org-wrap">Organization (2.x) <input name="org" placeholder="(instance name)" /></label>
|
|
64
|
+
</div>
|
|
65
|
+
<hr />
|
|
66
|
+
<h4 style="margin:0">Credentials</h4>
|
|
67
|
+
<p class="hint" style="margin:0" id="new-creds-hint">
|
|
68
|
+
Leave a password empty to auto-generate it (stored in <code>creds.env</code>, mode 0600).
|
|
69
|
+
</p>
|
|
70
|
+
<div class="grid2" id="new-creds-grid">
|
|
71
|
+
<label id="new-adminuser-wrap">Admin user <input name="adminUser" value="admin" /></label>
|
|
72
|
+
<label id="new-appuser-wrap">App user <input name="appUser" value="app" /></label>
|
|
73
|
+
<label id="new-adminpw-wrap">Admin password
|
|
74
|
+
<span class="pw-wrap">
|
|
75
|
+
<input name="adminPassword" type="password" autocomplete="new-password" placeholder="auto-generate" />
|
|
76
|
+
<button type="button" class="pw-eye" data-eye="adminPassword" title="Show / hide">๐</button>
|
|
77
|
+
</span>
|
|
78
|
+
</label>
|
|
79
|
+
<label id="new-apppw-wrap">App password
|
|
80
|
+
<span class="pw-wrap">
|
|
81
|
+
<input name="appPassword" type="password" autocomplete="new-password" placeholder="auto-generate" />
|
|
82
|
+
<button type="button" class="pw-eye" data-eye="appPassword" title="Show / hide">๐</button>
|
|
83
|
+
</span>
|
|
84
|
+
</label>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="modal-actions">
|
|
87
|
+
<button type="button" value="cancel" class="ghost" id="btn-new-cancel">Cancel</button>
|
|
88
|
+
<button value="create" class="primary" type="submit">Create</button>
|
|
89
|
+
</div>
|
|
90
|
+
</form>
|
|
91
|
+
</dialog>
|
|
92
|
+
|
|
93
|
+
<!-- Generic field dialog (rename / clone / set password / users / config) -->
|
|
94
|
+
<dialog id="modal-simple">
|
|
95
|
+
<form method="dialog" id="form-simple" class="modal-form">
|
|
96
|
+
<h3 id="simple-title"></h3>
|
|
97
|
+
<div id="simple-fields"></div>
|
|
98
|
+
<div class="modal-actions">
|
|
99
|
+
<button type="button" value="cancel" class="ghost" id="btn-simple-cancel">Cancel</button>
|
|
100
|
+
<button value="ok" class="primary" type="submit">OK</button>
|
|
101
|
+
</div>
|
|
102
|
+
</form>
|
|
103
|
+
</dialog>
|
|
104
|
+
|
|
105
|
+
<!-- Secrets / logs viewer -->
|
|
106
|
+
<dialog id="modal-viewer">
|
|
107
|
+
<div class="modal-form">
|
|
108
|
+
<h3 id="viewer-title"></h3>
|
|
109
|
+
<div id="viewer-body"></div>
|
|
110
|
+
<div class="modal-actions">
|
|
111
|
+
<button id="btn-viewer-close" class="ghost">Close</button>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</dialog>
|
|
115
|
+
|
|
116
|
+
<!-- Destroy confirm modal -->
|
|
117
|
+
<dialog id="modal-destroy">
|
|
118
|
+
<div class="modal-form">
|
|
119
|
+
<h3 class="danger-title">Destroy <span id="destroy-instance"></span>?</h3>
|
|
120
|
+
<p class="danger-text">
|
|
121
|
+
This permanently deletes <strong>ALL data</strong> (server files, logs, credentials) for this
|
|
122
|
+
instance. This cannot be undone.
|
|
123
|
+
</p>
|
|
124
|
+
<label>Type the instance name to confirm
|
|
125
|
+
<input id="destroy-confirm" placeholder="instance name" autocomplete="off" />
|
|
126
|
+
</label>
|
|
127
|
+
<div class="modal-actions">
|
|
128
|
+
<button id="btn-destroy-cancel" class="ghost">Cancel</button>
|
|
129
|
+
<button id="btn-destroy-go" class="danger" disabled>Destroy</button>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</dialog>
|
|
133
|
+
|
|
134
|
+
<script src="/app.js"></script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>
|
package/web/style.css
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0f1420;
|
|
3
|
+
--panel: #171e2e;
|
|
4
|
+
--panel-2: #1d2638;
|
|
5
|
+
--border: #2a3550;
|
|
6
|
+
--text: #e6ecf7;
|
|
7
|
+
--muted: #8fa1c0;
|
|
8
|
+
--accent: #2f81f7;
|
|
9
|
+
--green: #3fb950;
|
|
10
|
+
--yellow: #d29922;
|
|
11
|
+
--red: #f85149;
|
|
12
|
+
--mono: "SFMono-Regular", "JetBrains Mono", Consolas, monospace;
|
|
13
|
+
}
|
|
14
|
+
* { box-sizing: border-box; }
|
|
15
|
+
body {
|
|
16
|
+
margin: 0;
|
|
17
|
+
background: var(--bg);
|
|
18
|
+
color: var(--text);
|
|
19
|
+
font: 14px/1.5 -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
20
|
+
}
|
|
21
|
+
.hidden { display: none !important; }
|
|
22
|
+
code { font-family: var(--mono); background: var(--panel-2); padding: 1px 5px; border-radius: 4px; }
|
|
23
|
+
|
|
24
|
+
.topbar {
|
|
25
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
26
|
+
padding: 12px 20px; border-bottom: 1px solid var(--border); background: var(--panel);
|
|
27
|
+
}
|
|
28
|
+
.brand { display: flex; gap: 12px; align-items: center; }
|
|
29
|
+
.logo { font-size: 26px; }
|
|
30
|
+
h1 { font-size: 18px; margin: 0; }
|
|
31
|
+
.tag { margin: 0; color: var(--muted); font-size: 12px; }
|
|
32
|
+
|
|
33
|
+
.layout {
|
|
34
|
+
display: grid;
|
|
35
|
+
grid-template-columns: 300px minmax(0, 1fr);
|
|
36
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
37
|
+
grid-template-areas:
|
|
38
|
+
"sidebar detail"
|
|
39
|
+
"sidebar console";
|
|
40
|
+
height: calc(100vh - 62px);
|
|
41
|
+
min-height: 0;
|
|
42
|
+
}
|
|
43
|
+
.sidebar {
|
|
44
|
+
grid-area: sidebar; border-right: 1px solid var(--border); background: var(--panel);
|
|
45
|
+
padding: 12px; overflow-y: auto; min-width: 0;
|
|
46
|
+
}
|
|
47
|
+
.side-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
|
48
|
+
.side-head h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0; }
|
|
49
|
+
.instance-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
|
|
50
|
+
.instance-list li {
|
|
51
|
+
display: block;
|
|
52
|
+
padding: 6px 9px; border: 1px solid var(--border); border-radius: 8px;
|
|
53
|
+
cursor: pointer; background: var(--panel-2);
|
|
54
|
+
}
|
|
55
|
+
.instance-list li:hover { border-color: var(--accent); }
|
|
56
|
+
.instance-list li.active { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
|
|
57
|
+
.row-top { display: flex; align-items: center; gap: 6px; }
|
|
58
|
+
.row-bottom { display: flex; align-items: center; justify-content: space-between; gap: 4px; margin-top: 3px; }
|
|
59
|
+
.inst-name { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; }
|
|
60
|
+
.inst-port { color: var(--muted); font-family: var(--mono); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
61
|
+
.inst-actions { display: inline-flex; align-items: center; gap: 0; }
|
|
62
|
+
.rowbtn {
|
|
63
|
+
background: transparent; border: none; color: var(--muted); padding: 2px 5px;
|
|
64
|
+
font-size: 12px; line-height: 1; cursor: pointer; border-radius: 4px;
|
|
65
|
+
}
|
|
66
|
+
.rowbtn:hover { color: var(--text); background: var(--panel); }
|
|
67
|
+
.rowbtn:disabled { opacity: .3; cursor: not-allowed; }
|
|
68
|
+
.rowbtn[data-rowact="destroy"]:hover { color: var(--red); }
|
|
69
|
+
.rowbtn[data-rowact="stop"]:hover { color: var(--yellow); }
|
|
70
|
+
.rowbtn[data-rowact="start"]:hover { color: var(--green); }
|
|
71
|
+
.inst-port { color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
72
|
+
.chip { font-size: 11px; padding: 1px 8px; border-radius: 99px; text-transform: uppercase; letter-spacing: .04em; }
|
|
73
|
+
.chip.running { background: rgba(63,185,80,.15); color: var(--green); }
|
|
74
|
+
.chip.stopped { background: rgba(242,201,76,.12); color: var(--yellow); }
|
|
75
|
+
.chip.conflict { background: rgba(248,81,73,.14); color: var(--red); }
|
|
76
|
+
.chip.op { background: rgba(47,129,247,.2); color: #7cb1ff; }
|
|
77
|
+
.chip.role { background: rgba(47,129,247,.12); color: #7cb1ff; font-size: 10px; }
|
|
78
|
+
.chip.builtin { background: rgba(210,153,34,.14); color: var(--yellow); font-size: 10px; }
|
|
79
|
+
.user-row { display: flex; align-items: center; gap: 8px; padding: 5px 0; border-bottom: 1px dashed var(--border); font-size: 13px; flex-wrap: wrap; }
|
|
80
|
+
.user-row:last-child { border-bottom: none; }
|
|
81
|
+
.ov-list { display: flex; flex-direction: column; gap: 6px; }
|
|
82
|
+
.ov-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel-2); flex-wrap: wrap; }
|
|
83
|
+
.ov-row .inst-name { cursor: pointer; font-size: 14px; }
|
|
84
|
+
.ov-row .inst-name:hover { color: var(--accent); text-decoration: underline; }
|
|
85
|
+
.ov-meta { color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
86
|
+
.ov-actions { margin-left: auto; display: inline-flex; gap: 2px; }
|
|
87
|
+
.ov-actions .rowbtn { padding: 3px 7px; font-size: 13px; }
|
|
88
|
+
.quick-rows { display: flex; flex-direction: column; }
|
|
89
|
+
.q-row { display: flex; align-items: center; gap: 8px; padding: 6px 2px; border-bottom: 1px dashed var(--border); font-size: 13px; }
|
|
90
|
+
.q-row:last-child { border-bottom: none; }
|
|
91
|
+
.q-icon { width: 18px; height: 18px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 12px; flex: 0 0 auto; }
|
|
92
|
+
.q-icon.ok { background: rgba(63,185,80,.15); color: var(--green); }
|
|
93
|
+
.q-icon.bad { background: rgba(248,81,73,.15); color: var(--red); }
|
|
94
|
+
.q-icon.next { background: rgba(47,129,247,.18); color: #7cb1ff; }
|
|
95
|
+
.q-icon.none { color: var(--muted); }
|
|
96
|
+
.q-text { flex: 1; min-width: 0; }
|
|
97
|
+
.q-row button { flex: 0 0 auto; }
|
|
98
|
+
.u-name { font-weight: 600; font-family: var(--mono); }
|
|
99
|
+
.u-db { color: var(--muted); font-family: var(--mono); font-size: 12px; }
|
|
100
|
+
.u-roles { flex: 1; display: flex; gap: 4px; flex-wrap: wrap; min-width: 60px; }
|
|
101
|
+
.u-actions { display: inline-flex; gap: 6px; }
|
|
102
|
+
.empty-hint { color: var(--muted); font-size: 13px; }
|
|
103
|
+
|
|
104
|
+
.detail { grid-area: detail; overflow-y: auto; min-width: 0; padding: 16px 20px; }
|
|
105
|
+
.detail h2 { margin: 0 0 12px; }
|
|
106
|
+
.detail-empty { color: var(--muted); margin-top: 40px; text-align: center; font-size: 15px; }
|
|
107
|
+
|
|
108
|
+
.console-wrap {
|
|
109
|
+
grid-area: console; display: flex; flex-direction: column;
|
|
110
|
+
border-top: 1px solid var(--border); background: var(--panel);
|
|
111
|
+
height: 150px; min-height: 0; min-width: 0;
|
|
112
|
+
}
|
|
113
|
+
.console-wrap.collapsed { height: 36px; }
|
|
114
|
+
.console-wrap.collapsed .console { display: none; }
|
|
115
|
+
.console-head { display: flex; align-items: center; gap: 10px; padding: 6px 12px; flex: 0 0 auto; }
|
|
116
|
+
.console-head h2 { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin: 0; flex: 1; }
|
|
117
|
+
.op-state { font-size: 12px; color: var(--muted); }
|
|
118
|
+
.op-state.running { color: #7cb1ff; }
|
|
119
|
+
.op-state.done { color: var(--green); }
|
|
120
|
+
.op-state.error { color: var(--red); }
|
|
121
|
+
.console {
|
|
122
|
+
flex: 1; margin: 0; overflow-y: auto; padding: 6px 12px;
|
|
123
|
+
font-family: var(--mono); font-size: 12.5px; white-space: pre-wrap; word-break: break-all;
|
|
124
|
+
background: #0b0f18; color: #c9d6ee; min-height: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; margin-bottom: 16px; }
|
|
128
|
+
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 10px 12px; }
|
|
129
|
+
.card .k { color: var(--muted); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
|
|
130
|
+
.card .v { font-family: var(--mono); font-size: 14px; margin-top: 2px; word-break: break-all; }
|
|
131
|
+
|
|
132
|
+
.section { background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; margin-bottom: 14px; }
|
|
133
|
+
.section h3 { margin: 0 0 10px; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
|
|
134
|
+
|
|
135
|
+
.uri-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; font-family: var(--mono); font-size: 12.5px; }
|
|
136
|
+
.uri-row .u { flex: 1; background: var(--panel-2); padding: 6px 10px; border-radius: 6px; overflow-x: auto; white-space: nowrap; }
|
|
137
|
+
.uri-row .pw { color: #2ea043; font-weight: 600; }
|
|
138
|
+
|
|
139
|
+
.btn-row { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
140
|
+
button {
|
|
141
|
+
font: inherit; border: 1px solid var(--border); background: var(--panel-2); color: var(--text);
|
|
142
|
+
padding: 7px 14px; border-radius: 8px; cursor: pointer;
|
|
143
|
+
}
|
|
144
|
+
button:hover { border-color: var(--accent); }
|
|
145
|
+
button:disabled { opacity: .45; cursor: not-allowed; }
|
|
146
|
+
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
|
|
147
|
+
button.danger { background: var(--red); border-color: var(--red); color: #fff; font-weight: 600; }
|
|
148
|
+
button.ghost { background: transparent; }
|
|
149
|
+
button.small { padding: 4px 10px; font-size: 12px; }
|
|
150
|
+
|
|
151
|
+
.console-wrap {
|
|
152
|
+
grid-area: console; display: flex; flex-direction: column;
|
|
153
|
+
border-top: 1px solid var(--border); background: var(--panel);
|
|
154
|
+
height: 150px; min-height: 0;
|
|
155
|
+
}
|
|
156
|
+
.console-wrap.collapsed { height: 36px; }
|
|
157
|
+
.console-wrap.collapsed .console { display: none; }
|
|
158
|
+
.console-head { display: flex; align-items: center; gap: 10px; padding: 6px 12px; }
|
|
159
|
+
.console-head h2 { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin: 0; flex: 1; }
|
|
160
|
+
.op-state { font-size: 12px; color: var(--muted); }
|
|
161
|
+
.op-state.running { color: #7cb1ff; }
|
|
162
|
+
.op-state.done { color: var(--green); }
|
|
163
|
+
.op-state.error { color: var(--red); }
|
|
164
|
+
.console {
|
|
165
|
+
flex: 1; margin: 0; overflow-y: auto; padding: 8px 16px;
|
|
166
|
+
font-family: var(--mono); font-size: 12.5px; white-space: pre-wrap; word-break: break-all;
|
|
167
|
+
background: #0b0f18; color: #c9d6ee;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
dialog {
|
|
171
|
+
border: 1px solid var(--border); border-radius: 12px; background: var(--panel); color: var(--text);
|
|
172
|
+
padding: 16px 20px; width: min(460px, 94vw); max-width: 94vw;
|
|
173
|
+
max-height: min(92vh, 720px); overflow-x: hidden; overflow-y: auto;
|
|
174
|
+
}
|
|
175
|
+
dialog#modal-new { width: min(700px, 96vw); }
|
|
176
|
+
dialog#modal-config { width: min(480px, 94vw); }
|
|
177
|
+
dialog#modal-destroy { width: min(460px, 94vw); }
|
|
178
|
+
dialog#modal-simple { width: min(620px, 96vw); }
|
|
179
|
+
#simple-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; }
|
|
180
|
+
#simple-fields.single { grid-template-columns: minmax(0, 1fr); }
|
|
181
|
+
#simple-fields .hint { grid-column: 1 / -1; }
|
|
182
|
+
.cb-group { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px 10px; padding: 2px 0; }
|
|
183
|
+
.cb { flex-direction: row !important; align-items: center; gap: 4px; font-size: 12px; color: var(--text); }
|
|
184
|
+
.cb input { width: auto !important; }
|
|
185
|
+
dialog::backdrop { background: rgba(0,0,0,.55); }
|
|
186
|
+
.modal-form { display: flex; flex-direction: column; gap: 9px; }
|
|
187
|
+
.modal-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); min-width: 0; }
|
|
188
|
+
.modal-form input, .modal-form select {
|
|
189
|
+
background: var(--panel-2); border: 1px solid var(--border); color: var(--text);
|
|
190
|
+
border-radius: 7px; padding: 7px 10px; font: inherit; width: 100%; min-width: 0;
|
|
191
|
+
}
|
|
192
|
+
.modal-form input[type="number"] { width: 100%; }
|
|
193
|
+
.pw-wrap { position: relative; display: flex; min-width: 0; }
|
|
194
|
+
.pw-wrap input { flex: 1; padding-right: 36px; }
|
|
195
|
+
.pw-eye {
|
|
196
|
+
position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
|
|
197
|
+
background: transparent; border: none; cursor: pointer; padding: 2px 6px;
|
|
198
|
+
font-size: 13px; opacity: .65; line-height: 1;
|
|
199
|
+
}
|
|
200
|
+
.pw-eye:hover { opacity: 1; }
|
|
201
|
+
.modal-form h3, .modal-form h4 { margin: 2px 0; color: var(--text); }
|
|
202
|
+
.modal-form hr { border: 0; border-top: 1px solid var(--border); margin: 2px 0; }
|
|
203
|
+
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
|
204
|
+
.grid2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px 12px; }
|
|
205
|
+
@media (max-width: 520px) { .grid2 { grid-template-columns: 1fr; } }
|
|
206
|
+
.hint { color: var(--muted); font-size: 12px; margin: 0; }
|
|
207
|
+
.danger-title { color: var(--red); }
|
|
208
|
+
.danger-text { color: var(--text); }
|