mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Merge branch '3.1' into api-issue-04-burx2g
This commit is contained in:
44 files changed
+1839
-209
No files matched your search
@@ -37,6 +37,10 @@ from .controller.links import (
|
||||
Link,
|
||||
LinkCapture,
|
||||
LinkCreate,
|
||||
LinkFilterDefinition,
|
||||
LinkFilterParameter,
|
||||
LinkFilters,
|
||||
LinkFilterType,
|
||||
LinkUpdate,
|
||||
MarkerCreate,
|
||||
MarkerDefinitionCreate,
|
||||
@@ -109,6 +113,7 @@ from .compute.vpcs_nodes import VPCS, VPCSCreate, VPCSUpdate
|
||||
from .controller.capabilities import Capabilities
|
||||
from .controller.iou_license import IOULicense
|
||||
from .controller.netmiko import NetmikoDeviceType, NetmikoDeviceTypeList
|
||||
from .controller.notifications import Notification, NotificationAction
|
||||
from .controller.pools import Resource, ResourceCreate, ResourcePool, ResourcePoolCreate, ResourcePoolUpdate
|
||||
from .controller.rbac import ACE, ACECreate, ACEUpdate, Privilege, Role, RoleCreate, RoleUpdate
|
||||
from .controller.settings import SettingsResponse, SettingsUpdate, SettingsUpdateResponse
|
||||
@@ -244,6 +249,10 @@ __all__ = [
|
||||
"Link",
|
||||
"LinkCapture",
|
||||
"LinkCreate",
|
||||
"LinkFilterDefinition",
|
||||
"LinkFilterParameter",
|
||||
"LinkFilterType",
|
||||
"LinkFilters",
|
||||
"LinkUpdate",
|
||||
"LoggedInUserUpdate",
|
||||
"MarkerCreate",
|
||||
@@ -261,6 +270,8 @@ __all__ = [
|
||||
"NodeDuplicate",
|
||||
"NodeFile",
|
||||
"NodeUpdate",
|
||||
"Notification",
|
||||
"NotificationAction",
|
||||
"OpenAIMessage",
|
||||
"OpenAIToolCall",
|
||||
"Privilege",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
@@ -26,7 +26,11 @@ class ErrorMessage(BaseModel):
|
||||
"""
|
||||
|
||||
message: str
|
||||
code: Optional[str] = None
|
||||
code: Optional[str] = Field(
|
||||
None,
|
||||
description="Stable machine-readable error code, for example port_in_use or node_not_found",
|
||||
)
|
||||
details: Optional[dict[str, Any]] = Field(None, description="Optional structured data about the error")
|
||||
|
||||
|
||||
class NodeStatus(str, Enum):
|
||||
@@ -37,6 +41,8 @@ class NodeStatus(str, Enum):
|
||||
stopped = "stopped"
|
||||
started = "started"
|
||||
suspended = "suspended"
|
||||
starting = "starting"
|
||||
stopping = "stopping"
|
||||
|
||||
|
||||
class CustomAdapter(BaseModel):
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
from typing import List, Literal, Optional, Tuple
|
||||
from typing import Annotated, List, Literal, Optional, Tuple
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator, model_validator
|
||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
||||
|
||||
from ..update import PartialUpdateModel
|
||||
from .labels import Label
|
||||
@@ -80,6 +80,77 @@ class LinkStyle(BaseModel):
|
||||
control_offset: Optional[Tuple[float, float]] = None
|
||||
|
||||
|
||||
class LinkFilterType(str, Enum):
|
||||
"""
|
||||
Packet filter type.
|
||||
"""
|
||||
|
||||
frequency_drop = "frequency_drop"
|
||||
packet_loss = "packet_loss"
|
||||
delay = "delay"
|
||||
corrupt = "corrupt"
|
||||
bpf = "bpf"
|
||||
|
||||
|
||||
class LinkFilters(BaseModel):
|
||||
"""
|
||||
Packet filters applied on a link. Each filter is an array of positional values.
|
||||
A filter set to an empty array or to its disabled value (0, 0 for delay, empty text for bpf) is not applied.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
frequency_drop: Optional[List[Annotated[int, Field(ge=-1, le=32767)]]] = Field(
|
||||
None,
|
||||
max_length=1,
|
||||
description="[frequency]: -1 drops every packet, N > 0 drops every Nth packet, 0 disables the filter",
|
||||
)
|
||||
packet_loss: Optional[List[Annotated[int, Field(ge=0, le=100)]]] = Field(
|
||||
None,
|
||||
max_length=1,
|
||||
description="[chance]: percentage chance for a packet to be lost, 0 disables the filter",
|
||||
)
|
||||
delay: Optional[List[Annotated[int, Field(ge=0, le=32767)]]] = Field(
|
||||
None,
|
||||
max_length=2,
|
||||
description="[latency, jitter] in milliseconds: latency must be 1 to 32767 unless both values are 0, "
|
||||
"which disables the filter; jitter is 0 to 32767",
|
||||
)
|
||||
corrupt: Optional[List[Annotated[int, Field(ge=0, le=100)]]] = Field(
|
||||
None,
|
||||
max_length=1,
|
||||
description="[chance]: percentage chance for a packet to be corrupted, 0 disables the filter",
|
||||
)
|
||||
bpf: Optional[List[str]] = Field(
|
||||
None,
|
||||
max_length=1,
|
||||
description="[expressions]: BPF expressions, one per line, matching packets are dropped",
|
||||
)
|
||||
|
||||
|
||||
class LinkFilterParameter(BaseModel):
|
||||
"""
|
||||
Parameter of a packet filter.
|
||||
"""
|
||||
|
||||
name: str
|
||||
type: Literal["int", "text"]
|
||||
minimum: Optional[int] = None
|
||||
maximum: Optional[int] = None
|
||||
unit: Optional[str] = None
|
||||
|
||||
|
||||
class LinkFilterDefinition(BaseModel):
|
||||
"""
|
||||
Packet filter available on a link.
|
||||
"""
|
||||
|
||||
type: LinkFilterType
|
||||
name: str
|
||||
description: str
|
||||
parameters: List[LinkFilterParameter]
|
||||
|
||||
|
||||
class LinkBase(BaseModel):
|
||||
"""
|
||||
Link data.
|
||||
@@ -88,7 +159,7 @@ class LinkBase(BaseModel):
|
||||
nodes: Optional[List[LinkNode]] = Field(None, min_length=0, max_length=2)
|
||||
suspend: Optional[bool] = None
|
||||
link_style: Optional[LinkStyle] = None
|
||||
filters: Optional[dict] = None
|
||||
filters: Optional[LinkFilters] = None
|
||||
markers: Optional[dict] = Field(
|
||||
None, description="Traffic-insight markers on this link: name → {bpf, tag, enabled}"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#
|
||||
# Copyright (C) 2026 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from .computes import Compute
|
||||
from .drawings import Drawing
|
||||
from .links import Link
|
||||
from .nodes import Node
|
||||
from .projects import Project
|
||||
from .snapshots import Snapshot
|
||||
|
||||
|
||||
class NotificationAction(str, Enum):
|
||||
"""
|
||||
Values of the "action" field of a notification.
|
||||
"""
|
||||
|
||||
ping = "ping"
|
||||
log_info = "log.info"
|
||||
log_warning = "log.warning"
|
||||
log_error = "log.error"
|
||||
node_created = "node.created"
|
||||
node_updated = "node.updated"
|
||||
node_deleted = "node.deleted"
|
||||
node_interface_status = "node.interface_status"
|
||||
link_created = "link.created"
|
||||
link_updated = "link.updated"
|
||||
link_deleted = "link.deleted"
|
||||
link_web_wireshark_started = "link.web_wireshark_started"
|
||||
drawing_created = "drawing.created"
|
||||
drawing_updated = "drawing.updated"
|
||||
drawing_deleted = "drawing.deleted"
|
||||
marker_match = "marker.match"
|
||||
snapshot_restored = "snapshot.restored"
|
||||
project_created = "project.created"
|
||||
project_updated = "project.updated"
|
||||
project_opened = "project.opened"
|
||||
project_closed = "project.closed"
|
||||
project_deleted = "project.deleted"
|
||||
compute_created = "compute.created"
|
||||
compute_updated = "compute.updated"
|
||||
compute_deleted = "compute.deleted"
|
||||
template_created = "template.created"
|
||||
template_updated = "template.updated"
|
||||
template_deleted = "template.deleted"
|
||||
settings_updated = "settings.updated"
|
||||
|
||||
|
||||
class PingEvent(BaseModel):
|
||||
"""
|
||||
Event of a "ping" notification, sent when the stream is opened and then at least every 5 seconds.
|
||||
"""
|
||||
|
||||
cpu_usage_percent: float = Field(..., description="CPU usage of the server in percent")
|
||||
memory_usage_percent: float = Field(..., description="Memory usage of the server in percent")
|
||||
disk_usage_percent: float = Field(..., description="Disk usage of the project directory in percent")
|
||||
compute_id: Optional[str] = Field(None, description="Compute that sent the ping (project streams only)")
|
||||
|
||||
|
||||
class LogEvent(BaseModel):
|
||||
"""
|
||||
Event of a "log.info", "log.warning" or "log.error" notification.
|
||||
"""
|
||||
|
||||
message: str = Field(..., description="Log message")
|
||||
|
||||
|
||||
class NodeInterfaceStatusEvent(BaseModel):
|
||||
"""
|
||||
Event of a "node.interface_status" notification.
|
||||
"""
|
||||
|
||||
project_id: UUID
|
||||
node_id: UUID
|
||||
adapter_number: int
|
||||
port_number: int
|
||||
status: str = Field(..., description="Status of the interface")
|
||||
|
||||
|
||||
class MarkerMatchEvent(BaseModel):
|
||||
"""
|
||||
Event of a "marker.match" notification, only sent on the markers WebSocket.
|
||||
"""
|
||||
|
||||
project_id: UUID
|
||||
node_id: UUID
|
||||
link_id: Optional[UUID] = None
|
||||
filter: str = Field(..., description="Name of the packet filter that matched")
|
||||
tag: Optional[int] = Field(None, description="Tag of the marker")
|
||||
ts: float = Field(..., description="Timestamp reported by uBridge (seconds since the epoch)")
|
||||
len: int = Field(0, description="Length of the matching packet")
|
||||
dir: Optional[str] = Field(None, description="Direction of the packet relative to the capture node")
|
||||
|
||||
|
||||
class LinkWebWiresharkStartedEvent(BaseModel):
|
||||
"""
|
||||
Event of a "link.web_wireshark_started" notification.
|
||||
"""
|
||||
|
||||
link_id: UUID
|
||||
ws_url: Optional[str] = Field(None, description="URL of the Web Wireshark session")
|
||||
|
||||
|
||||
class SettingsUpdatedEvent(BaseModel):
|
||||
"""
|
||||
Event of a "settings.updated" notification.
|
||||
"""
|
||||
|
||||
changed: List[str] = Field(..., description="Names of the settings that changed (values are never sent)")
|
||||
restart_required: List[str] = Field(..., description="Names of the changed settings that need a server restart")
|
||||
|
||||
|
||||
class Notification(BaseModel):
|
||||
"""
|
||||
A message of the notification streams. HTTP streams send one JSON object per line (application/x-ndjson),
|
||||
WebSocket streams send one JSON object per text frame.
|
||||
"""
|
||||
|
||||
action: NotificationAction = Field(..., description="Type of the notification")
|
||||
event: Union[
|
||||
PingEvent,
|
||||
LogEvent,
|
||||
NodeInterfaceStatusEvent,
|
||||
MarkerMatchEvent,
|
||||
LinkWebWiresharkStartedEvent,
|
||||
SettingsUpdatedEvent,
|
||||
Node,
|
||||
Link,
|
||||
Drawing,
|
||||
Project,
|
||||
Compute,
|
||||
Snapshot,
|
||||
Dict[str, Any],
|
||||
] = Field(..., description="Payload of the notification, its content depends on the action")
|
||||
project_id: Optional[UUID] = Field(None, description="Project the notification belongs to, when set by the sender")
|
||||
compute_id: Optional[str] = Field(None, description="Compute that emitted the notification, when set by the sender")
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
@@ -111,8 +112,8 @@ class NodeFile(BaseModel):
|
||||
|
||||
path: str = Field(..., description="File name")
|
||||
size: int = Field(..., description="File size in bytes")
|
||||
created_at: str = Field(..., description="File creation time (ISO 8601)")
|
||||
modified_at: str = Field(..., description="File modification time (ISO 8601)")
|
||||
created_at: Optional[datetime] = Field(None, description="File creation time (ISO 8601)")
|
||||
modified_at: Optional[datetime] = Field(None, description="File modification time (ISO 8601)")
|
||||
file_type: str = Field(..., description="File type determined by the file command")
|
||||
|
||||
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
|
||||
class SnapshotBase(BaseModel):
|
||||
@@ -44,4 +45,14 @@ class Snapshot(SnapshotBase):
|
||||
name: str = Field(..., description="Name of the snapshot")
|
||||
filename: str = Field(..., description="Filename of the snapshot")
|
||||
description: str = Field(..., description="Description of the snapshot")
|
||||
created_at: int = Field(..., description="Date of the snapshot (UTC timestamp)")
|
||||
created_at: int = Field(
|
||||
..., description="Date of the snapshot (UTC timestamp). Deprecated: use created", deprecated=True
|
||||
)
|
||||
created: datetime = Field(..., description="Date of the snapshot (ISO 8601, UTC)")
|
||||
|
||||
@model_validator(mode="before")
|
||||
@classmethod
|
||||
def _derive_created(cls, data):
|
||||
if isinstance(data, dict) and "created" not in data and data.get("created_at") is not None:
|
||||
data = {**data, "created": datetime.fromtimestamp(data["created_at"], tz=timezone.utc)}
|
||||
return data
|
||||
@@ -22,7 +22,9 @@ from pydantic import BaseModel
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
expires_in: int
|
||||
refresh_token: Optional[str] = None
|
||||
refresh_expires_in: Optional[int] = None
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
|
||||
@@ -32,6 +32,11 @@ from .nodes import Node
|
||||
from .projects import Supplier, Variable
|
||||
|
||||
|
||||
class TopologyLink(Link):
|
||||
# Legacy topology files store filters as untyped dicts
|
||||
filters: Optional[dict] = None # type: ignore[assignment]
|
||||
|
||||
|
||||
class TopologyType(str, Enum):
|
||||
topology = "topology"
|
||||
|
||||
@@ -39,7 +44,7 @@ class TopologyType(str, Enum):
|
||||
class TopologyContent(BaseModel):
|
||||
computes: List[Compute] = Field(..., description="List of computes")
|
||||
drawings: List[Drawing] = Field(..., description="List of drawings")
|
||||
links: List[Link] = Field(..., description="List of links")
|
||||
links: List[TopologyLink] = Field(..., description="List of links")
|
||||
nodes: List[Node] = Field(..., description="List of nodes")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user