Skip to main content

ua_client/
model.rs

1use std::collections::{HashMap, HashSet, VecDeque};
2
3use opcua::types::{NodeId, ObjectId};
4
5use crate::types::{
6    AuthMode, EndpointInfo, LogLine, NodeSummary, ReferenceRow, SecurityMode, TreeChild,
7};
8
9const MAX_LOG_LINES: usize = 1000;
10const MAX_HISTORY: usize = 20;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ConnectionState {
14    Disconnected,
15    Connecting,
16    Connected,
17    Disconnecting,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum DetailTab {
22    Attributes,
23    Events,
24    DataChanges,
25    References,
26}
27
28#[derive(Debug, Default)]
29pub struct TreeModel {
30    pub children: HashMap<NodeId, Vec<TreeChild>>,
31    pub expanded: HashSet<NodeId>,
32    pub loading: HashSet<NodeId>,
33}
34
35impl TreeModel {
36    pub fn clear(&mut self) {
37        self.children.clear();
38        self.expanded.clear();
39        self.loading.clear();
40    }
41}
42
43pub struct AppModel {
44    pub endpoint_url: String,
45    pub endpoint_history: Vec<String>,
46    pub connection: ConnectionState,
47    pub root_node: NodeId,
48    pub tree: TreeModel,
49    pub selected: Option<NodeId>,
50    pub node_summary: Option<NodeSummary>,
51    pub active_tab: DetailTab,
52    pub references: Option<Vec<ReferenceRow>>,
53    pub references_loading: bool,
54    pub log: VecDeque<LogLine>,
55    pub selected_endpoint: Option<EndpointInfo>,
56    pub endpoints_loading: bool,
57    pub discovered_endpoints: Option<Vec<EndpointInfo>>,
58    pub endpoints_dialog_open: bool,
59    pub auth_mode: AuthMode,
60    pub auth_username: String,
61    pub auth_password: String,
62    pub auth_cert_path: String,
63    pub auth_key_path: String,
64    pub last_selection_paths: HashMap<String, Vec<NodeId>>,
65    pub endpoint_mode_filter: SecurityMode,
66    pub file_picker_open: bool,
67}
68
69impl Default for AppModel {
70    fn default() -> Self {
71        Self {
72            endpoint_url: "opc.tcp://localhost:4855".to_string(),
73            endpoint_history: Vec::new(),
74            connection: ConnectionState::Disconnected,
75            root_node: NodeId::new(0, ObjectId::RootFolder as u32),
76            tree: TreeModel::default(),
77            selected: None,
78            node_summary: None,
79            active_tab: DetailTab::References,
80            references: None,
81            references_loading: false,
82            log: VecDeque::with_capacity(MAX_LOG_LINES),
83            selected_endpoint: None,
84            endpoints_loading: false,
85            discovered_endpoints: None,
86            endpoints_dialog_open: false,
87            auth_mode: AuthMode::Anonymous,
88            auth_username: String::new(),
89            auth_password: String::new(),
90            auth_cert_path: String::new(),
91            auth_key_path: String::new(),
92            last_selection_paths: HashMap::new(),
93            endpoint_mode_filter: SecurityMode::None,
94            file_picker_open: false,
95        }
96    }
97}
98
99impl AppModel {
100    pub fn push_log(&mut self, line: LogLine) {
101        if self.log.len() == MAX_LOG_LINES {
102            self.log.pop_front();
103        }
104        self.log.push_back(line);
105    }
106
107    pub fn reset_session_state(&mut self) {
108        self.tree.clear();
109        self.selected = None;
110        self.node_summary = None;
111        self.references = None;
112        self.references_loading = false;
113    }
114
115    pub fn record_successful_connection(&mut self) {
116        let url = self.endpoint_url.trim().to_string();
117        if url.is_empty() {
118            return;
119        }
120        self.endpoint_history.retain(|u| u != &url);
121        self.endpoint_history.insert(0, url);
122        self.endpoint_history.truncate(MAX_HISTORY);
123    }
124}