Skip to content

Latest commit

 

History

History
860 lines (748 loc) · 71.3 KB

SPEC.md

File metadata and controls

860 lines (748 loc) · 71.3 KB

Constella 仕様書

ネットワークトポロジ図の作成に特化したデスクトップアプリ。 Packet Tracer のように「機器を置いて、ポートを選んでつなぐ」GUI と、 人や AI が書ける YAML 定義を双方向に同期する。

  • バージョン: 0.1(MVP 設計)
  • 最終更新: 2026-10-30

1. 目的とコンセプト

1.1 解決したい課題

  • draw.io や Visio は汎用作図ツールなので、インターフェース名・ポートの使用状況・VLAN などを手で書き込む必要があり、間違いや更新漏れが起きやすい。
  • 図がバイナリや独自形式だと Git で差分管理しづらく、AI に生成・編集させにくい。

1.2 コンセプト

  1. インターフェース中心: 機器は「機種」を持ち、機種に応じたポートが自動で用意される。リンクは必ず「機器:ポート」同士を結ぶ。
  2. GUI とテキストの双方向同期: GUI で編集すると YAML が更新され、YAML を編集すると図が更新される。
  3. AI フレンドリー: YAML は接続関係だけ書けば図になる(座標は省略可能、自動レイアウト)。JSON Schema を公開する。

1.3 スコープ外(当面やらないこと)

  • パケットの流れや ping などのシミュレーション(将来検討)
  • 実機へのコンフィグ投入
  • 複数人のリアルタイム共同編集

2. 対象プラットフォームと技術スタック

項目 採用技術
対象OS macOS を最初に完成させる。Windows / Linux は後続で対応(同一コードベース)
アプリ基盤 Tauri 2(Rust)
フロントエンド TypeScript(strict)+ React + Vite
図の描画 React Flow(@xyflow/react)
状態管理 Zustand(+ Immer)
YAML 編集画面 CodeMirror 6
YAML パース/出力 yaml(eemeli/yaml。コメント保持のため Document API を使う)
自動レイアウト elkjs
テスト Vitest
整形・静的解析 ESLint + Prettier、Rust は rustfmt + clippy
  • 各ライブラリはプロジェクト作成時点の最新安定版を使う。
  • Rust 側は最小限にする(ファイル入出力・ダイアログは Tauri 公式プラグイン fs / dialog を利用)。ロジックは基本的に TypeScript 側に置く。
  • OS 固有の処理を書く場合は必ず抽象化し、Windows / Linux 対応を妨げないこと。
  • macOS 版は、Apple Silicon・Intel の両方で動くユニバーサル版を、DMG で GitHub Releases に配布する。対応は macOS 13.3 以降。署名は簡易署名(ad-hoc。Apple の Developer ID による署名・公証はしない)。

3. 用語

用語 意味
トポロジ 1つの図。1ファイル = 1トポロジ
デバイス 図に置く機器(ルーター、スイッチ、サーバーなど)
モデル(機種) デバイスの種類定義。ポート一覧・役割・アイコンを持つテンプレート
インターフェース / ポート デバイスが持つ接続口。本仕様では同義
エンドポイント デバイスID:インターフェース名 の形式で表す接続端
リンク 2つのエンドポイントを結ぶ線
ロール デバイスの役割(router / l2switch / l3switch / firewall / server / hypervisor / pc / ap / cloud / generic)
ハイパーバイザ(ホスト) VM やコンテナを動かす機器(Proxmox VE のノードなど)。ロールは hypervisor
ゲスト ホストの上で動く VM(仮想マシン)またはコンテナ(Proxmox VE の LXC)
ブリッジ ホストの中の仮想スイッチ(Proxmox VE の vmbr0 など)。ゲストはブリッジにつなぐ
bond 複数の NIC を 1 本に束ねたもの(リンクアグリゲーション)
VLAN インターフェース 1 つの VLAN だけを扱う論理的な IF(vmbr0.10 など)
クラスタ 複数のホストをまとめて管理する単位(Proxmox VE のクラスタ)
SDN クラスタ全体で仮想ネットワークを定義する仕組み(Proxmox VE の SDN。ゾーンと VNet)

4. ファイル形式(トポロジ YAML)

4.1 基本

  • 拡張子: *.constella.yaml
  • 文字コード: UTF-8
  • 先頭に形式バージョン constella: 1 を必須とする。

4.2 例

# yaml-language-server: $schema=https://raw.githubusercontent.com/stellarcielo/Constella/main/schema/constella.schema.json
constella: 1

meta:
  title: 自宅ラボ DC 区画
  description: 学習用ルーティング網

devices:
  rt1:
    model: generic-router-4
    label: Edge Router
  core-sw1:
    model: cisco-c2960-24tt-l
    interfaces:
      Gi0/1:
        description: to rt1
  srv1:
    model: generic-server-2
  isp:
    role: cloud            # model 省略 = フレキシブル(任意のIF名を使える)

links:
  - a: rt1:Gi0/0
    b: isp:wan
  - a: rt1:Gi0/1
    b: core-sw1:Gi0/1
  - a: core-sw1:Fa0/1
    b: srv1:eth0
    label: mgmt

layout:
  rt1: { x: 300, y: 80 }
  core-sw1: { x: 300, y: 240 }
  # 座標が無いデバイスは自動レイアウトで配置する

4.3 各セクションの定義

devices(マップ。キー = デバイスID)

フィールド 必須 説明
model 任意 モデルID。省略時はフレキシブルデバイス
role 任意 省略時はモデルの role。モデルも無い場合は generic
label 任意 図に表示する名前。省略時はデバイスID
host 任意 ゲストのとき、動いているホストのデバイスID(4.6)
guest 任意 vm / container。省略時はモデルの guest。どちらも無く host があれば vm(4.6)
vmid 任意 Proxmox VE の VMID(100〜999999999 の整数)
interfaces 任意 インターフェースごとの追加情報(下記)

デバイスID の規則: ^[A-Za-z0-9][A-Za-z0-9._-]*$(: は使用不可)

interfaces.<IF名> に書ける項目(MVP では保存・表示のみ。図への反映はフェーズ3以降):

  • description: 文字列
  • ip: 文字列の配列(例: ["192.0.2.1/30", "2001:db8::1/64"])
  • mode: access / trunk / routed
  • vlan: access の場合の VLAN ID。ゲストの IF では VLAN タグ(Proxmox VE の tag)、VLAN インターフェースではその VLAN ID
  • vlans: trunk の場合の許可 VLAN(例: "10,20,100-110")。,・;・空白で区切り、10-20 の範囲と all(すべて)・none(なし)も書ける。ゲストの IF では Proxmox VE の trunks、VLAN aware なブリッジでは通す VLAN(Proxmox VE の bridge-vids。省略時は 2-4094)
  • transceiver: 挿しているトランシーバ(光モジュール)。例: 10GBASE-SR。5.4 の一覧の名前(大文字小文字は区別しない)なら、ポートに合うかと、リンクの両端が合うかを確かめる。一覧に無い名前(製品の型番など)も書けるが、確かめない

論理的な IF(bond・ブリッジ・VLAN インターフェース。4.6)に書ける項目:

  • type: bond / bridge / vlan。省略時は物理的な IF(ポート)。type を書いた IF は、モデルに無い名前でも使える
  • members: bond で束ねる IF、ブリッジにつなぐ IF(Proxmox VE の bond-slaves / bridge-ports)。同じデバイスの IF 名の配列
  • bond-mode: bond のモード(balance-rr / active-backup / balance-xor / broadcast / 802.3ad / balance-tlb / balance-alb)
  • vlan-aware: ブリッジが VLAN aware(VLAN を区別して通す)か。true / false、省略時は false
  • vlan-raw-device: VLAN インターフェースの親の IF
  • VLAN インターフェースの vlan と vlan-raw-device は、名前が 親.VLAN ID(vmbr0.10)なら省略できる。名前が vlan10 の形なら vlan を省略できる

links(配列)

フィールド 必須 説明
a 必須 エンドポイント デバイスID:IF名
b 必須 エンドポイント
media 任意 copper / fiber / dac / wireless / virtual。省略時は両端のトランシーバ(5.4)から、無ければポート種別から推定(ブリッジとのリンクは virtual)
label 任意 リンク中央に表示する文字
  • エンドポイントは 最初の : でデバイスIDとIF名に分割する。
  • a と b の順序に意味はない。
  • ゲストの IF とホストのブリッジを結ぶリンクも書く(a: web01:net0、b: pve1:vmbr0。4.6)。ブリッジには何本でもつなげる。
  • 無線のポート(ポート種別 wireless)にも何本でもつなげる(AP に複数の端末をつなぐため)。

layout(マップ。キー = デバイスID、またはブリッジの ホストのデバイスID:ブリッジ名)

  • { x: number, y: number }。GUI でデバイスを動かすと自動更新される。
  • ブリッジ(4.6)の位置は pve1:vmbr0: { x: 300, y: 520 } のように書く。
  • 任意で _viewport: { x, y, zoom } を保存してよい。
  • 手で動かした IF 名ラベル(7.3)の位置は _labels に書く。GUI でラベルを動かすと自動更新される。
    • キーはラベルの付く端(リンクの端と同じ デバイスID:IF名。ブリッジとホストの間の線のホスト側のラベルは ホスト:ブリッジ名)。
    • 値は { along: number, side: number }。線の端から線に沿った距離と、線からの横のずれ(線に沿って外を向いたときの右がプラス)。
    layout:
      _labels:
        pve2:eno3: { along: 120, side: -8 }
  • 曲げた線(7.3)の点は _bends に書く。GUI で線を曲げると自動更新される。
    • キーは線の両端のノード(デバイスID。ブリッジなら ホスト:ブリッジ名)を、名前の順に並べて空白でつないだもの。同じ 2 台の間の線(LAG など)は、まとめて同じ点を通る。
    • 値は { x: number, y: number } の配列。キーの 1 つ目のノードの側から順に並べる。
    layout:
      _bends:
        core1 core2: [ { x: 380, y: 300 }, { x: 380, y: 420 } ]

groups(配列)

groups:
  - id: rack1
    kind: rack          # site / rack / vlan / subnet / as / cluster / custom
    label: Rack 1
    members: [rt1, core-sw1]
  • kind: cluster のグループは、クラスタのネットワーク networks(マップ。キー = ネットワークの名前、値 = サブネット)を持てる(4.6)。

sdn(マップ。4.6)

  • zones: SDN のゾーン(キー = ゾーンID)
  • vnets: SDN の VNet(キー = VNet ID)

4.4 インターフェース名の正規化

  • Cisco 系の長い名前と短い名前を同一視する。 例: GigabitEthernet0/1 = Gi0/1、TenGigabitEthernet1/0/1 = Te1/0/1
  • 略称テーブル(初期): Ethernet→Eth, FastEthernet→Fa, GigabitEthernet→Gi, TenGigabitEthernet→Te, TwentyFiveGigE→Twe, FortyGigabitEthernet→Fo, HundredGigE→Hu, Port-channel→Po, Vlan→Vl, Loopback→Lo, Serial→Se, Management→Mgmt
  • 大文字小文字は区別しない。
  • YAML 読み込み時はどちらでも受け付け、保存時は短縮形に統一する。
  • ただし type を書いた IF(bond・ブリッジ・VLAN インターフェース。4.6)の名前は Linux の名前なので、書いたとおりに保つ(Linux の VLAN インターフェース vlan10 を Vl10 にしない)。比べるときは同じ規則で同一視する。

4.5 バリデーション

読み込み・編集のたびに検証し、結果を「問題パネル」に一覧表示する。

レベル 内容 扱い
エラー(パース) YAML 構文エラー、constella バージョン不正 図は直前の正常状態のまま。エディタ上に赤線表示
エラー(意味) 存在しないデバイス/IF への参照、同一ポートの二重使用(ブリッジと無線のポートは除く)、デバイスID重複、未知のモデル 図には反映するが、該当リンク/デバイスを赤で強調
警告 ポート種別の不一致(例: RJ45 と SFP)、自分自身への接続、トランシーバがポートに合わない、両端のトランシーバが合わない(5.4)、layout._bends に対応する線が無い 黄色で強調
  • フレキシブルデバイス(model 無し、または flexible: true のモデル)は、未定義の IF 名を参照されたら自動でその IF を持っているものとして扱う。
  • 銅線のトランシーバ(1000BASE-T・10GBASE-T)を挿した SFP のポートは、RJ45 のポートとみなしてポート種別を比べる。
  • ハイパーバイザまわり(ゲスト・ブリッジ・bond・VLAN インターフェース・クラスタ・SDN)の検証は 4.6 を参照。

4.6 ハイパーバイザ(仮想化基盤)

Proxmox VE のような仮想化基盤を描くための書き方。キーや IF の名前は Proxmox VE に合わせるが、ほかのハイパーバイザにも使える汎用の形にする。

devices:
  core-sw1:
    model: generic-l2switch-24
    interfaces:
      Gi0/1: { mode: trunk, vlans: "10,20,40" }
      Gi0/2: { mode: trunk, vlans: "10,20,40" }
  pve1:
    model: generic-hypervisor-4      # ロールは hypervisor。物理的な NIC は eno1〜eno4
    interfaces:
      bond0:    { type: bond, members: [eno1, eno2], bond-mode: 802.3ad }
      vmbr0:    { type: bridge, members: [bond0], vlan-aware: true }
      vmbr0.10: { type: vlan, ip: [192.168.10.2/24] }   # ホストの管理用の IP
  web01:
    model: generic-vm                # guest: vm
    host: pve1                       # pve1 の上で動く
    vmid: 100
    interfaces:
      net0: { vlan: 10, ip: [192.168.10.11/24] }       # vlan = Proxmox VE の tag
  fw01:
    model: generic-vm
    role: firewall                   # ロール(機能)は今までどおり。OPNsense の VM なら firewall
    host: pve1
    vmid: 101
    interfaces:
      net0: { mode: trunk, vlans: "10,20" }             # Proxmox VE の trunks
  db01:
    model: generic-container         # guest: container(Proxmox VE の LXC)
    host: pve1
    vmid: 200
    interfaces:
      net0: { vlan: 20 }

links:
  - a: pve1:eno1                     # 物理的な配線は今までどおり
    b: core-sw1:Gi0/1
  - a: pve1:eno2
    b: core-sw1:Gi0/2
  - a: web01:net0                    # ゲストはブリッジにつなぐ
    b: pve1:vmbr0
  - a: fw01:net0
    b: pve1:vmbr0
  - a: db01:net0
    b: pve1:vmbr0

layout:
  pve1:vmbr0: { x: 300, y: 520 }     # ブリッジの位置は「ホスト:ブリッジ」で覚える

groups:
  - id: lab
    kind: cluster
    members: [pve1, pve2, pve3]      # pve2・pve3 の定義は省略
    networks:                        # クラスタのネットワーク(名前: サブネット)
      corosync: 10.10.0.0/24
      ceph-public: 10.20.0.0/24
      migration: 10.30.0.0/24

sdn:
  zones:
    vlanzone: { type: vlan, bridge: vmbr0 }    # nodes を省略 = ロールが hypervisor の機器すべて
  vnets:
    vnet40: { zone: vlanzone, tag: 40, subnets: [10.40.0.0/24] }   # 各ホストに vnet40 ができる

ホストとゲスト

  • ホストは、ふつうはロールが hypervisor の機器(組み込みモデル generic-hypervisor-4 など)。ほかのロールの機器(コンテナを動かすサーバーなど)もホストにできる。
  • ゲストは、host に動いているホストのデバイスIDを書いた機器。
    • VM かコンテナかは guest(vm / container)で表す。省略時はモデルの guest、それも無ければ vm。
    • guest があって host が無い機器は、図に描かないホストの上のゲストとして扱う(クラウドの VM など)。
    • ロールは、ゲストでも機能を表す(OPNsense の VM なら firewall)。
  • ゲストの上でさらにゲストを動かす(入れ子の仮想化)こともできる。

ホストの中のネットワーク

  • bond・ブリッジ・VLAN インターフェースは、ホストの interfaces に type を付けて書く(Linux や Proxmox VE の /etc/network/interfaces と同じ考え方)。
  • bond の members には物理的な IF を、ブリッジの members には物理的な IF・bond・VLAN インターフェースを書く。members の無いブリッジは、ホストの中だけのネットワークになる。
  • ホストの IP は、ブリッジや VLAN インターフェースの ip に書く。
  • リンクをつなぐのは、物理的な IF とブリッジだけ。bond・VLAN インターフェースにはつながない(束ねた線は、メンバーの物理的な IF ごとに書く)。

ゲストとブリッジのつなぎ方

  • links に、ゲストの IF とホストのブリッジを結ぶリンクを書く。ブリッジにつなげるのは、そのホストの上で動くゲストの IF だけ。
  • VLAN タグはゲストの IF の vlan に、トランク(複数の VLAN を通す)は mode: trunk と vlans に書く。

クラスタ

  • groups に kind: cluster のグループを書き、members にホストを並べる。1 台のホストが入れるクラスタは 1 つだけ。
  • networks に、クラスタのネットワークの名前とサブネット(10.10.0.0/24 の形。IPv4・IPv6)を書く。名前は自由(corosync・ceph-public・ceph-cluster・migration など)。
  • クラスタのホストの IF のうち、IP がそのサブネットに入るものは、ラベルの IP の行にネットワークの名前を添える(10.10.0.11/24 (corosync)。2 つ以上に入れば (ceph-public, ceph-cluster))。ゲストの IF には添えない。
  • GUI では、ほかのグループと同じく作り、グループのインスペクタで種類をクラスタにして、ネットワークを編集する(7.5)。

SDN

  • 最上位の sdn に、ゾーン(zones)と VNet(vnets)を書く。
  • ゾーンに書ける項目:
    • type(必須): simple / vlan / qinq / vxlan / evpn
    • bridge: vlan・qinq のときに使う、各ホストのブリッジ(vlan・qinq では必須)
    • tag: qinq の外側の VLAN ID(qinq では必須)
    • nodes: ゾーンを使うホストのデバイスID。省略時は、ロールが hypervisor の機器すべて
  • VNet に書ける項目:
    • zone(必須): ゾーンID
    • tag: VLAN ID(vlan・qinq では 1〜4094。qinq では内側の VLAN)。vxlan・evpn では VNI(1〜16777215)。simple 以外では必須
    • subnets: サブネットの配列(10.40.0.0/24 の形)
  • VNet は、ゾーンの各ホストの中のブリッジとして扱う。ゲストは pve1:vnet40 につなぎ、位置も pve1:vnet40 で覚える。
  • VNet の箱は、ゲストがつながっているホストと、位置を覚えているホストにだけ描く(7.3)。
  • ゾーンと VNet は YAML で書く(GUI で作る・編集する機能はまだ無い)。

Proxmox VE との対応

Proxmox VE Constella
ノード ロールが hypervisor の機器
VM(qemu)/ CT(lxc) guest: vm / guest: container の機器。host にノード、vmid に VMID
ゲストの NIC(net0)の bridge=vmbr0 リンク web01:net0 ↔ pve1:vmbr0
ゲストの NIC の tag=10 IF net0 の vlan: 10
ゲストの NIC の trunks=20;30 IF net0 の mode: trunk、vlans: "20,30"
bond-slaves / bond-mode type: bond の IF の members / bond-mode
bridge-ports / bridge-vlan-aware / bridge-vids type: bridge の IF の members / vlan-aware / vlans
vmbr0.10、vlan-raw-device type: vlan の IF、vlan-raw-device
クラスタ、corosync・Ceph・移行(migration)のネットワーク kind: cluster のグループ、networks
SDN のゾーン・VNet sdn の zones・vnets

検証(4.5 に加える)

レベル 内容
エラー host のデバイスが無い。host をたどると輪になる(自分自身を含む)
エラー VMID の重複(同じホストの中。ホストがクラスタに入っていれば、クラスタの中)
エラー 1 台のホストが 2 つ以上のクラスタに入っている(2 つ目以降のクラスタでは数えない)
エラー members の IF が無い。1 つの IF が 2 つの bond・ブリッジに入っている。bond の members に物理的でない IF がある。ブリッジの members にブリッジがある
エラー VLAN インターフェースの親が無い・分からない。VLAN ID が分からない・1〜4094 の外
エラー ブリッジに、そのホストの上で動くゲスト以外の IF がつながっている。bond・VLAN インターフェースにリンクがつながっている
エラー SDN: 参照先(ホスト・ゾーン)が無い。vlan・qinq のゾーンに bridge が無い、ゾーンのホストにその bridge が無い。qinq のゾーンに tag が無い。ゾーンに入っていないホストで VNet を使っている。VNet の tag が無い(simple 以外)・範囲の外(vlan・qinq では 1〜4094)。VNet ID がホストの IF 名と重なる
警告 VLAN の通り道(下記)で、通らない VLAN がある
警告 vlans の書き方が分からない(その場所は VLAN の通り道で確かめない)
警告 クラスタのホストに、networks のサブネットに入る IP が無い(ホストごとに 1 件にまとめ、足りないネットワークを並べる)
警告 networks のサブネットの書き方が分からない(そのネットワークは使わない)。kind: cluster 以外のグループに networks がある(使わない)
警告 VNet ID が 8 文字より長い(Proxmox VE の制限)
警告 VNet の subnets の書き方が分からない

VLAN の通り道(最初のスイッチまでを確かめる):

  • 対象は、ブリッジにつながったゲストの IF のタグ(vlan)とトランクの vlans、ホストの VLAN インターフェースの VLAN、SDN の VLAN(vlan ゾーンは VNet の tag、qinq ゾーンはゾーンの tag(外側の VLAN))。トランクで vlans が無い(すべて通す)ゲストの IF と、VNet につながったゲストの IF は確かめない。
  • SDN の VLAN は、ゾーンのすべてのホストで、ゾーンの bridge から確かめる(使っている IF は pve1:vnet40 のように書く)。
  • ブリッジが VLAN aware なら、ブリッジの vlans(省略時は 2-4094)に入っているか。VLAN aware でなければ、確かめずに先へ進む(タグを付けたまま物理的な IF から出るため)。
  • ブリッジの members から bond をたどって物理的な IF へ進む。VLAN インターフェースは親から始める。
    • members の VLAN インターフェース(bond0.20 など)の先と、親が VLAN インターフェースの VLAN インターフェースは、タグが内側に包まれるので確かめない(外側の VLAN は、その VLAN インターフェース自身で確かめる)。
  • 物理的な IF にリンクでつながる相手のポートで通るか。機器のロールは問わない。リンクが無ければ確かめない。
    • trunk(mode: trunk、または mode が無くて vlans がある)なら、vlans(省略時はすべて通す)に入っているか。
    • access なら、タグの付いた VLAN は通らない。
    • routed のときと、mode・vlans のどちらも無いときは確かめない。
  • 警告は止まる場所(ブリッジ・相手のポート)ごとに 1 件出し、通らない VLAN と、それを使っている IF を書く。止まった VLAN は、その先では確かめない。

4.7 Proxmox VE からの取り込み

Proxmox VE のクラスタ(ノード 1 台だけでもよい)の構成を読み込み、新しい図を作る。

  1. どれか 1 台のノードで、root として書き出しスクリプト scripts/proxmox-export.sh を実行する。スクリプトは pvesh(Proxmox VE の API をコマンドで使う道具)で読むだけで、設定は変えない。パスワードやトークンも使わない。
    sh proxmox-export.sh > constella-proxmox.json
  2. できた JSON ファイルを、ツールバーの「取り込む」(ファイルメニューの「Proxmox VE から取り込む…」)で選ぶ。未保存の変更があれば、開くときと同じく確かめる。
  3. 新しい図(未保存)として開く。座標は書かず、開くと自動で並べる。読めなかったものがあれば、取り込んだあとに一覧で知らせる。

書き出す JSON(format: constella-proxmox、version: 1):

  • exported(書き出した日時)、host(書き出したノード)
  • nodes: ノードごとに node(名前)、network(pvesh get /nodes/<node>/network の結果)、qemu・lxc(ゲストごとに vmid と、pvesh get /nodes/<node>/qemu/<vmid>/config などの設定)
  • cluster: status(/cluster/status)、nodes(/cluster/config/nodes。corosync のアドレス)、options(/cluster/options。移行のネットワーク)、ceph(/etc/pve/ceph.conf の public_network・cluster_network。Ceph を使っていなければ null)
  • sdn: zones(/cluster/sdn/zones)、vnets(/cluster/sdn/vnets)、subnets(VNet ごとの /cluster/sdn/vnets/<vnet>/subnets)。適用済みの設定(running)を読み、読めなければ今の設定を読む。
  • 停止中のノードなど、読めなかったところは null にして、続きを書き出す。ノード 1 台だけでクラスタが無ければ、cluster の中は読めないところが null になる。
  • cluster・sdn はフェーズ5-2 で足した(どちらも無い JSON も、同じ version: 1 として読む。クラスタと SDN は無い扱い)。

取り込み方:

Proxmox VE Constella
ノード モデル無し(どんな IF 名も使える)で role: hypervisor の機器。ID はノード名
ネットワークの eth・OVSPort 物理的な IF。使っていないものも書く(GUI でスイッチにつなぐとき、ポートの一覧に出るように)
bond・OVSBond type: bond。slaves・ovs_bonds → members、bond_mode → bond-mode(Linux の bond のモードだけ)
bridge・OVSBridge type: bridge。bridge_ports・ovs_ports → members、bridge_vlan_aware → vlan-aware(OVS のブリッジはいつも VLAN aware)、bridge_vids → vlans(既定の 2-4094 なら書かない)
vlan、タグのある OVSIntPort type: vlan。vlan-raw-device・ovs_bridge と vlan-id・ovs_tag(名前が「親.VLAN ID」の形なら書かない)
cidr・cidr6、comments ip、description
VM・CT ゲスト(モデル generic-vm / generic-container)。host にノード、vmid に VMID
ゲストの net0 など IF net0 など。tag → vlan、trunks → mode: trunk と vlans(; は , に)、CT の ip・ip6 と VM の cloud-init の ipconfig0 などの ip・ip6 → ip(dhcp などは書かない)
ゲストの bridge= ゲストの IF とノードのブリッジ(SDN の VNet も)を結ぶリンク
クラスタ kind: cluster のグループ。ID はクラスタ名、members はノード
corosync のリンク 0(ring0_addr)・リンク 1 … networks の corosync・corosync-link1 …。サブネットは、そのアドレスを持つノードの IF の IP から求める
移行のネットワーク(migration の network) networks の migration
Ceph の public_network・cluster_network networks の ceph-public・ceph-cluster(2 つ以上書いてあれば ceph-public-2 …)
SDN のゾーン sdn.zones。type・bridge・tag・nodes(, 区切りを配列に)
SDN の VNet sdn.vnets。zone・tag、サブネットの cidr → subnets
  • ゲストのデバイスID は、VM の名前(CT はホスト名)。使えない文字は - に置き換え、ほかの機器と重なれば -VMID を付ける。名前が無ければ vm100 / ct200。名前と違う ID にしたときは label に名前を書く。
  • 機器は、ノード(書き出した順)のあとに、ノードごとのゲスト(VMID の順)を並べる。ノードの IF は、物理的な IF・bond・ブリッジ・VLAN インターフェースの順に並べる。
  • ネットワークを読めなかったノードには、ゲストの bridge= からブリッジだけを作る。ネットワークを読めたノードのブリッジにも、SDN の VNet にも無いところへのリンクは作らない。
  • クラスタのネットワークのサブネットが分からないとき(corosync のアドレスを持つ IF が無い、アドレスがホスト名など)は、そのネットワークを書かない。
  • ゾーン・VNet のうち、取り込めない種類のゾーン(SDN の 5 種類にないもの)と、そこの VNet は取り込まない。
  • 取り込めなかったもの(上の 3 つ、読めなかったノード・ゲスト)は、取り込んだあとに一覧で知らせる。
  • テンプレート(template: 1)の VM・CT は取り込まない。
  • スイッチと物理的な配線は取り込めない(取り込んだあとに図に足してつなぐ)。
  • 図のタイトルは「Proxmox VE」、説明は書き出した日時とノード。

5. モデル(機種)ライブラリ

5.1 モデル定義ファイル形式

id: cisco-c2960-24tt-l
vendor: Cisco
name: Catalyst 2960-24TT-L
role: l2switch
ports:
  - name: "FastEthernet0/{1-24}"
    type: rj45
    speed: 100M
  - name: "GigabitEthernet0/{1-2}"
    type: rj45
    speed: 1G
  • name の {開始-終了} は連番展開する(1つの name に複数書くことも許可。例: Gi1/{0-1}/{1-4})。
  • type: rj45 / sfp / sfp+ / sfp28 / qsfp+ / qsfp28 / console / serial / wireless / virtual
  • speed: 表示用の文字列(100M / 1G / 10G / 25G / 40G / 100G など)
  • flexible: true を指定すると、定義外の IF 名も使用可能になる。
  • guest: vm / guest: container を指定すると、そのモデルの機器はゲスト(4.6)になる。
  • icon でアイコンを変えられる。組み込みのアイコンの名前(role と同じ名前。例: firewall)か、アイコンのフォルダ(5.2)の画像のファイル名(.svg・.png。例: fortigate.svg)を書く。省略すると role のアイコン。画像が見つからなければ role のアイコンで表示し、パレットで知らせる。

5.2 モデルの置き場所

  1. 組み込みモデル: アプリ同梱(src/library/models/*.yaml)
  2. ユーザーモデル: OS ごとのアプリデータディレクトリ配下の models/(macOS では ~/Library/Application Support/Constella/models/)。Tauri のパス API で取得すること。
  3. 同じ id があればユーザーモデルを優先する。
  4. アイコンの画像(5.1 の icon): ユーザーモデルのフォルダと並ぶ icons/(macOS では ~/Library/Application Support/Constella/icons/)。モデルの管理画面(5.5)で取り込むと、ここにコピーする(1 MB まで)。

5.3 MVP で同梱するモデル

汎用モデル(ポート名は Cisco 風に統一):

  • 汎用モデル(generic-*。仮想化のモデルも)はすべて flexible: true にする(一覧に無い IF 名も使える)。実機モデルは定義したポートだけ。
  • generic-router-4(Gi0/0〜Gi0/3、rj45)
  • generic-l2switch-24(Gi0/1〜Gi0/24 rj45 + Te0/25〜Te0/28 sfp+)
  • generic-l3switch-48(Gi1/0/1〜Gi1/0/48 rj45 + Te1/1/1〜Te1/1/4 sfp+)
  • generic-firewall-8(port1〜port8、rj45)
  • generic-server-2(eth0〜eth1)
  • generic-pc(eth0 rj45、wlan0 wireless)
  • generic-ap(eth0, wlan0)
  • generic-cloud(flexible)

仮想化のモデル(フェーズ4で追加。4.6):

  • generic-hypervisor-4(eno1〜eno4、rj45。role: hypervisor)
  • generic-vm(net0〜net3、virtual、flexible。role: server、guest: vm)
  • generic-container(net0〜net3、virtual、flexible。role: server、guest: container)

実機モデル(例として1つ):

  • cisco-c2960-24tt-l(上記 5.1 の定義)

実機モデルは後からユーザーが追加していく前提。実機モデルを追加する場合はポート構成を公式資料で確認すること。

5.4 トランシーバ(光モジュール)

IF の transceiver(4.3)で確かめる名前の一覧。アプリに組み込む。

速度 形 名前
1G SFP 1000BASE-SX・1000BASE-LX・1000BASE-EX・1000BASE-ZX・1000BASE-BX-U・1000BASE-BX-D・1000BASE-T
10G SFP+ 10GBASE-SR・10GBASE-LRM・10GBASE-LR・10GBASE-ER・10GBASE-ZR・10GBASE-BX-U・10GBASE-BX-D・10GBASE-T・10G-DAC・10G-AOC
25G SFP28 25GBASE-SR・25GBASE-LR・25G-DAC・25G-AOC
40G QSFP+ 40GBASE-SR4・40GBASE-LR4・40G-DAC・40G-AOC
100G QSFP28 100GBASE-SR4・100GBASE-LR4・100GBASE-CWDM4・100GBASE-DR・100G-DAC・100G-AOC
  • DAC は両端にコネクタの付いた銅のケーブル、AOC は両端にコネクタの付いた光のケーブル。BiDi(-BX-U・-BX-D)は 1 本の光ファイバーで送受信するもの。
  • 別名も受け付ける: 1000BASE-LX10・1000BASE-LH(= 1000BASE-LX)、1000BASE-BX10-U・1000BASE-BX10-D・10GBASE-BX10-U・10GBASE-BX10-D(= それぞれの -BX-U・-BX-D)、10GBASE-CR・25GBASE-CR・40GBASE-CR4・100GBASE-CR4(= それぞれの DAC)。
  • 挿せるポート:
    • SFP のポート: SFP。SFP+ のポート: SFP・SFP+。SFP28 のポート: SFP・SFP+・SFP28
    • QSFP+ のポート: QSFP+。QSFP28 のポート: QSFP+・QSFP28
    • RJ45・コンソール・シリアル・無線・仮想のポートと、論理的な IF(4.6)には挿せない(一覧に無い名前でも警告する)
  • リンクの両端が合うのは、同じ名前(別名は同じとみなす)のとき。BiDi は、同じ速度の U と D の組み合わせのとき。片方の端にしか書いていなければ、確かめない。
  • media の推定(4.3): 光のもの(AOC を含む)は fiber、1000BASE-T・10GBASE-T は copper、DAC は dac。両端で違うときは、端 a を優先する。

5.5 モデルの管理画面

メニューの「ファイル > モデルの管理…」と、パレットの「管理」ボタンで開く、アプリの中の大きなダイアログ。

  • 左にモデルの一覧(検索欄、role ごとのまとまり、組み込み・ユーザーの印)。読めないユーザーモデルのファイルも、一覧の下に出す。
  • 右に、選んだモデルの中身: 名前・ID・メーカー・役割・ゲスト・フレキシブル、アイコン、ポート(種類ごとの数と名前の範囲)、開いている図で使っている台数。
  • 操作:
    • 「+ モデルを追加」、「複製」(組み込みもユーザーも)、「編集」(ユーザーモデル。追加・編集のダイアログを開く)、「削除」(ユーザーモデル。図で使っている台数を確認に出す)。
    • 「アイコンを変える」(ユーザーモデル): 組み込みのアイコンから選ぶ、画像(SVG・PNG。1 MB まで)を取り込む、role のアイコンに戻す。取り込んだ画像は、どのモデルからも選べる。どのモデルも使っていない画像は消せる。
    • 組み込みモデルは変えられない(複製してから変える)。
  • 追加・編集のダイアログにも、同じ選び方のアイコンの欄を置く。
  • まとめて書き出す・読み込む(別の Mac や人に渡すため):
    • 「書き出す…」: 書き出すユーザーモデルをチェックで選び(最初はすべて)、1 つの YAML のファイル(名前の初期値は constella-models.yaml)に書き出す。選んだモデルが使うアイコンの画像も入れる。
    • 「読み込む…」: 書き出したファイルを選び、中身の一覧(新しいモデル・中身が違うモデル・今と同じモデル・組み込みのモデルを置き換えるモデル・読めないもの・名前を変えて取り込む画像)を確かめてから取り込む。
      • 同じ ID のユーザーモデルがあって中身が違えば、「上書きして取り込む」か「重なるものは飛ばす」かを選ぶ。中身が同じなら何もしない。
      • 画像は、同じ名前で中身も同じなら今の画像を使う。中身が違えば、同じ中身の画像が別の名前であればそれを使い(取り込み直しても増やさない)、無ければ -2 などを付けて取り込む。名前が変わったら、モデルの icon もその名前に書き換える(モデルの YAML のほかの書き方とコメントはそのまま)。取り込むモデルが使う画像だけを書き込む。
      • 読めないモデルと、1 MB を超える画像は取り込まない。
    • ファイルの形(先頭の constella-models でトポロジの YAML と見分ける。モデルの YAML は書いたとおり、画像は base64):
constella-models: 1
models:
  - file: my-switch.yaml
    text: |
      id: my-switch
      role: l2switch
      icon: my-switch.svg
      ...
icons:
  - file: my-switch.svg
    data: PHN2ZyB4bWxucz0i...

6. 画面構成

┌──────────────────────────────────────────────────────────────┐
│ ツールバー: 新規 開く 取り込む 保存 | 選択 接続 | 自動整列 | YAML表示切替 │
├──────────┬───────────────────────────────┬──────────────────┤
│ パレット │                               │ インスペクタ      │
│          │        キャンバス               │ (選択中の        │
│ 検索欄    │       (React Flow)            │  デバイス/リンク) │
│ ロール別  │                               │                  │
│ モデル一覧 │                               ├──────────────────┤
│          │                               │ YAMLエディタ      │
│          │                               │ (表示切替可)      │
├──────────┴───────────────────────────────┴──────────────────┤
│ ステータスバー: ファイル名 / 未保存マーク / エラー n 件 警告 n 件  │
└──────────────────────────────────────────────────────────────┘
  • YAML エディタは右側の下段に置き、ツールバーで表示/非表示を切り替える。将来的に「キャンバスとYAMLを左右半々」レイアウトにも切り替えられる構造にしておく。
  • 問題パネルはステータスバーの件数をクリックすると開く。項目クリックで該当デバイス/リンク、および YAML の該当行へジャンプ。
  • ツールバーの「グリッド」で、機器を背景の点(方眼)に合わせて置くかを切り替える(7.4)。アプリに覚えておき、最初は切ってある。
  • macOS の標準メニューバー(ファイル/編集/表示/ウィンドウ/ヘルプ)と標準ショートカット(⌘N, ⌘O, ⌘S, ⇧⌘S, ⌘Z, ⇧⌘Z, ⌘C, ⌘V, ⌘A, Delete)に対応する。

7. 操作仕様

7.1 デバイスの配置

  • パレットからキャンバスへドラッグ&ドロップで配置。
  • パレットのモデルは role ごとにまとめ、ゲストのモデル(guest のあるモデル)は「ゲスト(VM・コンテナ)」にまとめる。インスペクタのモデル一覧も同じ。
  • VM・コンテナのモデルを置くとき(4.6):
    • ハイパーバイザ(またはゲストのいるホスト)の上に落とすと、そのホストのゲストになり、ホストの下に並ぶ(同じホストのゲストがいれば、その右)。
    • ホストの枠の中に落とすと、その枠のホストのゲストになる(落とした位置に置く)。
    • それ以外の所に落とすと、host なし。
    • ほかのホストの枠や機器と重なるときは、同じ段の左、下の段の順に、空いている場所に置く。
    • 置いたあとにドラッグしても、ホストは変わらない(インスペクタで変える)。
  • bond・VLAN インターフェース(4.6)は、ホストのインスペクタの「bond・VLAN インターフェース」の欄で足して編集する(キャンバスには箱を置かない):
    • bond は members(物理的な IF)とモード、VLAN インターフェースは親と VLAN ID、どちらも IP・説明を編集できる。
    • VLAN インターフェースの名前が「親.VLAN ID」の形なら、親や VLAN ID を変えると名前も変える。それ以外の名前(vlan10 など)なら、vlan-raw-device と vlan に書く。
    • bond・ブリッジの members の候補では、ほかの bond・ブリッジに入っている IF は選べない(外すことはできる)。
  • ブリッジ(4.6)の作り方と編集:
    • ホストのインスペクタの「ブリッジを追加」で足す。名前は vmbr0・vmbr1 … と自動で付け、ホストの下の段に置く(重なるときは、ゲストと同じ順に空いている場所を探す)。
    • ブリッジの箱を選ぶと、ブリッジのインスペクタを出す(名前・members・VLAN aware・通す VLAN・IP・説明、つながっているゲストの一覧、削除)。名前を変えると、リンク・位置のキー・ほかの IF の members も書き換える。
    • ブリッジの箱は、デバイスと同じく選ぶ・動かす(位置は layout の ホスト:ブリッジ名)・Delete キーで消す(つながっていたリンクも消える)ことができる。
  • VNet(4.6)の箱を選ぶと、VNet のインスペクタを出す(ゾーン・tag・subnets・そのホストでつながっているゲストの表示と、「このホストから外す」)。「このホストから外す」と Delete キーは、そのホストの VNet の箱(つながっていたリンクと位置)を消す。VNet の定義は残る。
  • デバイスIDは role ごとのプレフィックス + 連番で自動採番(rt1, sw1, l3sw1, fw1, srv1, hv1, pc1, ap1, cloud1)。ゲストは role ではなく種類で採番する(vm1, ct1)。
  • インスペクタでデバイスID・ラベル・モデルを変更できる。ゲストなら、種類・ホスト・VMID も変更できる。
    • デバイスIDを変更したら、関連するリンク・layout・groups の参照もすべて書き換える(ゲストの host、ブリッジの位置のキー、SDN の nodes も含む)。
    • モデルを変更して既存リンクのポートが存在しなくなる場合は確認ダイアログを出す。
    • ゲストのホストを変えると、今のホストのブリッジへのリンクは、新しいホストに同じ名前のブリッジがあれば付け替える。無ければ、確認ダイアログで「リンクを消す」か「そのまま残す(エラーになる)」かを選ぶ。
  • デバイスのインスペクタの IF 一覧では、IF ごとに接続先を出す(押すとそのリンクを選ぶ)。2 本以上つながっている IF(無線のポートなど)は本数を出し、押すとその線をすべて選ぶ。
  • IF のトランシーバ(4.3)は、デバイスのインスペクタで IF の行を開いて入れる。そのポートに合うもの(5.4)を候補に出し、自由にも書ける。
    • リンクのインスペクタでは、両端にまとめて入れる(挿せない端、つまり RJ45 などのポートとブリッジには入れない)。BiDi の U を入れると、反対の端に D を入れる。両端で違うものにするときは、IF の行で 1 つずつ入れる。

7.2 接続(Packet Tracer 風)

  1. ツールバーで「接続」モードにする(ショートカット: C)。
  2. デバイスAをクリック → ポート選択ポップアップが開く。
  3. ポートを選ぶ → デバイスBをクリック → ポート選択ポップアップ → リンク作成。
  4. Esc でキャンセル。
  • ブリッジ(4.6)の箱をクリックしたときは、ポート選択ポップアップを出さず、そのブリッジを端にする。

ポート選択ポップアップ:

  • デバイスの右に開き、キャンバスに収まらなければ左に開く。下に収まらなければ、デバイスの下端から上へ開く(上下もキャンバスの中に収める)。ほかのパネルより手前に出す。
  • ポート種別ごとにグループ表示(RJ45 / SFP+ …)。
  • 使用中ポートはグレーアウトし、接続先(core-sw1:Gi0/1 など)を併記。選択不可。
  • ただし無線のポート(4.3)は、使用中でも選べる(つながっている本数を添える)。
  • 一覧上部に検索欄。
  • 「次の空きポート」ボタン(先頭の未使用ポートを選択。無線のポートも、1 本もつながっていないときだけ選ぶ)。
  • フレキシブルデバイスの場合は IF 名を自由入力できる。
  • 相手側ポートと種別が合わない場合は警告アイコンを表示(接続自体は許可)。
  • ブリッジは「ブリッジ(何本でもつなげる)」のグループに出し、使用中にしない(つながっている本数を添える)。ゾーンのホストでは、VNet(4.6)も同じグループに出す(VNet と添える)。
  • bond・VLAN インターフェースは出さない(線をつながないため)。

7.3 リンクの表示

  • デバイスの中心同士を直線で結ぶ「フローティングエッジ」方式で描画する(ポート数が多いため、デバイス上に接続点ハンドルは表示しない)。曲げる点(layout._bends)があれば、その点を順に通る折れ線にする。
  • リンク両端のデバイス寄りの位置にIF名(短縮形)を表示する。ラベルは線に重ならないよう小さな背景付きで描く。
  • IF にトランシーバ・IP・VLAN が書かれていれば、IF 名ラベルの下に 1 行ずつ表示する(トランシーバは書いたとおり、IP は 1 つずつ、access は VLAN 10、trunk は trunk 10,20,30)。行の順は、IF 名・bond の名前(ハイパーバイザの表示)・トランシーバ・IP・VLAN。
  • 同じデバイス間に複数リンクがある場合(LAG など)は、線を少しずつずらして重ならないようにする。
    • 両端の IF 名ラベルが隣の線のラベルと重なるときは、線に沿って段違いにする。線が短くて段違いでは収まらないときは、線の両側(横向きの線なら上下)に分けて置く。そのときも、3 本以上で同じ側に並ぶラベルと、真ん中の線のラベルは段違いにする。
  • 同じデバイスに付く別々の線の IF 名ラベルが重なるとき(線の向きが近いときなど)は、デバイスに近いラベルはそのままにし、遠いほうのラベルをその線に沿って外へずらす。ブリッジとホストの間の線のホスト側のラベルも同じに扱う。線が短くて重ならない所までずらせないときは、反対の端のラベルの手前までずらす。
  • IF 名ラベル(ブリッジとホストの間の線のホスト側のラベルも)は、選択モードでドラッグして動かせる(ドロップしたときに layout._labels へ 1 回書く。Undo 可能)。
    • 動かした位置は、線の端からの「線に沿った距離」と「線からの横のずれ」で覚える。機器を動かしても、ラベルは線に付いていく。
    • 手で動かしたラベルは自動ではずらさず、ほかのラベルがそれを避ける。
    • ラベルをダブルクリックすると、自動の位置に戻す。ツールバーの「ラベルを戻す」で、すべてのラベルを戻す。
    • ラベルをクリックすると、そのリンクを選ぶ。接続モードでは触れない。リンク中央のラベルと、自分自身へのリンクの輪のラベルは動かせない。
    • リンクやデバイスを消すと、そのラベルの位置も消す。デバイスID を変えると、キーも書き換える。
  • 線は、選択モードで曲げられる(ドロップしたときに layout._bends へ 1 回書く。Undo 可能)。
    • リンクを選ぶと、曲げる点(四角)と、区間の真ん中(小さな丸)につまみを出す。真ん中の丸をドラッグすると、そこに曲げる点を足す。曲げる点はドラッグで動かし、ダブルクリックで消す。
    • ドラッグ中、隣の点(端なら機器の中心)と水平・垂直に近ければ(画面で 8px 以内)そろえ、ガイドの線(7.4)を出す。⌥(Option)を押している間はそろえない。
    • 同じ 2 台の間の線(LAG など)は、まとめて同じ点を通り、平行にずらして描く。
    • 両端の IF 名ラベルは、端の区間(機器から最初の点まで)に沿って置く。リンク中央のラベルは、折れ線の長さの真ん中に置く。
    • リンクのインスペクタの「まっすぐに戻す」で、そのリンク(同じ 2 台の間の線すべて)の点を消す。
    • 両端の機器をまとめて動かしたときは、点も一緒に動かす(ドラッグ中も)。片方だけ動かしたときは、点はそのまま。
    • 同じ 2 台の間の線がすべて無くなったら、その点も消す。デバイスID やブリッジの名前を変えると、キーも書き換える。
    • 自分自身へのリンクの輪と、ブリッジとホストの間の点線は曲げられない。
  • media による線のスタイル: copper=実線(グレー系)、fiber=実線(オレンジ系)、dac=実線(青系)、wireless=破線、virtual=点線。
  • 機器のアイコンは role ごとのアイコン。モデルに icon(5.1)があれば、そのアイコンにする。組み込みのアイコンは role の色で描き、画像のアイコンは色を付けずにそのまま表示する。SVG・PNG の書き出しには、画像を埋め込む。
  • ハイパーバイザ(4.6)の表示:
    • ホストは枠で囲み、中にブリッジとゲストを置く。グループの枠は、メンバーのホストの枠を丸ごと囲む。
    • ホストの枠のラベルは、直接のゲストの数(VM 2・CT 1。ゲストがいなくてブリッジだけなら「ゲストなし」)。クリックすると、ホストとゲストをまとめて選ぶ。
    • ホストの枠は「グループ」の表示とは別に、ゲストを表示しているときに出す。
    • ゲストのアイコンには VM / CT の印を付ける。
    • ブリッジ(SDN の VNet を含む)は小さな箱で描き、VLAN aware なら VLAN の印を付け、IP を箱の下に出す。ブリッジの上の VLAN インターフェースの IP も、箱の下に「vmbr0.10 192.168.10.2/24」の形で出す。
    • VNet の箱には VNet の印を付け、箱の下に tag(vlan は VLAN 40、qinq は VLAN 40(外側 100)、vxlan は VXLAN 1000、evpn は EVPN 1000。「VLAN」の表示に従う)と subnets(「IP」の表示に従う)を出す。点線は、vlan・qinq ならゾーンのブリッジの箱へ、vxlan・evpn ならホストへ引く(simple は引かない)。
    • bond の IP と、bond・物理的な IF の上の VLAN インターフェースの IP は、ホストの名前の下に「bond1 10.20.0.11/24」の形で出す。
    • bond に束ねた物理的な IF の線は、ホスト側のラベルの IF 名の下に、bond の名前を添える。
    • ブリッジとホストの間には、点線(仮想のリンクと同じ)を引く。線のホスト側には、ブリッジのメンバー(bond0 など。bond なら、その下に「eno1 + eno2(802.3ad)」のように束ねた NIC とモードも)を出す。members の無いブリッジには線を引かない(ホストの中だけのネットワーク)。
    • ゲストとブリッジのリンクは、ブリッジの箱につなぐ。ブリッジ側の端には IF 名を出さない(箱に名前があるため)。
    • クラスタのネットワークに入る IF は、IP の行にネットワークの名前を添える(10.10.0.11/24 (corosync)。4.6)。リンクの端・ホストの名前の下・ブリッジの箱の下の、どの IP の行も同じ。
  • 表示オプション: IF名ラベル・トランシーバ・IP・VLAN・ゲスト(VM・コンテナ・ブリッジ)それぞれの表示/非表示、長い名前/短縮形の切り替え。
  • 表示レイヤー(すべて / L1 / L2 / L3)で、ラベルの行とグループの枠をまとめて切り替える。
    • L1: IF 名とトランシーバ、拠点・ラックの枠。ゲストとブリッジは隠す
    • L2: IF 名と VLAN、VLAN の枠
    • L3: IF 名と IP、サブネット・AS の枠
    • 種類なし・custom・cluster の枠は、どのレイヤーでも表示する。
    • レイヤーを選んだあとも、個々の表示オプションで調整できる。
    • ショートカットは ⌘0(すべて)、⌘1〜⌘3(L1〜L3)。

7.4 編集操作

  • 選択・複数選択(ドラッグ範囲選択、⇧クリック)・移動・削除。
  • ガイドとスナップ: 機器(ブリッジの箱も)をドラッグすると、アイコンの中心がほかの機器・ブリッジの箱の中心と縦・横でそろう位置に近づいたとき(画面で 8px 以内)吸い付き、そろった機器どうしの間にガイドの線(点線。両端を少しはみ出す)を出す。
    • 複数の機器をまとめて動かすときは、つかんでいる機器(範囲選択の枠をつかんだときは、その中の最初の機器)を基準にそろえ、ほかの機器も同じだけ動かす。
    • 等間隔: 同じ行(縦なら同じ列)に並ぶほかの機器と、アイコンの中心どうしの間隔が等しくなる位置に近づいたとき(画面で 8px 以内)も吸い付く。
      • 同じ行とみなすのは、アイコンの中心の縦のずれがアイコンの高さより小さい機器・ブリッジの箱(横から見てアイコンが重なるもの)。
      • 吸い付く位置は、両隣の 2 台の真ん中と、片側の隣の 2 台の間隔をそのまま延ばした位置。
      • 中心をそろえる位置と等間隔の位置の両方が近いときは、近いほうに合わせる(同じ距離なら、そろえるほう)。
      • 等しい間隔ごとに、アイコンのすき間に実線と両端の短い線のガイドを出す。同じ間隔で続いている機器があれば、その間隔にも出す。
      • 機器のドラッグだけに効く(曲げる点のドラッグ、パレットから置くときには効かない)。
    • ツールバーの「グリッド」を入れると、そろう機器が近くに無い向きは、アイコンの中心を背景の点(20px ごと)に合わせる。パレットから置くときも、アイコンの中心を背景の点に合わせる。
    • ⌥(Option)を押している間は、吸い付かず、ガイドも出さない。矢印キーで動かすときは吸い付かない。
  • デバイス削除時は接続していたリンクも削除する。SDN のゾーンの nodes からも外す。
    • ホストを削除しても、ゲストは残して host を外す(ホストとゲストをまとめて消すときは、ホストの枠のラベルをクリックして選ぶ)。
  • コピー&ペースト: 選択デバイスと、選択内で完結するリンクを複製。デバイスIDは自動で再採番。
    • ゲストの host は、ホストも一緒に複製したなら新しいホストに、そうでなければ元のまま(図に無いホストなら外す)。
    • 複製したリンクの曲げた点(貼り付けた位置に合わせてずらす)と、手で動かした IF 名ラベルの位置も複製する。
    • グループ: メンバーの一部を複製したら、同じ ID のグループに入れる(貼り付け先に無ければ、そのグループを作る)。図にあるメンバーを全部複製したグループは、新しいグループとして複製する。
      • 作るグループの ID は、空いていればそのまま、使われていれば末尾の数字を今ある最大の番号 + 1 にする(rack1 → rack2、数字が無ければ dc → dc2)。種類・ラベル・ネットワークは引き継ぐ。
      • クリップボードの YAML には、選んだデバイスの入っているグループを、図にあるメンバーをすべて書いて入れる(全部を複製したかどうかを、貼り付けるときに見分けるため)。
  • Undo/Redo: すべての編集(GUI・YAML の両方)を対象にする。YAML エディタでの編集は、反映の単位(後述のデバウンス後)で1ステップとする。

7.5 グループの編集

  • 作る: 機器を選んで「グループにする」(⌘G。編集メニュー、複数選択のインスペクタ、デバイスのインスペクタの「グループ」欄にもある)。
    • 選んだデバイスをメンバーにした、種類なしのグループを groups の最後に足す。ブリッジの箱はメンバーに入れない。
    • ID は group1・group2 …(デバイスID と同じく、今ある最大の番号 + 1)。
    • 作ったグループを選び、インスペクタのラベル欄にカーソルを置く。「グループ」の表示が切ってあれば入れる。
  • 選ぶ: 枠のラベルをクリックすると、メンバーを選び(そのままドラッグで一緒に動かせる)、インスペクタにグループを出す。トポロジのインスペクタ(何も選んでいないとき)のグループの一覧と、問題パネルのグループの問題からも選べる(枠を出していないグループや、メンバーが空のグループも)。選んだグループの枠は強調する。
    • 選んでいる機器を変えると(クリック・範囲選択など)、グループの選択は外れる。
  • グループのインスペクタ:
    • ID(デバイスIDと同じ書き方。ほかのグループと重ならない)、種類、ラベル(空なら枠に ID を出す)。
    • メンバーの一覧(クリックでそのデバイスを選ぶ、× で外す。図に無いデバイスの ID も外せる)と、デバイスを選んで足す欄。
    • 種類がクラスタ(または networks がある)なら、ネットワークの名前とサブネットの行を足す・変える・消す(4.6)。サブネットは書き方を確かめる。
    • 「グループを削除」(メンバーの機器は残す)。
    • 枠を出していなければ、その理由(「グループ」の表示、表示レイヤー、図にあるメンバーがいない)を添える。
  • デバイスのインスペクタの「グループ」欄: 入っているグループ(クリックで選ぶ、× で外す)、ほかのグループに入れる欄、「新しいグループ」。
  • ⇧⌘G(グループを解除。編集メニューにもある)で、選んでいるグループを消す(機器は残す)。グループを選んでいるときの Delete キーは、今までどおりメンバーの機器を消す(グループは空のまま残る)。
  • YAML エディタにカーソルがあるときの ⌘G・⇧⌘G は、今までどおり「次を検索」「前を検索」。入力欄にカーソルがあるときは何もしない。
  • グループを消しても、ほかのグループの書き方やコメントは変えない。すべて消したら groups ごと消す。

7.6 右クリックのメニュー

キャンバスで右クリック(control + クリックも)すると、その物でできることをメニューに出す。

右クリックした所 項目
機器(ブリッジの箱と一緒に選んでいても) 接続を始める(機器 1 台のとき)・コピー ⌘C・カット ⌘X・グループにする ⌘G・削除 ⌫
ブリッジの箱(だけ) 削除 ⌫
線(IF 名ラベルも) まっすぐに戻す・ラベルを戻す・削除 ⌫
グループの枠のラベル メンバーをコピー ⌘C・グループを解除 ⇧⌘G
ホストの枠のラベル 機器と同じ(ホストとゲストを選ぶ)
何も無い所 ここにペースト・すべてを選択 ⌘A・自動整列・ラベルを戻す(すべて)
  • 選んでいない物を右クリックしたら、それだけを選んでからメニューを出す。選んでいる物を右クリックしたら、選択はそのままで、選んでいる物にまとめて効く。何も無い所の右クリックでは、選択を変えない。
  • 使えない項目は消さずに薄く出す(曲げていない線の「まっすぐに戻す」など)。
  • 「接続を始める」は、接続モードにして、その機器のポート選択ポップアップ(7.2)を出す。
  • 「ここにペースト」は、貼り付ける機器のまとまりの真ん中のアイコンの中心を、右クリックした位置に合わせる。続けて ⌘V で貼るときのずらす量には数えない。
  • 右ボタンのドラッグは図を動かす操作なので、何も無い所では、動かさずに離したときだけメニューを出す。
  • Esc・メニューの外のクリック・スクロールで閉じる。上下キーで項目を選び、Enter で実行する。
  • 接続モードではメニューを出さず、作りかけの接続を取りやめる。
  • キャンバスでは WebView の標準のメニューを出さない。キャンバスの外(入力欄など)では、標準のメニューのまま。

8. GUI ⇔ YAML 同期

8.1 データの流れ

  • 唯一の正(Single Source of Truth)はメモリ上のトポロジモデル(Zustand ストア)。
  • GUI 操作 → モデル更新 → YAML 文字列を再生成 → エディタに反映。
  • YAML 編集 → 300ms デバウンス → パース → 検証 → モデル更新 → キャンバス再描画。
  • 更新の発生元(gui / yaml / file)をタグ付けし、更新のループや、入力中のエディタ内容が上書きされることを防ぐ。

8.2 YAML 出力のルール

  • ユーザーが書いたコメント・キー順は可能な限り保持する(yaml の Document API で、既存ドキュメントに差分を当てる形で更新する)。
  • 新規要素は末尾に追加する。
  • IF 名は短縮形で出力する。
  • layout は GUI でデバイスやラベルを動かしたとき・線を曲げたときに更新する(ドラッグ中は更新せず、ドロップ時に1回)。

8.3 パースエラー時

  • キャンバスは直前の正常なモデルを表示し続ける。
  • エディタ上の該当行に赤線とエラーメッセージ。
  • この状態で GUI 操作をした場合は、確認ダイアログ(「YAML にエラーがあります。GUI の内容で上書きしますか?」)を出す。

9. 自動レイアウト

  • elkjs の layered アルゴリズムを使用(上から下へ: cloud → router/firewall → l3switch → l2switch → server/hypervisor/pc/ap の順になるよう、role でレイヤーのヒントを与える)。
  • ゲストは、ホストの枠の中にまとめて並べる(上からホスト・ブリッジ・ゲストの順)。ホストとゲストのかたまりは、ホストの段に置く。
  • ファイル読み込み時、layout に座標が無いデバイスだけを自動配置する(既存の座標は動かさない)。
    • ホストに座標があるブリッジとゲストは、ホストの下に並べる(上からブリッジ・ゲストの段。同じ段にあれば、その右に続ける)。ほかの機器と重なったときは「自動整列」で直す。
  • ツールバーの「自動整列」で全デバイスを再配置し、手で動かしたラベルも自動の位置に戻し、曲げた線もまっすぐに戻す(Undo 可能)。

10. ファイル操作

  • 新規 / 開く / 保存 / 名前を付けて保存。
  • 未保存の変更がある場合はタイトルとステータスバーに表示し、閉じる・新規・開く操作の前に確認する。
  • 最近使ったファイル(最大10件)をメニューに表示。
  • 新規作成したファイルの先頭には、JSON Schema のコメント行(11章)を入れる。
  • Proxmox VE から取り込む(4.7)。取り込んだ図は新規と同じく未保存の文書になる(最近使ったファイルには入れない)。
  • ファイルを外部で変更された場合の検知はフェーズ2以降。

11. AI・外部ツール向け

  • schema/constella.schema.json(JSON Schema)をリポジトリに置き、トポロジ YAML の仕様を機械可読にする。
    • 公開 URL: https://raw.githubusercontent.com/stellarcielo/Constella/main/schema/constella.schema.json
    • ファイルの先頭に # yaml-language-server: $schema=<公開 URL> を書くと、対応エディタ(VS Code の YAML 拡張機能など)で補完と検証が効く。
  • examples/ にサンプルトポロジを数個置く(小規模ラボ、2層スイッチ網、冗長構成+LAG、Proxmox VE)。
  • 将来: CLI(constella validate <file> / constella render <file> -o out.svg)と MCP サーバーを提供し、AI から直接検証・描画できるようにする。そのため、モデル・パース・検証・レイアウトのロジックは React に依存しない純粋な TypeScript モジュールとして分離しておくこと。

12. フェーズ計画と完了条件

フェーズ1: MVP

  • Tauri 2 + React + Vite のプロジェクト雛形、macOS でビルド・起動できる
  • トポロジモデルの型定義、YAML パース/出力、IF 名正規化、バリデーション(Vitest でテスト)
  • 組み込みモデルライブラリの読み込みとポート連番展開
  • パレットからのデバイス配置、移動、削除
  • ポート選択ポップアップによる接続
  • フローティングエッジ + 両端 IF 名ラベル + 並列リンクのずらし表示
  • インスペクタ(デバイスID/ラベル/モデル/IF一覧と使用状況、リンクの media/label)
  • ファイルの新規/開く/保存
  • 問題パネル

完了条件: 4.2 の例と同等のトポロジを GUI だけで作成して保存でき、保存した YAML を開き直すと同じ図が再現される。

フェーズ2: 双方向同期とレイアウト

  • YAML エディタ(CodeMirror)とのリアルタイム双方向同期(コメント保持)
  • Undo/Redo、コピー&ペースト
  • elkjs による自動レイアウト
  • SVG / PNG エクスポート
  • JSON Schema と examples の整備
  • 最近使ったファイル(最大10件)をメニューに表示
  • 問題パネルの項目から YAML エディタの該当行へジャンプ

完了条件: YAML だけを手書き(座標なし)して開くと自動配置された図になり、GUI で動かすとコメントを残したまま YAML の layout が更新される。

フェーズ3: ネットワーク情報の可視化

  • グループ(site / rack / vlan / subnet / as)の枠表示
  • IF の IP / VLAN 情報をラベルに表示
  • L1 / L2 / L3 表示切り替え
  • ユーザーモデルの追加 UI

フェーズ4以降(候補)

  • macOS 版の配布準備(簡易署名・ユニバーサル版・アイコン・GitHub Releases)
  • ハイパーバイザ(Proxmox VE など)の対応(4.6)
    • 4-1 ゲストとホストの枠(role hypervisor、host・guest・vmid、仮想化の組み込みモデル、ホストの枠、自動レイアウト、表示オプション「ゲスト」)
    • 4-2 ブリッジ(type: bridge、ブリッジの箱、ゲストとのリンク、ブリッジの位置の保存)
    • 4-3 bond・VLAN インターフェース(type: bond / vlan、ラベル)
    • 4-4 VLAN の通り道の検証
    • 4-5 クラスタ(kind: cluster、networks)
    • 4-6 SDN(ゾーン・VNet)
  • Proxmox VE からの取り込み(4.7)
    • 5-1 書き出しスクリプトと、ノード・ネットワーク・ゲスト・リンクの取り込み
    • 5-2 クラスタ(kind: cluster、corosync・migration のネットワーク)と SDN の取り込み
    • API から直接取り込む(候補。URL と API トークンを入力する)
  • IF 名ラベルを手で動かす・元に戻す(7.3)
  • トランシーバ(光モジュール)を IF に書く・確かめる(5.4)
  • 線を曲げる(7.3)
  • ガイドとスナップ(7.4)
  • モデルの管理画面(5.5)
  • モデルのアイコンを置き換える(5.1)
  • グループを GUI で作る・編集する(7.5)
  • 無線のポートに何本でもつなぐ(4.3)・汎用モデルをフレキシブルにする(5.3)
  • コピー&ペーストで、曲げた点・ラベルの位置・グループも引き継ぐ(7.4)
  • 右クリックのメニュー(7.6)
  • 等間隔のガイド(7.4)
  • モデルとアイコンをまとめて書き出す・読み込む(5.5)
  • Windows / Linux ビルドとインストーラ
  • CLI / MCP サーバー
  • LLDP / SNMP からの取り込み、containerlab / NetBox との連携
  • シミュレーション機能

13. ディレクトリ構成(案)

constella/
├── CLAUDE.md
├── docs/
│   └── SPEC.md
├── schema/
│   └── constella.schema.json
├── examples/
├── src-tauri/                 # Rust(最小限)
└── src/
    ├── core/                  # React 非依存の純粋ロジック
    │   ├── types.ts
    │   ├── parse.ts           # YAML → モデル
    │   ├── serialize.ts       # モデル → YAML(Document 差分更新)
    │   ├── validate.ts
    │   ├── ifname.ts          # IF 名の正規化・略称
    │   ├── portExpand.ts      # {1-24} の連番展開
    │   └── layout.ts          # elkjs ラッパ
    ├── library/
    │   └── models/            # 組み込みモデル YAML
    ├── store/                 # Zustand ストア、Undo 履歴
    ├── components/
    │   ├── Canvas/            # React Flow、カスタムノード/エッジ
    │   ├── Palette/
    │   ├── Inspector/
    │   ├── PortPicker/
    │   ├── YamlEditor/
    │   ├── ProblemsPanel/
    │   └── Toolbar/
    ├── icons/                 # ロール別アイコン(オリジナル SVG)
    └── platform/              # Tauri API の薄いラッパ(OS 差分をここに閉じ込める)
  • アイコンはオリジナルのシンプルな SVG を用意する。他社のアイコンセットはライセンス確認が必要なため同梱しない。

14. テスト方針

  • src/core/ は Vitest で単体テストを書く(パース、出力、IF 名正規化、連番展開、バリデーション、コメント保持)。
  • 往復テスト: examples/ の各ファイルを「読み込み → 出力」して意味的に同一であることを確認する。
  • UI は各フェーズの完了条件を手動確認チェックリストで確認する。