jeongsoo commited on
Commit
f09b46a
ยท
1 Parent(s): 2ed13ec
Files changed (1) hide show
  1. index.html +508 -18
index.html CHANGED
@@ -1,19 +1,509 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>LocalPCAgent ์ œ์–ด</title>
7
+ <style>
8
+ body {
9
+ font-family: -apple-system, system-ui, sans-serif;
10
+ max-width: 800px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ line-height: 1.6;
14
+ }
15
+ .container { margin-bottom: 20px; }
16
+ .card {
17
+ border: 1px solid #ddd;
18
+ border-radius: 5px;
19
+ padding: 15px;
20
+ margin-bottom: 15px;
21
+ }
22
+ button {
23
+ background-color: #4CAF50;
24
+ color: white;
25
+ border: none;
26
+ padding: 10px 15px;
27
+ border-radius: 4px;
28
+ cursor: pointer;
29
+ }
30
+ button:hover { background-color: #45a049; }
31
+ input, select, textarea {
32
+ width: 100%;
33
+ padding: 8px;
34
+ margin: 8px 0;
35
+ border: 1px solid #ccc;
36
+ border-radius: 4px;
37
+ box-sizing: border-box;
38
+ }
39
+ .log-area {
40
+ background-color: #f5f5f5;
41
+ border: 1px solid #ddd;
42
+ padding: 10px;
43
+ height: 200px;
44
+ overflow-y: auto;
45
+ font-family: monospace;
46
+ }
47
+ .log-success { color: green; }
48
+ .log-error { color: red; }
49
+ .tab-buttons { display: flex; margin-bottom: 15px; }
50
+ .tab-button {
51
+ flex: 1;
52
+ background-color: #f1f1f1;
53
+ padding: 10px;
54
+ text-align: center;
55
+ cursor: pointer;
56
+ }
57
+ .tab-button.active {
58
+ background-color: #4CAF50;
59
+ color: white;
60
+ }
61
+ .tab-content { display: none; }
62
+ .tab-content.active { display: block; }
63
+ .status-indicator {
64
+ display: inline-block;
65
+ padding: 5px 10px;
66
+ border-radius: 4px;
67
+ margin-left: 10px;
68
+ }
69
+ .connected {
70
+ background-color: rgba(16, 185, 129, 0.1);
71
+ color: #10b981;
72
+ }
73
+ .disconnected {
74
+ background-color: rgba(239, 68, 68, 0.1);
75
+ color: #ef4444;
76
+ }
77
+ .result-panel {
78
+ background-color: #f8f8f8;
79
+ border: 1px solid #ddd;
80
+ padding: 10px;
81
+ margin-top: 10px;
82
+ min-height: 50px;
83
+ max-height: 200px;
84
+ overflow-y: auto;
85
+ font-family: monospace;
86
+ white-space: pre-wrap;
87
+ }
88
+ </style>
89
+ </head>
90
+ <body>
91
+ <h1>LocalPCAgent ์ œ์–ด ์ธํ„ฐํŽ˜์ด์Šค</h1>
92
+
93
+ <div class="container">
94
+ <div class="card">
95
+ <h2>์„œ๋ฒ„ ์—ฐ๊ฒฐ</h2>
96
+ <div style="display: flex; align-items: center;">
97
+ <input type="text" id="serverUrl" placeholder="ngrok URL ์ž…๋ ฅ (์˜ˆ: https://xxxx-xx-xx-xxx-xx.ngrok.io)">
98
+ <button id="connectBtn">์—ฐ๊ฒฐ</button>
99
+ <div id="connectionStatus" class="status-indicator disconnected">์—ฐ๊ฒฐ ์•ˆ๋จ</div>
100
+ </div>
101
+ </div>
102
+ </div>
103
+
104
+ <div class="tab-buttons">
105
+ <div class="tab-button active" data-tab="basic">๊ธฐ๋ณธ ๊ธฐ๋Šฅ</div>
106
+ <div class="tab-button" data-tab="devices">์žฅ์น˜ ๊ด€๋ฆฌ</div>
107
+ <div class="tab-button" data-tab="programs">ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰</div>
108
+ </div>
109
+
110
+ <div id="basic" class="tab-content active">
111
+ <div class="card">
112
+ <h3>์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ</h3>
113
+ <button id="statusBtn">์ƒํƒœ ํ™•์ธ</button>
114
+ <div id="statusResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
115
+ </div>
116
+
117
+ <div class="card">
118
+ <h3>์—์ฝ” ํ…Œ์ŠคํŠธ</h3>
119
+ <input type="text" id="echoMessage" value="Hello from HuggingFace!" placeholder="ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€">
120
+ <button id="echoBtn">์—์ฝ” ํ…Œ์ŠคํŠธ</button>
121
+ <div id="echoResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
122
+ </div>
123
+ </div>
124
+
125
+ <div id="devices" class="tab-content">
126
+ <div class="card">
127
+ <h3>์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ</h3>
128
+ <button id="getDevicesBtn">์žฅ์น˜ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ</button>
129
+ <div id="devicesResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
130
+ </div>
131
+
132
+ <div class="card">
133
+ <h3>์žฅ์น˜ ์ •๋ณด ์กฐํšŒ</h3>
134
+ <select id="deviceSelect">
135
+ <option value="">-- ์žฅ์น˜ ์„ ํƒ --</option>
136
+ </select>
137
+ <button id="getDeviceInfoBtn">์žฅ์น˜ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ</button>
138
+ <div id="deviceInfoResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
139
+ </div>
140
+ </div>
141
+
142
+ <div id="programs" class="tab-content">
143
+ <div class="card">
144
+ <h3>ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ</h3>
145
+ <button id="getProgramsBtn">ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ</button>
146
+ <div id="programsResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
147
+ </div>
148
+
149
+ <div class="card">
150
+ <h3>ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰</h3>
151
+ <select id="programSelect">
152
+ <option value="">-- ํ”„๋กœ๊ทธ๋žจ ์„ ํƒ --</option>
153
+ </select>
154
+ <button id="executeProgramBtn">ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰</button>
155
+ <div id="executeProgramResult" class="result-panel">๊ฒฐ๊ณผ๊ฐ€ ์—ฌ๊ธฐ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>
156
+ </div>
157
+ </div>
158
+
159
+ <div class="container">
160
+ <h2>๋กœ๊ทธ</h2>
161
+ <button id="clearLogBtn">๋กœ๊ทธ ์ง€์šฐ๊ธฐ</button>
162
+ <div id="logArea" class="log-area"></div>
163
+ </div>
164
+
165
+ <script>
166
+ // ์ƒํƒœ ๋ณ€์ˆ˜
167
+ let serverUrl = '';
168
+ let isConnected = false;
169
+
170
+ // ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์—์„œ URL ๋กœ๋“œ
171
+ document.addEventListener('DOMContentLoaded', () => {
172
+ const savedUrl = localStorage.getItem('serverUrl');
173
+ if (savedUrl) {
174
+ document.getElementById('serverUrl').value = savedUrl;
175
+ serverUrl = savedUrl;
176
+ checkConnection();
177
+ }
178
+
179
+ // ํƒญ ์ „ํ™˜ ์ด๋ฒคํŠธ
180
+ document.querySelectorAll('.tab-button').forEach(button => {
181
+ button.addEventListener('click', () => {
182
+ const tabId = button.getAttribute('data-tab');
183
+ activateTab(tabId);
184
+ });
185
+ });
186
+
187
+ // ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ
188
+ document.getElementById('connectBtn').addEventListener('click', handleConnect);
189
+ document.getElementById('statusBtn').addEventListener('click', checkStatus);
190
+ document.getElementById('echoBtn').addEventListener('click', testEcho);
191
+ document.getElementById('getDevicesBtn').addEventListener('click', getDevices);
192
+ document.getElementById('getDeviceInfoBtn').addEventListener('click', getDeviceInfo);
193
+ document.getElementById('getProgramsBtn').addEventListener('click', getPrograms);
194
+ document.getElementById('executeProgramBtn').addEventListener('click', executeProgram);
195
+ document.getElementById('clearLogBtn').addEventListener('click', clearLog);
196
+ });
197
+
198
+ // ๋กœ๊ทธ ํ•จ์ˆ˜
199
+ function addLog(message, type = 'info') {
200
+ console.log(`[${type.toUpperCase()}] ${message}`);
201
+ const logArea = document.getElementById('logArea');
202
+ const logEntry = document.createElement('div');
203
+ logEntry.className = type ? `log-${type}` : '';
204
+ logEntry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
205
+ logArea.appendChild(logEntry);
206
+ logArea.scrollTop = logArea.scrollHeight;
207
+ }
208
+
209
+ // ํƒญ ํ™œ์„ฑํ™”
210
+ function activateTab(tabId) {
211
+ document.querySelectorAll('.tab-button').forEach(btn => {
212
+ btn.classList.toggle('active', btn.getAttribute('data-tab') === tabId);
213
+ });
214
+
215
+ document.querySelectorAll('.tab-content').forEach(content => {
216
+ content.classList.toggle('active', content.id === tabId);
217
+ });
218
+ }
219
+
220
+ // ์—ฐ๊ฒฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
221
+ function updateConnectionStatus(connected) {
222
+ isConnected = connected;
223
+ const status = document.getElementById('connectionStatus');
224
+
225
+ if (connected) {
226
+ status.textContent = '์—ฐ๊ฒฐ๋จ';
227
+ status.className = 'status-indicator connected';
228
+ } else {
229
+ status.textContent = '์—ฐ๊ฒฐ ์•ˆ๋จ';
230
+ status.className = 'status-indicator disconnected';
231
+ }
232
+ }
233
+
234
+ // CORS ์šฐํšŒ fetch ๋ž˜ํผ ํ•จ์ˆ˜
235
+ async function fetchWithCors(url, options = {}) {
236
+ try {
237
+ // ์ง์ ‘ ์—ฐ๊ฒฐ ์‹œ๋„
238
+ const response = await fetch(url, {
239
+ ...options,
240
+ mode: 'cors',
241
+ headers: {
242
+ ...options.headers,
243
+ 'Accept': 'application/json'
244
+ }
245
+ });
246
+ if (response.ok) return response;
247
+ } catch (error) {
248
+ console.log('์ง์ ‘ ์—ฐ๊ฒฐ ์‹คํŒจ, ํ”„๋ก์‹œ ์‹œ๋„:', error);
249
+ }
250
+
251
+ // ํ”„๋ก์‹œ ์‚ฌ์šฉ ์‹œ๋„
252
+ try {
253
+ const proxyUrl = 'https://cors-anywhere.herokuapp.com/';
254
+ return fetch(proxyUrl + url, {
255
+ ...options,
256
+ headers: {
257
+ ...options.headers,
258
+ 'X-Requested-With': 'XMLHttpRequest'
259
+ }
260
+ });
261
+ } catch (proxyError) {
262
+ addLog(`CORS ํ”„๋ก์‹œ ์˜ค๋ฅ˜: ${proxyError.message}`, 'error');
263
+ throw proxyError;
264
+ }
265
+ }
266
+
267
+ // ์—ฐ๊ฒฐ ๋ฒ„ํŠผ ํ•ธ๋“ค๋Ÿฌ
268
+ function handleConnect() {
269
+ const urlInput = document.getElementById('serverUrl');
270
+ const url = urlInput.value.trim();
271
+
272
+ if (!url) {
273
+ addLog('์„œ๋ฒ„ URL์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!', 'error');
274
+ return;
275
+ }
276
+
277
+ // URL ๋งˆ์ง€๋ง‰์˜ ์Šฌ๋ž˜์‹œ ์ œ๊ฑฐ
278
+ serverUrl = url.endsWith('/') ? url.slice(0, -1) : url;
279
+ localStorage.setItem('serverUrl', serverUrl);
280
+
281
+ checkConnection();
282
+ }
283
+
284
+ // ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ
285
+ async function checkConnection() {
286
+ if (!serverUrl) return;
287
+
288
+ addLog(`์„œ๋ฒ„ ์—ฐ๊ฒฐ ํ™•์ธ ์ค‘: ${serverUrl}`);
289
+
290
+ try {
291
+ const response = await fetchWithCors(`${serverUrl}/health`);
292
+ if (response.ok) {
293
+ updateConnectionStatus(true);
294
+ addLog('์„œ๋ฒ„ ์—ฐ๊ฒฐ ์„ฑ๊ณต!', 'success');
295
+ } else {
296
+ updateConnectionStatus(false);
297
+ addLog(`์„œ๋ฒ„ ์‘๋‹ต ์˜ค๋ฅ˜: ${response.status}`, 'error');
298
+ }
299
+ } catch (error) {
300
+ updateConnectionStatus(false);
301
+ addLog(`์„œ๋ฒ„ ์—ฐ๊ฒฐ ์‹คํŒจ: ${error.message}`, 'error');
302
+ }
303
+ }
304
+
305
+ // ์—ฐ๊ฒฐ ํ™•์ธ ํ•จ์ˆ˜
306
+ function validateConnection() {
307
+ if (!serverUrl || !isConnected) {
308
+ addLog('์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋จผ์ € ์—ฐ๊ฒฐํ•˜์„ธ์š”.', 'error');
309
+ return false;
310
+ }
311
+ return true;
312
+ }
313
+
314
+ // ์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ
315
+ async function checkStatus() {
316
+ if (!validateConnection()) return;
317
+
318
+ addLog('์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ ์ค‘...');
319
+
320
+ try {
321
+ const response = await fetchWithCors(`${serverUrl}/api/status`);
322
+
323
+ if (!response.ok) {
324
+ addLog(`์ƒํƒœ ํ™•์ธ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
325
+ return;
326
+ }
327
+
328
+ const result = await response.json();
329
+ document.getElementById('statusResult').textContent = JSON.stringify(result, null, 2);
330
+ addLog('์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ ์„ฑ๊ณต', 'success');
331
+ } catch (error) {
332
+ addLog(`์ƒํƒœ ํ™•์ธ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
333
+ }
334
+ }
335
+
336
+ // ์—์ฝ” ํ…Œ์ŠคํŠธ
337
+ async function testEcho() {
338
+ if (!validateConnection()) return;
339
+
340
+ const message = document.getElementById('echoMessage').value.trim();
341
+
342
+ if (!message) {
343
+ addLog('๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!', 'error');
344
+ return;
345
+ }
346
+
347
+ addLog(`์—์ฝ” ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ „์†ก: "${message}"`);
348
+
349
+ try {
350
+ const response = await fetchWithCors(`${serverUrl}/api/send`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify({
354
+ action: 'echo',
355
+ data: { message: message },
356
+ timestamp: Date.now()
357
+ })
358
+ });
359
+
360
+ if (!response.ok) {
361
+ addLog(`์—์ฝ” ํ…Œ์ŠคํŠธ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
362
+ return;
363
+ }
364
+
365
+ const result = await response.json();
366
+ document.getElementById('echoResult').textContent = JSON.stringify(result, null, 2);
367
+ addLog('์—์ฝ” ํ…Œ์ŠคํŠธ ์„ฑ๊ณต', 'success');
368
+ } catch (error) {
369
+ addLog(`์—์ฝ” ํ…Œ์ŠคํŠธ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
370
+ }
371
+ }
372
+
373
+ // ์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ
374
+ async function getDevices() {
375
+ if (!validateConnection()) return;
376
+
377
+ addLog('์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ ์ค‘...');
378
+
379
+ try {
380
+ const response = await fetchWithCors(`${serverUrl}/api/devices`);
381
+
382
+ if (!response.ok) {
383
+ addLog(`์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
384
+ return;
385
+ }
386
+
387
+ const result = await response.json();
388
+ document.getElementById('devicesResult').textContent = JSON.stringify(result, null, 2);
389
+
390
+ // ์žฅ์น˜ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด ์—…๋ฐ์ดํŠธ
391
+ const deviceSelect = document.getElementById('deviceSelect');
392
+ deviceSelect.innerHTML = '<option value="">-- ์žฅ์น˜ ์„ ํƒ --</option>';
393
+
394
+ result.devices.forEach(device => {
395
+ const option = document.createElement('option');
396
+ option.value = device.id;
397
+ option.textContent = `${device.name} (${device.type})`;
398
+ deviceSelect.appendChild(option);
399
+ });
400
+
401
+ addLog(`์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต: ${result.devices.length}๊ฐœ ์žฅ์น˜ ๋ฐœ๊ฒฌ`, 'success');
402
+ } catch (error) {
403
+ addLog(`์žฅ์น˜ ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
404
+ }
405
+ }
406
+
407
+ // ์žฅ์น˜ ์ •๋ณด ์กฐํšŒ
408
+ async function getDeviceInfo() {
409
+ if (!validateConnection()) return;
410
+
411
+ const deviceId = document.getElementById('deviceSelect').value;
412
+
413
+ if (!deviceId) {
414
+ addLog('์žฅ์น˜๋ฅผ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”!', 'error');
415
+ return;
416
+ }
417
+
418
+ addLog(`์žฅ์น˜ ID "${deviceId}" ์ƒ์„ธ ์ •๋ณด ์กฐํšŒ ์ค‘...`);
419
+
420
+ try {
421
+ const response = await fetchWithCors(`${serverUrl}/api/device/${deviceId}/info`);
422
+
423
+ if (!response.ok) {
424
+ addLog(`์žฅ์น˜ ์ •๋ณด ์กฐํšŒ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
425
+ return;
426
+ }
427
+
428
+ const result = await response.json();
429
+ document.getElementById('deviceInfoResult').textContent = JSON.stringify(result, null, 2);
430
+ addLog('์žฅ์น˜ ์ •๋ณด ์กฐํšŒ ์„ฑ๊ณต', 'success');
431
+ } catch (error) {
432
+ addLog(`์žฅ์น˜ ์ •๋ณด ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
433
+ }
434
+ }
435
+
436
+ // ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ
437
+ async function getPrograms() {
438
+ if (!validateConnection()) return;
439
+
440
+ addLog('ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ ์ค‘...');
441
+
442
+ try {
443
+ const response = await fetchWithCors(`${serverUrl}/api/programs`);
444
+
445
+ if (!response.ok) {
446
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
447
+ return;
448
+ }
449
+
450
+ const result = await response.json();
451
+ document.getElementById('programsResult').textContent = JSON.stringify(result, null, 2);
452
+
453
+ // ํ”„๋กœ๊ทธ๋žจ ์„ ํƒ ๋“œ๋กญ๋‹ค์šด ์—…๋ฐ์ดํŠธ
454
+ const programSelect = document.getElementById('programSelect');
455
+ programSelect.innerHTML = '<option value="">-- ํ”„๋กœ๊ทธ๋žจ ์„ ํƒ --</option>';
456
+
457
+ result.programs.forEach(program => {
458
+ const option = document.createElement('option');
459
+ option.value = program.id;
460
+ option.textContent = `${program.name} - ${program.description || '์„ค๋ช… ์—†์Œ'}`;
461
+ programSelect.appendChild(option);
462
+ });
463
+
464
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ ์„ฑ๊ณต: ${result.programs.length}๊ฐœ ํ”„๋กœ๊ทธ๋žจ ๋ฐœ๊ฒฌ`, 'success');
465
+ } catch (error) {
466
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ๋ชฉ๋ก ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
467
+ }
468
+ }
469
+
470
+ // ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰
471
+ async function executeProgram() {
472
+ if (!validateConnection()) return;
473
+
474
+ const programId = document.getElementById('programSelect').value;
475
+
476
+ if (!programId) {
477
+ addLog('ํ”„๋กœ๊ทธ๋žจ์„ ๋จผ์ € ์„ ํƒํ•ด์ฃผ์„ธ์š”!', 'error');
478
+ return;
479
+ }
480
+
481
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ID "${programId}" ์‹คํ–‰ ์ค‘...`);
482
+
483
+ try {
484
+ const response = await fetchWithCors(`${serverUrl}/api/programs/${programId}/execute`, {
485
+ method: 'POST',
486
+ headers: { 'Content-Type': 'application/json' }
487
+ });
488
+
489
+ if (!response.ok) {
490
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์‹คํŒจ! ์ƒํƒœ ์ฝ”๋“œ: ${response.status}`, 'error');
491
+ return;
492
+ }
493
+
494
+ const result = await response.json();
495
+ document.getElementById('executeProgramResult').textContent = JSON.stringify(result, null, 2);
496
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์„ฑ๊ณต: ${result.message || ''}`, 'success');
497
+ } catch (error) {
498
+ addLog(`ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${error.message}`, 'error');
499
+ }
500
+ }
501
+
502
+ // ๋กœ๊ทธ ์ง€์šฐ๊ธฐ
503
+ function clearLog() {
504
+ document.getElementById('logArea').innerHTML = '';
505
+ addLog('๋กœ๊ทธ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
506
+ }
507
+ </script>
508
+ </body>
509
  </html>