node-red-contrib-fox-admin 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.
package/lib/login.html ADDED
@@ -0,0 +1,195 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Fox Control | 系统管理控制台</title>
7
+ <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" type="image/x-icon">
8
+ <style>
9
+ :root {
10
+ --primary: #ed1c24;
11
+ --primary-rgb: 237, 28, 36;
12
+ --success: #52c41a;
13
+ --error: #ff4d4f;
14
+ --text-main: #ffffff;
15
+ --text-muted: rgba(255, 255, 255, 0.7);
16
+ --bg-gradient: linear-gradient(135deg, #1e2a38 0%, #2c3e50 100%);
17
+ --card-bg: rgba(255, 255, 255, 0.08);
18
+ --card-border: rgba(255, 255, 255, 0.15);
19
+ }
20
+
21
+ * { box-sizing: border-box; outline: none; }
22
+
23
+ body {
24
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
25
+ background: var(--bg-gradient);
26
+ color: var(--text-main);
27
+ display: flex; justify-content: center; align-items: center;
28
+ min-height: 100vh; margin: 0; padding: 20px;
29
+ }
30
+
31
+ .glass-container {
32
+ background: var(--card-bg);
33
+ backdrop-filter: blur(25px);
34
+ -webkit-backdrop-filter: blur(25px);
35
+ width: 100%; max-width: 450px;
36
+ padding: 50px 40px;
37
+ border-radius: 24px;
38
+ border: 1px solid var(--card-border);
39
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
40
+ text-align: center;
41
+ animation: cardAppear 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
42
+ }
43
+
44
+ @keyframes cardAppear {
45
+ from { opacity: 0; transform: translateY(30px) scale(0.95); }
46
+ to { opacity: 1; transform: translateY(0) scale(1); }
47
+ }
48
+
49
+ .logo {
50
+ width: 80px; height: 80px;
51
+ background: linear-gradient(135deg, var(--primary) 0%, #ff6b6b 100%);
52
+ border-radius: 20px;
53
+ display: flex; align-items: center; justify-content: center;
54
+ margin: 0 auto 25px;
55
+ font-size: 36px;
56
+ box-shadow: 0 10px 30px rgba(var(--primary-rgb), 0.3);
57
+ }
58
+
59
+ h2 { margin: 0 0 10px; font-size: 28px; font-weight: 800; letter-spacing: -0.5px; }
60
+ p.subtitle { color: var(--text-muted); font-size: 15px; margin-bottom: 35px; }
61
+
62
+ .input-group { margin-bottom: 20px; text-align: left; }
63
+
64
+ label {
65
+ display: block; margin-bottom: 8px;
66
+ font-size: 14px; font-weight: 600;
67
+ color: var(--text-muted);
68
+ }
69
+
70
+ input[type="password"] {
71
+ width: 100%; padding: 14px 16px;
72
+ background: rgba(255, 255, 255, 0.05);
73
+ border: 2px solid rgba(255, 255, 255, 0.1);
74
+ border-radius: 12px;
75
+ color: white; font-size: 16px;
76
+ transition: all 0.3s ease;
77
+ }
78
+
79
+ input[type="password"]:focus {
80
+ border-color: var(--primary);
81
+ background: rgba(255, 255, 255, 0.08);
82
+ box-shadow: 0 0 20px rgba(var(--primary-rgb), 0.2);
83
+ }
84
+
85
+ button {
86
+ background: var(--primary);
87
+ color: white; border: none;
88
+ width: 100%; padding: 16px;
89
+ border-radius: 12px; font-size: 17px; font-weight: 700;
90
+ cursor: pointer; transition: all 0.2s ease;
91
+ box-shadow: 0 8px 20px rgba(var(--primary-rgb), 0.3);
92
+ margin-top: 10px;
93
+ }
94
+
95
+ button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 10px 25px rgba(var(--primary-rgb), 0.5); }
96
+ button:disabled { opacity: 0.5; cursor: not-allowed; box-shadow: none; }
97
+
98
+ #status-modal {
99
+ margin-top: 25px; padding: 18px; border-radius: 12px;
100
+ display: none; font-size: 14px; animation: fadeIn 0.4s ease;
101
+ }
102
+
103
+ @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
104
+ .modal-success { background: rgba(82, 196, 38, 0.15); border: 1px solid var(--success); color: #b7eb8f; }
105
+ .modal-error { background: rgba(255, 77, 79, 0.15); border: 1px solid var(--error); color: #ffccc7; }
106
+ .modal-loading { background: rgba(255, 255, 255, 0.05); border: 1px solid var(--card-border); color: var(--text-muted); }
107
+
108
+ .loading-spinner {
109
+ display: inline-block; width: 20px; height: 20px;
110
+ border: 2px solid rgba(255, 255, 255, 0.3);
111
+ border-top-color: white; border-radius: 50%;
112
+ animation: spin 0.8s linear infinite; margin-right: 8px; vertical-align: middle;
113
+ }
114
+
115
+ @keyframes spin { to { transform: rotate(360deg); } }
116
+ </style>
117
+ </head>
118
+ <body>
119
+ <div class="glass-container">
120
+ <div class="logo">
121
+ <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" class="logo-img" alt="Fox Logo">
122
+ </div>
123
+ <h2>系统管理控制台</h2>
124
+ <p class="subtitle">Fox Control 系统管理后台</p>
125
+
126
+ <div class="input-group">
127
+ <label for="password">管理员密码</label>
128
+ <input type="password" id="password" placeholder="请输入管理员密码" autocomplete="current-password">
129
+ </div>
130
+
131
+ <button id="loginBtn">登录</button>
132
+
133
+ <div id="status-modal"></div>
134
+ </div>
135
+
136
+ <script>
137
+ const passwordInput = document.getElementById('password');
138
+ const loginBtn = document.getElementById('loginBtn');
139
+ const statusModal = document.getElementById('status-modal');
140
+
141
+ let isLoggingIn = false;
142
+
143
+ passwordInput.addEventListener('keypress', (e) => {
144
+ if (e.key === 'Enter' && !isLoggingIn) {
145
+ loginBtn.click();
146
+ }
147
+ });
148
+
149
+ loginBtn.onclick = async () => {
150
+ const password = passwordInput.value.trim();
151
+ if (!password) {
152
+ showStatus('请输入密码', 'error');
153
+ return;
154
+ }
155
+
156
+ if (isLoggingIn) return;
157
+
158
+ isLoggingIn = true;
159
+ loginBtn.disabled = true;
160
+ showStatus('<span class="loading-spinner"></span>正在登录...', 'loading');
161
+
162
+ try {
163
+ const res = await fetch('/foxcontrol_api/admin/login', {
164
+ method: 'POST',
165
+ headers: { 'Content-Type': 'application/json' },
166
+ body: JSON.stringify({ password })
167
+ });
168
+
169
+ const data = await res.json();
170
+
171
+ if (data.status === 'success') {
172
+ showStatus('登录成功,正在跳转...', 'success');
173
+ setTimeout(() => {
174
+ window.location.href = '../foxcontroladmin_dashboard';
175
+ }, 1000);
176
+ } else {
177
+ throw new Error(data.message || '登录失败');
178
+ }
179
+ } catch (err) {
180
+ showStatus('登录失败: ' + err.message, 'error');
181
+ isLoggingIn = false;
182
+ loginBtn.disabled = false;
183
+ }
184
+ };
185
+
186
+ function showStatus(message, type) {
187
+ statusModal.style.display = 'block';
188
+ statusModal.className = 'modal-' + type;
189
+ statusModal.innerHTML = message;
190
+ }
191
+
192
+ passwordInput.focus();
193
+ </script>
194
+ </body>
195
+ </html>