From a9aa31cc4a1920ed41615ee54df0dd2e1a18a6a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=9C=EA=B3=A8=EC=95=BD=EC=82=AC?= Date: Sat, 13 Sep 2025 23:28:20 +0900 Subject: [PATCH] Implement FarmQ Admin machine name display fix for Magic DNS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix machine management page to display proper Magic DNS names: - Use given_name instead of hostname for machine display - Add Magic DNS address with copy-to-clipboard functionality - Distinguish between machine name and OS hostname like Headplane - Enhance UI with Magic DNS information (.headscale.local) Changes: - farmq-admin/utils/database_new.py: Use given_name for machine_name - farmq-admin/models/farmq_models.py: Update sync logic for given_name - farmq-admin/templates/machines/list.html: Add Magic DNS display with copy feature - FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md: Complete analysis and implementation plan Now displays: - Machine Name: pbs-hp (Magic DNS name) - Magic DNS: pbs-hp.headscale.local (with copy button) - OS Hostname: proxmox-backup-server (system name) ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md | 289 +++++++++++++++++++++++ farmq-admin/models/farmq_models.py | 3 +- farmq-admin/templates/machines/list.html | 22 +- farmq-admin/utils/database_new.py | 2 +- 4 files changed, 313 insertions(+), 3 deletions(-) create mode 100644 FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md diff --git a/FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md b/FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md new file mode 100644 index 0000000..b2392af --- /dev/null +++ b/FARMQ_ADMIN_MACHINE_NAME_FIX_PLAN.md @@ -0,0 +1,289 @@ +# FarmQ Admin ๋จธ์‹  ์ด๋ฆ„ ํ‘œ์‹œ ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ณ„ํš + +## ๐Ÿ“‹ ๋ฌธ์ œ ์ƒํ™ฉ + +ํ˜„์žฌ FarmQ Admin์˜ ๋จธ์‹  ๊ด€๋ฆฌ ํŽ˜์ด์ง€์—์„œ **๋จธ์‹  ์ด๋ฆ„์ด hostname์œผ๋กœ๋งŒ ํ‘œ์‹œ**๋˜๊ณ  ์žˆ์–ด, Magic DNS์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์‹ค์ œ ๋…ธ๋“œ ์ด๋ฆ„(`given_name`)๊ณผ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. + +## ๐Ÿ” ๋ฌธ์ œ ๋ถ„์„ + +### 1. ํ˜„์žฌ ์ƒํ™ฉ + +**Headscale CLI API ์‹ค์ œ ๋ฐ์ดํ„ฐ:** +```json +{ + "id": 1, + "name": "0bin-Ubuntu-VM", // โŒ ์‹ค์ œ Magic DNS์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฆ„ + "given_name": "0bin-ubuntu-vm", // โœ… Magic DNS ์ ‘๋‘์–ด (pev.headscale.local) + "ip_addresses": ["100.64.0.1"], + "user": {"name": "myuser"}, + "online": true +} +``` + +**FarmQ Admin ํ˜„์žฌ ํ‘œ์‹œ:** +```html +{{ machine_data.machine_name or machine_data.hostname }} +
{{ machine_data.hostname }}
+``` + +### 2. ๋ฌธ์ œ์˜ ๊ทผ๋ณธ ์›์ธ + +#### A. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๋ถˆ์ผ์น˜ + +**Headscale ๋ชจ๋ธ:** +```python +# models/headscale_models.py +class Node: + hostname = Column(String) # ์‹œ์Šคํ…œ ํ˜ธ์ŠคํŠธ๋ช… (0bin-Ubuntu-VM) + given_name = Column(String) # ์‚ฌ์šฉ์ž ์ง€์ • ์ด๋ฆ„ (0bin-ubuntu-vm) - Magic DNS์šฉ +``` + +**FarmQ ๋ชจ๋ธ:** +```python +# models/farmq_models.py +class MachineProfile: + hostname = Column(String) # hostname์œผ๋กœ ๋ณต์‚ฌ๋จ + machine_name = Column(String) # hostname์œผ๋กœ ์ค‘๋ณต ์ €์žฅ๋จ โŒ +``` + +#### B. ๋™๊ธฐํ™” ๋กœ์ง ๋ฌธ์ œ + +```python +# utils/database_new.py (line 343) +machine_data = { + 'hostname': node.hostname, # "0bin-Ubuntu-VM" + 'machine_name': node.hostname, # โŒ hostname ์ค‘๋ณต! given_name์ด์–ด์•ผ ํ•จ + 'tailscale_ip': node.ipv4, +} +``` + +#### C. ํ…œํ”Œ๋ฆฟ ํ‘œ์‹œ ๋ฌธ์ œ + +```html + +{{ machine_data.machine_name or machine_data.hostname }} + + +``` + +## โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ + +### 1. ์ฆ‰์‹œ ์ˆ˜์ • (Quick Fix) + +#### A. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋™๊ธฐํ™” ๋กœ์ง ์ˆ˜์ • +```python +# farmq-admin/utils/database_new.py +def get_all_machines_with_details(): + machine_data = { + 'hostname': node.hostname, # ์‹œ์Šคํ…œ ํ˜ธ์ŠคํŠธ๋ช… + 'machine_name': node.given_name, # โœ… Magic DNS ์ด๋ฆ„์œผ๋กœ ๋ณ€๊ฒฝ + 'display_name': node.given_name or node.hostname, # ํ‘œ์‹œ์šฉ ์ด๋ฆ„ + 'tailscale_ip': node.ipv4, + } +``` + +#### B. FarmQ ๋ชจ๋ธ ๋™๊ธฐํ™” ์ˆ˜์ • +```python +# models/farmq_models.py - sync_machine_from_headscale +machine = MachineProfile( + hostname=headscale_node_data.get('hostname'), + machine_name=headscale_node_data.get('given_name'), # โœ… given_name ์‚ฌ์šฉ + tailscale_ip=headscale_node_data.get('ipv4'), +) +``` + +#### C. ํ…œํ”Œ๋ฆฟ ํ‘œ์‹œ ๊ฐœ์„  +```html + +{{ machine_data.machine_name or machine_data.hostname }} +
+ {{ machine_data.machine_name }}.headscale.local +
+
+ {{ machine_data.hostname }} +
+``` + +### 2. ๊ทผ๋ณธ์  ๊ฐœ์„  (Long-term) + +#### A. ํ•„๋“œ ๋ช…์นญ ๋ช…ํ™•ํ™” +```python +class MachineProfile: + system_hostname = Column(String) # ์‹œ์Šคํ…œ ํ˜ธ์ŠคํŠธ๋ช… (0bin-Ubuntu-VM) + headscale_name = Column(String) # Headscale given_name (0bin-ubuntu-vm) + magic_dns_name = Column(String) # Magic DNS ์ „์ฒด ์ด๋ฆ„ (0bin-ubuntu-vm.headscale.local) + display_name = Column(String) # ์‚ฌ์šฉ์ž ํ‘œ์‹œ์šฉ ์ด๋ฆ„ +``` + +#### B. Magic DNS ์ •๋ณด ํ‘œ์‹œ ๊ฐ•ํ™” +```html +
+
{{ machine.display_name }}
+
+ {{ machine.magic_dns_name }} + +
+
+ ์‹œ์Šคํ…œ: {{ machine.system_hostname }} +
+
+``` + +## ๐Ÿš€ ๊ตฌํ˜„ ๋‹จ๊ณ„ + +### Phase 1: ๊ธด๊ธ‰ ์ˆ˜์ • (1-2์‹œ๊ฐ„) + +1. **๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ๋กœ์ง ์ˆ˜์ •** + ```bash + # ํŒŒ์ผ: farmq-admin/utils/database_new.py + # ๋ผ์ธ: 343 + 'machine_name': node.given_name, # hostname โ†’ given_name + ``` + +2. **ํ…œํ”Œ๋ฆฟ ํ‘œ์‹œ ๊ฐœ์„ ** + ```bash + # ํŒŒ์ผ: farmq-admin/templates/machines/list.html + # Magic DNS ์ •๋ณด ์ถ”๊ฐ€ ํ‘œ์‹œ + ``` + +3. **๋™๊ธฐํ™” ํ•จ์ˆ˜ ์ˆ˜์ •** + ```bash + # ํŒŒ์ผ: farmq-admin/models/farmq_models.py + # sync_machine_from_headscale ํ•จ์ˆ˜ ์ˆ˜์ • + ``` + +### Phase 2: ํ‘œ์‹œ ๊ฐœ์„  (2-3์‹œ๊ฐ„) + +1. **Magic DNS ์ •๋ณด ๊ฐ•ํ™” ํ‘œ์‹œ** + - `.headscale.local` ์ ‘๋ฏธ์‚ฌ ์ž๋™ ํ‘œ์‹œ + - ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ ๊ธฐ๋Šฅ + - ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ + +2. **ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ์ถ”๊ฐ€** + - Magic DNS ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰ + - ์˜จ๋ผ์ธ/์˜คํ”„๋ผ์ธ ํ•„ํ„ฐ + - ์‚ฌ์šฉ์ž๋ณ„ ํ•„ํ„ฐ + +### Phase 3: ๊ตฌ์กฐ์  ๊ฐœ์„  (4-6์‹œ๊ฐ„) + +1. **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๊ฐœ์„ ** + - ํ•„๋“œ๋ช… ๋ช…ํ™•ํ™” + - Magic DNS ์ „์šฉ ํ•„๋“œ ์ถ”๊ฐ€ + - ์ธ๋ฑ์Šค ์ตœ์ ํ™” + +2. **API ํ†ตํ•ฉ ๊ฐœ์„ ** + - Headscale CLI ์‘๋‹ต ์บ์‹ฑ + - ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + - WebSocket์„ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ + +## ๐Ÿ“Š ์˜ˆ์ƒ ๊ฒฐ๊ณผ + +### Before (ํ˜„์žฌ) +``` +๋จธ์‹  ์ด๋ฆ„: 0bin-Ubuntu-VM # hostname (์‹œ์Šคํ…œ๋ช…) +ํ˜ธ์ŠคํŠธ๋ช…: 0bin-Ubuntu-VM # ์ค‘๋ณต ์ •๋ณด +Magic DNS: ์‚ฌ์šฉ๋ถˆ๊ฐ€ โŒ # given_name ์ •๋ณด ๋ถ€์กฑ +``` + +### After (์ˆ˜์ • ํ›„) +``` +๋จธ์‹  ์ด๋ฆ„: 0bin-ubuntu-vm # given_name (Magic DNS์šฉ) +Magic DNS: 0bin-ubuntu-vm.headscale.local โœ… +์‹œ์Šคํ…œ๋ช…: 0bin-Ubuntu-VM # hostname (์ฐธ๊ณ  ์ •๋ณด) +IP ์ฃผ์†Œ: 100.64.0.1 # ํ˜„์žฌ์™€ ๋™์ผ +``` + +## ๐Ÿงช ํ…Œ์ŠคํŠธ ๊ณ„ํš + +### 1. ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ +```python +# ํ…Œ์ŠคํŠธ ์Šคํฌ๋ฆฝํŠธ +def test_machine_name_mapping(): + nodes = headscale_session.query(Node).all() + for node in nodes: + print(f"ID: {node.id}") + print(f"Hostname: {node.hostname}") # 0bin-Ubuntu-VM + print(f"Given Name: {node.given_name}") # 0bin-ubuntu-vm + print(f"Magic DNS: {node.given_name}.headscale.local") + print("---") +``` + +### 2. Magic DNS ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ +```bash +# ๊ฐ ๋…ธ๋“œ๋ณ„ Magic DNS ํ…Œ์ŠคํŠธ +ping 0bin-ubuntu-vm.headscale.local +ping pev.headscale.local +ping pqserver.headscale.local +``` + +### 3. UI ํ‘œ์‹œ ํ™•์ธ +- ๋จธ์‹  ๋ชฉ๋ก์—์„œ ์˜ฌ๋ฐ”๋ฅธ ์ด๋ฆ„ ํ‘œ์‹œ +- Magic DNS ์ฃผ์†Œ ๋ณต์‚ฌ ๊ธฐ๋Šฅ +- ์—ฐ๊ฒฐ ์ƒํƒœ์™€ ์ผ์น˜์„ฑ ํ™•์ธ + +## ๐Ÿ“ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +### ์ฝ”๋“œ ์ˆ˜์ • +- [ ] `farmq-admin/utils/database_new.py` - ๋™๊ธฐํ™” ๋กœ์ง ์ˆ˜์ • +- [ ] `farmq-admin/models/farmq_models.py` - ๋ชจ๋ธ ๋™๊ธฐํ™” ์ˆ˜์ • +- [ ] `farmq-admin/templates/machines/list.html` - ํ‘œ์‹œ ๊ฐœ์„  +- [ ] `farmq-admin/templates/machines/detail.html` - ์ƒ์„ธ ํŽ˜์ด์ง€ ์ˆ˜์ • + +### ํ…Œ์ŠคํŠธ +- [ ] ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋™๊ธฐํ™” ํ…Œ์ŠคํŠธ +- [ ] Magic DNS ์ด๋ฆ„ ํ‘œ์‹œ ํ™•์ธ +- [ ] UI ํ‘œ์‹œ ์ •์ƒ์„ฑ ํ™•์ธ +- [ ] ๊ธฐ์กด ๊ธฐ๋Šฅ ํ˜ธํ™˜์„ฑ ํ…Œ์ŠคํŠธ + +### ๋ฌธ์„œํ™” +- [ ] ๋ณ€๊ฒฝ์‚ฌํ•ญ README ์—…๋ฐ์ดํŠธ +- [ ] API ๋ฌธ์„œ ๊ฐฑ์‹  +- [ ] ์‚ฌ์šฉ์ž ๊ฐ€์ด๋“œ ์ˆ˜์ • + +## ๐Ÿ”ง ๊ตฌํ˜„ ํŒŒ์ผ ๋ชฉ๋ก + +### ์ˆ˜์ •ํ•  ํŒŒ์ผ๋“ค +1. **`farmq-admin/utils/database_new.py`** (๋ผ์ธ 343) + - `machine_name` ํ•„๋“œ๋ฅผ `given_name`์œผ๋กœ ๋ณ€๊ฒฝ + +2. **`farmq-admin/models/farmq_models.py`** (๋ผ์ธ 445) + - ๋™๊ธฐํ™” ์‹œ `given_name` ์‚ฌ์šฉ + +3. **`farmq-admin/templates/machines/list.html`** (๋ผ์ธ 115-119) + - Magic DNS ์ •๋ณด ์ถ”๊ฐ€ ํ‘œ์‹œ + +4. **`farmq-admin/templates/machines/detail.html`** + - ์ƒ์„ธ ํŽ˜์ด์ง€ Magic DNS ์ •๋ณด ๊ฐœ์„  + +### ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•  ๊ธฐ๋Šฅ +- Magic DNS ์ฃผ์†Œ ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ +- ์—ฐ๊ฒฐ ํ…Œ์ŠคํŠธ ๋ฒ„ํŠผ +- ์‹ค์‹œ๊ฐ„ ์˜จ๋ผ์ธ ์ƒํƒœ ํ‘œ์‹œ + +## ๐Ÿ’ก ์žฅ๊ธฐ์  ๊ฐœ์„ ์‚ฌํ•ญ + +### 1. Headscale API ์ง์ ‘ ํ†ตํ•ฉ +ํ˜„์žฌ CLI ๊ธฐ๋ฐ˜ โ†’ REST API ์ง์ ‘ ํ˜ธ์ถœ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์„ฑ๋Šฅ ๊ฐœ์„  + +### 2. ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง +WebSocket์„ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ๋…ธ๋“œ ์ƒํƒœ ์—…๋ฐ์ดํŠธ + +### 3. Magic DNS ๊ด€๋ฆฌ ๊ธฐ๋Šฅ +- ๋…ธ๋“œ ์ด๋ฆ„ ๋ณ€๊ฒฝ +- Magic DNS ๋„๋ฉ”์ธ ์„ค์ • +- DNS ํ•ด์„ ํ…Œ์ŠคํŠธ ๋„๊ตฌ + +## ๐Ÿ“… ๊ตฌํ˜„ ์ผ์ • + +| ๋‹จ๊ณ„ | ์ž‘์—… | ์†Œ์š”์‹œ๊ฐ„ | ์™„๋ฃŒ์ผ | +|------|------|---------|---------| +| Phase 1 | ๊ธด๊ธ‰ ์ˆ˜์ • | 2์‹œ๊ฐ„ | ๋‹น์ผ | +| Phase 2 | ํ‘œ์‹œ ๊ฐœ์„  | 3์‹œ๊ฐ„ | 1์ผ | +| Phase 3 | ๊ตฌ์กฐ ๊ฐœ์„  | 6์‹œ๊ฐ„ | 2-3์ผ | + +--- + +**์ž‘์„ฑ์ผ:** 2025๋…„ 9์›” 13์ผ +**์—…๋ฐ์ดํŠธ:** FarmQ Admin ๋จธ์‹  ์ด๋ฆ„ ํ‘œ์‹œ ๋ฌธ์ œ ๋ถ„์„ ๋ฐ ํ•ด๊ฒฐ ๊ณ„ํš ์ˆ˜๋ฆฝ ์™„๋ฃŒ \ No newline at end of file diff --git a/farmq-admin/models/farmq_models.py b/farmq-admin/models/farmq_models.py index ccdcbfc..54b2c99 100644 --- a/farmq-admin/models/farmq_models.py +++ b/farmq-admin/models/farmq_models.py @@ -432,6 +432,7 @@ class FarmqDatabaseManager: if machine: # ๊ธฐ์กด ๋จธ์‹  ์—…๋ฐ์ดํŠธ machine.hostname = headscale_node_data.get('hostname') + machine.machine_name = headscale_node_data.get('given_name') or headscale_node_data.get('hostname') machine.tailscale_ip = headscale_node_data.get('ipv4') machine.tailscale_status = 'online' if headscale_node_data.get('is_online') else 'offline' machine.last_seen = datetime.now() @@ -442,7 +443,7 @@ class FarmqDatabaseManager: headscale_node_id=headscale_node_data.get('id'), headscale_machine_key=headscale_node_data.get('machine_key'), hostname=headscale_node_data.get('hostname'), - machine_name=headscale_node_data.get('hostname'), + machine_name=headscale_node_data.get('given_name') or headscale_node_data.get('hostname'), tailscale_ip=headscale_node_data.get('ipv4'), tailscale_status='online' if headscale_node_data.get('is_online') else 'offline', last_seen=datetime.now() diff --git a/farmq-admin/templates/machines/list.html b/farmq-admin/templates/machines/list.html index e64c4aa..41f5d7a 100644 --- a/farmq-admin/templates/machines/list.html +++ b/farmq-admin/templates/machines/list.html @@ -113,7 +113,17 @@
{{ machine_data.machine_name or machine_data.hostname }} -
{{ machine_data.hostname }}
+
+ {{ machine_data.machine_name or machine_data.hostname }}.headscale.local + +
+ {% if machine_data.hostname != (machine_data.machine_name or machine_data.hostname) %} +
+ OS: {{ machine_data.hostname }} +
+ {% endif %}
{{ machine_data.headscale_user_name or '๋ฏธ์ง€์ •' }}
@@ -440,6 +450,16 @@ function deleteNode(nodeId, nodeName) { }); } +// Magic DNS ์ฃผ์†Œ ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ ๊ธฐ๋Šฅ +function copyToClipboard(text) { + navigator.clipboard.writeText(text).then(() => { + showToast(`Magic DNS ์ฃผ์†Œ๊ฐ€ ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค: ${text}`, 'success'); + }).catch(err => { + console.error('๋ณต์‚ฌ ์‹คํŒจ:', err); + showToast('๋ณต์‚ฌ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.', 'danger'); + }); +} + // ์ดˆ๊ธฐ ์นด์šดํ„ฐ ์„ค์ • document.addEventListener('DOMContentLoaded', function() { filterMachines(); diff --git a/farmq-admin/utils/database_new.py b/farmq-admin/utils/database_new.py index 0d72ad6..e9c8e4e 100644 --- a/farmq-admin/utils/database_new.py +++ b/farmq-admin/utils/database_new.py @@ -340,7 +340,7 @@ def get_all_machines_with_details() -> List[Dict[str, Any]]: machine_data = { 'id': node.id, 'hostname': node.hostname, - 'machine_name': node.hostname, # ํ‘œ์‹œ์šฉ ์ด๋ฆ„ + 'machine_name': node.given_name or node.hostname, # Magic DNS์šฉ ์ด๋ฆ„ (given_name ์šฐ์„ ) 'tailscale_ip': node.ipv4, 'ipv6': node.ipv6, 'headscale_user_name': node.user.name if node.user else '๋ฏธ์ง€์ •',