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}