spectrum-barometer 2.2.2__tar.gz → 2.2.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {spectrum_barometer-2.2.2/spectrum_barometer.egg-info → spectrum_barometer-2.2.5}/PKG-INFO +1 -1
  2. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/background.py +131 -96
  3. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/data.py +29 -18
  4. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/graphs.py +20 -9
  5. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer_logger.py +17 -7
  6. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/pyproject.toml +1 -1
  7. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5/spectrum_barometer.egg-info}/PKG-INFO +1 -1
  8. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/routes.py +3 -10
  9. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/templates/dashboard.html +1 -1
  10. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/templates/stats.html +1 -1
  11. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/LICENSE.txt +0 -0
  12. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/MANIFEST.in +0 -0
  13. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/README.md +0 -0
  14. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/__init__.py +0 -0
  15. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/actions.py +0 -0
  16. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/barometer/paths.py +0 -0
  17. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/setup.cfg +0 -0
  18. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/spectrum_barometer.egg-info/SOURCES.txt +0 -0
  19. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/spectrum_barometer.egg-info/dependency_links.txt +0 -0
  20. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/spectrum_barometer.egg-info/entry_points.txt +0 -0
  21. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/spectrum_barometer.egg-info/requires.txt +0 -0
  22. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/spectrum_barometer.egg-info/top_level.txt +0 -0
  23. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/__init__.py +0 -0
  24. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/app.py +0 -0
  25. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/static/refresh.js +0 -0
  26. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/static/stabbypeng.png +0 -0
  27. {spectrum_barometer-2.2.2 → spectrum_barometer-2.2.5}/web/templates/base.html +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spectrum-barometer
3
- Version: 2.2.2
3
+ Version: 2.2.5
4
4
  Summary: Monitor barometric pressure from Spectrum SAX2V1S routers
5
5
  Author-email: BobaTeagrl <Unsorted2750@proton.me>
6
6
  License: MIT
@@ -1,114 +1,148 @@
1
1
  """Background monitoring using threading with file-based state"""
2
2
  import threading
3
3
  import time
4
+ import os
4
5
  import logging
6
+ import psutil
5
7
  from barometer.paths import get_app_dir
6
8
  from datetime import datetime
7
9
  from pathlib import Path
8
10
 
11
+
9
12
  def get_state_file():
10
13
  """Get path to state file"""
11
14
  return get_app_dir() / 'monitor.state'
12
15
 
16
+
13
17
  def get_interval_file():
14
18
  """Get path to interval file"""
15
19
  return get_app_dir() / 'monitor.interval'
16
20
 
21
+
17
22
  def get_pid_file():
18
23
  """Get path to PID file"""
19
24
  return get_app_dir() / 'monitor.pid'
20
25
 
26
+
21
27
  def get_owner_file():
22
- """Get path to owner file"""
28
+ """Get path to owner file (kept around, not used for access control anymore)"""
23
29
  return get_app_dir() / 'monitor.owner'
24
30
 
31
+
32
+ def _all_state_files():
33
+ return [get_state_file(), get_pid_file(), get_interval_file(), get_owner_file()]
34
+
35
+
36
+ def _cleanup_state_files():
37
+ for f in _all_state_files():
38
+ f.unlink(missing_ok=True)
39
+
40
+
41
+ def _write_pid_record():
42
+ # store pid and create_time together so a reused pid can't be mistaken
43
+ pid = os.getpid()
44
+ try:
45
+ create_time = psutil.Process(pid).create_time()
46
+ except psutil.Error:
47
+ create_time = None
48
+ get_pid_file().write_text(f"{pid}:{create_time}")
49
+ return pid
50
+
51
+
52
+ def _read_pid_record():
53
+ pid_file = get_pid_file()
54
+ if not pid_file.exists():
55
+ return None, None
56
+ try:
57
+ text = pid_file.read_text().strip()
58
+ pid_str, _, ct_str = text.partition(':')
59
+ pid = int(pid_str)
60
+ create_time = float(ct_str) if ct_str and ct_str != 'None' else None
61
+ return pid, create_time
62
+ except (ValueError, OSError):
63
+ return None, None
64
+
65
+
66
+ def _process_still_matches(pid, create_time):
67
+ # pid alone isn't enough it could've been recycled by a totally different process
68
+ if pid is None:
69
+ return False
70
+ try:
71
+ proc = psutil.Process(pid)
72
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
73
+ return False
74
+
75
+ if create_time is None:
76
+ return True
77
+
78
+ try:
79
+ return abs(proc.create_time() - create_time) < 1.0
80
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
81
+ return False
82
+
83
+
25
84
  def is_monitoring():
26
85
  """Check if monitoring thread is running"""
27
- import os
28
- import psutil
29
-
30
86
  state_file = get_state_file()
31
- pid_file = get_pid_file()
32
-
33
- if not state_file.exists() or not pid_file.exists():
87
+
88
+ if not state_file.exists():
34
89
  return False
35
-
90
+
36
91
  try:
37
92
  state = state_file.read_text().strip()
38
- pid = int(pid_file.read_text().strip())
39
-
40
- # Check if state says running AND process exists
41
- if state != 'running':
42
- return False
43
-
44
- # Check if the PID is still alive
45
- if psutil.pid_exists(pid):
46
- return True
47
- else:
48
- # Stale state files - clean them up
49
- state_file.unlink(missing_ok=True)
50
- pid_file.unlink(missing_ok=True)
51
- get_interval_file().unlink(missing_ok=True)
52
- get_owner_file().unlink(missing_ok=True)
53
- return False
54
- except:
93
+ except OSError:
55
94
  return False
56
95
 
57
- def is_owned_by_current_process():
58
- """Check if monitoring is owned by the current process"""
59
- import os
60
-
61
- pid_file = get_pid_file()
62
- if not pid_file.exists():
63
- return False
64
-
65
- try:
66
- pid = int(pid_file.read_text().strip())
67
- return pid == os.getpid()
68
- except:
96
+ if state != 'running':
69
97
  return False
70
98
 
99
+ pid, create_time = _read_pid_record()
100
+
101
+ if _process_still_matches(pid, create_time):
102
+ return True
103
+
104
+ # stale files left over from a crash - clean up
105
+ _cleanup_state_files()
106
+ return False
107
+
108
+
109
+ def is_owned_by_current_process():
110
+ """Kept for backwards compat, not used to gate start/stop anymore"""
111
+ pid, _ = _read_pid_record()
112
+ return pid == os.getpid()
113
+
114
+
71
115
  def get_monitor_info():
72
116
  """Get monitoring status"""
73
- import os
74
-
75
117
  interval_file = get_interval_file()
76
- pid_file = get_pid_file()
77
118
  interval = 300
78
- pid = None
79
- owned_by_me = False
80
-
119
+
81
120
  if interval_file.exists():
82
121
  try:
83
122
  interval = int(interval_file.read_text().strip())
84
123
  except ValueError:
85
124
  pass
86
-
87
- if pid_file.exists():
88
- try:
89
- pid = int(pid_file.read_text().strip())
90
- owned_by_me = (pid == os.getpid())
91
- except ValueError:
92
- pass
93
-
125
+
94
126
  running = is_monitoring()
95
-
127
+ pid, _ = _read_pid_record() if running else (None, None)
128
+
96
129
  return {
97
130
  'running': running,
98
- 'pid': pid if running else None,
131
+ 'pid': pid,
99
132
  'interval': interval if running else None,
100
- 'owned_by_me': owned_by_me if running else False,
101
133
  }
102
134
 
135
+
103
136
  # Thread management (only exists in the process that started it)
104
137
  _monitor_thread = None
105
138
 
139
+
106
140
  def _monitor_loop(interval):
107
141
  """Internal monitoring loop"""
108
142
  from barometer.actions import scrape_single_reading
109
-
143
+
110
144
  state_file = get_state_file()
111
-
145
+
112
146
  while state_file.exists() and state_file.read_text().strip() == 'running':
113
147
  try:
114
148
  result = scrape_single_reading()
@@ -118,94 +152,95 @@ def _monitor_loop(interval):
118
152
  logging.error(f"Background scrape failed: {result['message']}")
119
153
  except Exception as e:
120
154
  logging.error(f"Monitor error: {e}")
121
-
155
+
122
156
  # Sleep in small chunks so we can stop quickly
123
157
  for _ in range(interval):
124
158
  if not state_file.exists() or state_file.read_text().strip() != 'running':
125
159
  break
126
160
  time.sleep(1)
127
-
128
- # Clean up when loop exits
129
- state_file.unlink(missing_ok=True)
161
+
162
+ _cleanup_state_files()
130
163
  logging.info("Background monitoring stopped")
131
164
 
165
+
132
166
  def start_monitoring(interval=300):
133
167
  """Start monitoring in background thread"""
134
168
  global _monitor_thread
135
- import os
136
-
169
+
137
170
  state_file = get_state_file()
138
171
  interval_file = get_interval_file()
139
- pid_file = get_pid_file()
140
- owner_file = get_owner_file()
141
-
172
+
142
173
  # Check if already running (cleans up stale files automatically)
143
174
  if is_monitoring():
144
- pid = int(pid_file.read_text().strip()) if pid_file.exists() else None
175
+ pid, _ = _read_pid_record()
145
176
  return {
146
177
  'success': False,
147
178
  'message': 'Monitoring is already running',
148
179
  'pid': pid
149
180
  }
150
-
181
+
151
182
  try:
152
- current_pid = os.getpid()
153
183
  state_file.write_text('running')
154
184
  interval_file.write_text(str(interval))
155
- pid_file.write_text(str(current_pid))
156
- owner_file.write_text(str(current_pid))
157
-
185
+ current_pid = _write_pid_record()
186
+
158
187
  # Start thread
159
188
  _monitor_thread = threading.Thread(
160
- target=_monitor_loop,
161
- args=(interval,),
189
+ target=_monitor_loop,
190
+ args=(interval,),
162
191
  daemon=True,
163
192
  name="BarometerMonitor"
164
193
  )
165
194
  _monitor_thread.start()
166
-
195
+
167
196
  return {
168
197
  'success': True,
169
198
  'message': f'Monitoring started (interval: {interval}s)',
170
199
  'pid': current_pid
171
200
  }
172
201
  except Exception as e:
173
- state_file.unlink(missing_ok=True)
174
- pid_file.unlink(missing_ok=True)
175
- owner_file.unlink(missing_ok=True)
202
+ _cleanup_state_files()
176
203
  return {
177
204
  'success': False,
178
205
  'message': f'Failed to start: {e}',
179
206
  'pid': None
180
207
  }
181
208
 
182
- def stop_monitoring():
183
- """Stop monitoring thread (only if owned by this process)"""
209
+
210
+ def stop_monitoring(wait_seconds=3):
211
+ """Stop background monitoring. Works from any process - signals the
212
+ real owner through the state file instead of requiring a pid match."""
184
213
  state_file = get_state_file()
185
- interval_file = get_interval_file()
186
- pid_file = get_pid_file()
187
- owner_file = get_owner_file()
188
-
214
+
189
215
  if not state_file.exists():
216
+ _cleanup_state_files()
190
217
  return {
191
218
  'success': False,
192
219
  'message': 'Monitoring is not running'
193
220
  }
194
-
195
- # Only stop if we own it
196
- if not is_owned_by_current_process():
221
+
222
+ pid, create_time = _read_pid_record()
223
+
224
+ if not _process_still_matches(pid, create_time):
225
+ # owner's already dead/recycled, nothing to signal
226
+ _cleanup_state_files()
197
227
  return {
198
- 'success': False,
199
- 'message': 'Monitoring is running in a different process'
228
+ 'success': True,
229
+ 'message': 'Cleared stale monitoring state (the process behind it was no longer running)'
200
230
  }
201
-
202
- # Signal thread to stop and clean up all state files
203
- state_file.unlink(missing_ok=True)
204
- interval_file.unlink(missing_ok=True)
205
- pid_file.unlink(missing_ok=True)
206
- owner_file.unlink(missing_ok=True)
207
-
231
+
232
+ state_file.write_text('stop')
233
+
234
+ pid_file = get_pid_file()
235
+ for _ in range(wait_seconds * 2):
236
+ if not pid_file.exists():
237
+ return {
238
+ 'success': True,
239
+ 'message': 'Monitoring stopped'
240
+ }
241
+ time.sleep(0.5)
242
+
208
243
  return {
209
244
  'success': True,
210
- 'message': 'Monitoring stopped'
245
+ 'message': 'Stop signal sent - monitoring will halt within a few seconds'
211
246
  }
@@ -1,42 +1,57 @@
1
- from barometer.paths import get_data_dir
1
+ from barometer.paths import get_data_dir, get_archive_dir
2
2
  import pandas
3
3
  from datetime import datetime, timedelta
4
4
  import logging
5
5
  from barometer.paths import get_logs_dir
6
6
 
7
+
8
+ def _read_and_clean(csv_path):
9
+ #drop rows with bad timestamps instead of letting them become NaT
10
+ #and messing up sort order / "latest reading"
11
+ df = pandas.read_csv(csv_path)
12
+ df['timestamp'] = pandas.to_datetime(df['timestamp'], format='ISO8601', errors='coerce')
13
+
14
+ bad_rows = df['timestamp'].isna().sum()
15
+ if bad_rows:
16
+ logging.warning(
17
+ f"Dropped {bad_rows} row(s) with unparseable/missing timestamp from {csv_path}"
18
+ )
19
+ df = df.dropna(subset=['timestamp'])
20
+
21
+ return df
22
+
23
+
7
24
  def load_data(include_archives=False):
8
25
  data_file = get_data_dir() / 'readings.csv'
9
-
26
+
10
27
  if not data_file.exists():
11
28
  return None
12
-
29
+
13
30
  dfs = []
14
- df = pandas.read_csv(data_file)
15
- df['timestamp'] = pandas.to_datetime(df['timestamp'], format='ISO8601')
31
+ df = _read_and_clean(data_file)
16
32
  dfs.append(df)
17
-
33
+
18
34
  if include_archives:
19
35
  archive_dir = get_archive_dir()
20
36
  if archive_dir.exists():
21
37
  for csv_file in archive_dir.rglob('*.csv'):
22
- df_archive = pandas.read_csv(csv_file)
23
- df_archive['timestamp'] = pandas.to_datetime(df_archive['timestamp'], format='ISO8601')
24
- dfs.append(df_archive)
25
-
38
+ dfs.append(_read_and_clean(csv_file))
39
+
26
40
  if not dfs:
27
41
  return None
28
-
42
+
29
43
  combined = pandas.concat(dfs, ignore_index=True)
30
44
  combined = combined.sort_values('timestamp').reset_index(drop=True)
31
45
  combined = combined.drop_duplicates(subset=['timestamp'], keep='last')
32
-
46
+
33
47
  return combined
34
48
 
49
+
35
50
  def setup_logging(verbose=False):
36
51
  # configure logging
37
52
  level = logging.DEBUG if verbose else logging.INFO
38
53
  log_file = get_logs_dir() / 'barometer.log'
39
-
54
+
40
55
  logging.basicConfig(
41
56
  level=level,
42
57
  format='%(asctime)s - %(levelname)s - %(message)s',
@@ -44,8 +59,4 @@ def setup_logging(verbose=False):
44
59
  logging.FileHandler(log_file),
45
60
  logging.StreamHandler()
46
61
  ]
47
- )
48
-
49
-
50
-
51
-
62
+ )
@@ -12,7 +12,18 @@ from qbstyles import mpl_style
12
12
  from pathlib import Path
13
13
 
14
14
 
15
- mpl_style(dark=True)
15
+ mpl_style(dark=True, minor_ticks=False)
16
+
17
+ def _apply_x_axis_format(ax, days):
18
+ """Show hours if generating one day"""
19
+ if days == 1:
20
+ ax.xaxis.set_major_locator(mdates.HourLocator(interval=1))
21
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
22
+
23
+ else:
24
+ ax.xaxis.set_major_locator(mdates.AutoDateLocator())
25
+ ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
26
+
16
27
  def generate_line_graph(df, output, days):
17
28
  """Standard line graph"""
18
29
  fig, ax = plt.subplots(figsize=(12, 6))
@@ -25,7 +36,7 @@ def generate_line_graph(df, output, days):
25
36
  ax.set_title(f'Barometric Pressure - Last {days} Days', fontsize=14, fontweight='bold')
26
37
  ax.grid(True, alpha=0.3)
27
38
  ax.legend()
28
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
39
+ _apply_x_axis_format(ax, days)
29
40
  plt.xticks(rotation=45)
30
41
  plt.tight_layout()
31
42
  plt.savefig(output, dpi=150, bbox_inches='tight')
@@ -50,7 +61,7 @@ def generate_smooth_graph(df, output, days, window=12):
50
61
  ax.set_title(f'Barometric Pressure with Trend - Last {days} Days', fontsize=14, fontweight='bold')
51
62
  ax.grid(True, alpha=0.3)
52
63
  ax.legend()
53
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
64
+ _apply_x_axis_format(ax, days)
54
65
  plt.xticks(rotation=45)
55
66
  plt.tight_layout()
56
67
  plt.savefig(output, dpi=150, bbox_inches='tight')
@@ -71,7 +82,7 @@ def generate_area_graph(df, output, days):
71
82
  ax.set_title(f'Barometric Pressure Area - Last {days} Days', fontsize=14, fontweight='bold')
72
83
  ax.grid(True, alpha=0.3)
73
84
  ax.legend()
74
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
85
+ _apply_x_axis_format(ax, days)
75
86
  plt.xticks(rotation=45)
76
87
  plt.tight_layout()
77
88
  plt.savefig(output, dpi=150, bbox_inches='tight')
@@ -160,7 +171,7 @@ def generate_rate_of_change(df, output, days):
160
171
  ax.set_ylabel('Pressure Change (hPa/hour)', fontsize=12)
161
172
  ax.set_title(f'Rate of Pressure Change - Last {days} Days', fontsize=14, fontweight='bold')
162
173
  ax.grid(True, alpha=0.3, axis='y')
163
- ax.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d %H:%M'))
174
+ _apply_x_axis_format(ax, days)
164
175
  plt.xticks(rotation=45)
165
176
 
166
177
  # Legend
@@ -187,8 +198,8 @@ def generate_dashboard(df, output, days):
187
198
  ax1.set_title('Pressure Over Time', fontweight='bold')
188
199
  ax1.set_ylabel('Pressure (hPa)')
189
200
  ax1.grid(True, alpha=0.3)
190
- ax1.xaxis.set_major_formatter(mdates.DateFormatter('%m/%d'))
191
-
201
+ ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M' if days == 1 else '%m/%d'))
202
+
192
203
  # 2. Distribution
193
204
  ax2 = fig.add_subplot(gs[1, 0])
194
205
  ax2.hist(df['pressure_hpa'], bins=20, color='#2E86AB', alpha=0.7, edgecolor='black')
@@ -249,9 +260,9 @@ def generate_dashboard(df, output, days):
249
260
 
250
261
  def apply_theme(theme: str):
251
262
  if theme == "dark":
252
- mpl_style(dark=True)
263
+ mpl_style(dark=True, minor_ticks=False)
253
264
  else:
254
- mpl_style(dark=False)
265
+ mpl_style(dark=False, minor_ticks=False)
255
266
 
256
267
 
257
268
  def generate_graph(days=7, output=None, graph_type='line',
@@ -6,6 +6,7 @@ import yaml
6
6
  import time
7
7
  import re
8
8
  import click
9
+ import fcntl
9
10
  from io import StringIO
10
11
  from datetime import datetime, timedelta
11
12
  import os
@@ -88,18 +89,27 @@ class BarometerScraper:
88
89
 
89
90
  def save_reading(self, pressure):
90
91
  # save pressure reading to CSV file
92
+ # locked so a concurrent scrape and background thread can't interleave writes
91
93
  data_file = get_data_dir() / 'readings.csv'
92
-
94
+ timestamp = datetime.now().isoformat()
95
+
93
96
  data = {
94
- 'timestamp': [datetime.now().isoformat()],
97
+ 'timestamp': [timestamp],
95
98
  'pressure_pa': [pressure],
96
99
  'pressure_hpa': [pressure / 100]
97
100
  }
98
-
99
101
  df = pandas.DataFrame(data)
100
- file_exists = data_file.exists()
101
- df.to_csv(data_file, mode='a', header=not file_exists, index=False)
102
-
102
+
103
+ with open(data_file, 'a', newline='') as f:
104
+ fcntl.flock(f.fileno(), fcntl.LOCK_EX)
105
+ try:
106
+ write_header = f.tell() == 0
107
+ df.to_csv(f, mode='a', header=write_header, index=False)
108
+ f.flush()
109
+ os.fsync(f.fileno())
110
+ finally:
111
+ fcntl.flock(f.fileno(), fcntl.LOCK_UN)
112
+
103
113
  return True
104
114
 
105
115
 
@@ -124,7 +134,7 @@ def cli(ctx, verbose):
124
134
  @cli.command()
125
135
  def version():
126
136
  """Show version information"""
127
- click.echo("spectrum-barometer version 2.2.2! (that was fast :3)")
137
+ click.echo("spectrum-barometer version 2.2.5! (that was fast :3)")
128
138
 
129
139
 
130
140
  @cli.command()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "spectrum-barometer"
7
- version = "2.2.2"
7
+ version = "2.2.5"
8
8
  description = "Monitor barometric pressure from Spectrum SAX2V1S routers"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spectrum-barometer
3
- Version: 2.2.2
3
+ Version: 2.2.5
4
4
  Summary: Monitor barometric pressure from Spectrum SAX2V1S routers
5
5
  Author-email: BobaTeagrl <Unsorted2750@proton.me>
6
6
  License: MIT
@@ -24,7 +24,7 @@ def dashboard():
24
24
  theme=theme,
25
25
  graph_ts=int(time()),
26
26
  monitor=monitor_info,
27
- can_control_monitor=monitor_info.get('owned_by_me', False) or not monitor_info.get('running', False),
27
+ can_control_monitor=True,
28
28
  graph_types=['line', 'smooth', 'area', 'daily', 'distribution', 'change', 'dashboard'],
29
29
  )
30
30
 
@@ -134,18 +134,11 @@ def monitor_start():
134
134
 
135
135
  @bp.route("/monitor/stop", methods=["POST"])
136
136
  def monitor_stop():
137
- from barometer.background import is_owned_by_current_process
138
-
139
- # check if we can stop it
140
- if not is_owned_by_current_process():
141
- flash("Cannot stop monitoring - it's running in a different process (e.g., CLI). Stop it from where you started it", "error")
142
- return redirect(url_for("main.dashboard"))
143
-
144
137
  result = stop_monitoring()
145
-
138
+
146
139
  if result['success']:
147
140
  flash(result['message'], "success")
148
141
  else:
149
142
  flash(result['message'], "error")
150
-
143
+
151
144
  return redirect(url_for("main.dashboard"))
@@ -275,7 +275,7 @@
275
275
  <nav>
276
276
  <a href="{{ url_for('main.dashboard') }}" >Dashboard</a>
277
277
  <a href="{{ url_for('main.stats') }}">Statistics</a>
278
- <a class="version" href="https://github.com/BobaTeagrl/spectrum-barometer">V 2.2.2 :3</a>
278
+ <a class="version" href="https://github.com/BobaTeagrl/spectrum-barometer">V 2.2.5 :3</a>
279
279
  </nav>
280
280
 
281
281
  <!-- Flash messages -->
@@ -69,7 +69,7 @@
69
69
  <nav>
70
70
  <a href="{{ url_for('main.dashboard') }}">Dashboard</a>
71
71
  <a href="{{ url_for('main.stats') }}">Statistics</a>
72
- <a class="version" href="https://github.com/BobaTeagrl/spectrum-barometer">V 2.2.2 :3</a>
72
+ <a class="version" href="https://github.com/BobaTeagrl/spectrum-barometer">V 2.2.5 :3</a>
73
73
 
74
74
  </nav>
75
75