aprsd 1.0.0__py3-none-any.whl → 3.4.2__py3-none-any.whl
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.
- aprsd/__init__.py +6 -4
- aprsd/cli_helper.py +151 -0
- aprsd/client/__init__.py +13 -0
- aprsd/client/aprsis.py +132 -0
- aprsd/client/base.py +105 -0
- aprsd/client/drivers/__init__.py +0 -0
- aprsd/client/drivers/aprsis.py +228 -0
- aprsd/client/drivers/fake.py +73 -0
- aprsd/client/drivers/kiss.py +119 -0
- aprsd/client/factory.py +88 -0
- aprsd/client/fake.py +48 -0
- aprsd/client/kiss.py +103 -0
- aprsd/client/stats.py +38 -0
- aprsd/cmds/__init__.py +0 -0
- aprsd/cmds/completion.py +22 -0
- aprsd/cmds/dev.py +162 -0
- aprsd/cmds/fetch_stats.py +156 -0
- aprsd/cmds/healthcheck.py +86 -0
- aprsd/cmds/list_plugins.py +319 -0
- aprsd/cmds/listen.py +231 -0
- aprsd/cmds/send_message.py +171 -0
- aprsd/cmds/server.py +137 -0
- aprsd/cmds/webchat.py +674 -0
- aprsd/conf/__init__.py +56 -0
- aprsd/conf/client.py +131 -0
- aprsd/conf/common.py +301 -0
- aprsd/conf/log.py +65 -0
- aprsd/conf/opts.py +80 -0
- aprsd/conf/plugin_common.py +182 -0
- aprsd/conf/plugin_email.py +105 -0
- aprsd/exception.py +13 -0
- aprsd/log/__init__.py +0 -0
- aprsd/log/log.py +138 -0
- aprsd/main.py +104 -867
- aprsd/packets/__init__.py +20 -0
- aprsd/packets/collector.py +79 -0
- aprsd/packets/core.py +823 -0
- aprsd/packets/log.py +161 -0
- aprsd/packets/packet_list.py +110 -0
- aprsd/packets/seen_list.py +49 -0
- aprsd/packets/tracker.py +103 -0
- aprsd/packets/watch_list.py +119 -0
- aprsd/plugin.py +474 -284
- aprsd/plugin_utils.py +86 -0
- aprsd/plugins/__init__.py +0 -0
- aprsd/plugins/email.py +709 -0
- aprsd/plugins/fortune.py +61 -0
- aprsd/plugins/location.py +179 -0
- aprsd/plugins/notify.py +61 -0
- aprsd/plugins/ping.py +31 -0
- aprsd/plugins/time.py +115 -0
- aprsd/plugins/version.py +31 -0
- aprsd/plugins/weather.py +405 -0
- aprsd/stats/__init__.py +20 -0
- aprsd/stats/app.py +49 -0
- aprsd/stats/collector.py +37 -0
- aprsd/threads/__init__.py +11 -0
- aprsd/threads/aprsd.py +119 -0
- aprsd/threads/keep_alive.py +131 -0
- aprsd/threads/log_monitor.py +121 -0
- aprsd/threads/registry.py +56 -0
- aprsd/threads/rx.py +354 -0
- aprsd/threads/stats.py +44 -0
- aprsd/threads/tx.py +255 -0
- aprsd/utils/__init__.py +218 -0
- aprsd/utils/counter.py +51 -0
- aprsd/utils/json.py +80 -0
- aprsd/utils/objectstore.py +123 -0
- aprsd/utils/ring_buffer.py +40 -0
- aprsd/utils/trace.py +180 -0
- aprsd/web/__init__.py +0 -0
- aprsd/web/admin/__init__.py +0 -0
- aprsd/web/admin/static/css/index.css +84 -0
- aprsd/web/admin/static/css/prism.css +4 -0
- aprsd/web/admin/static/css/tabs.css +35 -0
- aprsd/web/admin/static/images/Untitled.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/admin/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/admin/static/js/charts.js +235 -0
- aprsd/web/admin/static/js/echarts.js +465 -0
- aprsd/web/admin/static/js/logs.js +26 -0
- aprsd/web/admin/static/js/main.js +231 -0
- aprsd/web/admin/static/js/prism.js +12 -0
- aprsd/web/admin/static/js/send-message.js +114 -0
- aprsd/web/admin/static/js/tabs.js +28 -0
- aprsd/web/admin/templates/index.html +196 -0
- aprsd/web/chat/static/css/chat.css +115 -0
- aprsd/web/chat/static/css/index.css +66 -0
- aprsd/web/chat/static/css/style.css.map +1 -0
- aprsd/web/chat/static/css/tabs.css +41 -0
- aprsd/web/chat/static/css/upstream/bootstrap.min.css +6 -0
- aprsd/web/chat/static/css/upstream/font.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/google-fonts.css +23 -0
- aprsd/web/chat/static/css/upstream/jquery-ui.css +1311 -0
- aprsd/web/chat/static/css/upstream/jquery.toast.css +28 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Bold.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/LatoLatin-Regular.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/icons.woff2 +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff +0 -0
- aprsd/web/chat/static/css/upstream/themes/default/assets/fonts/outline-icons.woff2 +0 -0
- aprsd/web/chat/static/images/Untitled.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-16-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-0.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-1.png +0 -0
- aprsd/web/chat/static/images/aprs-symbols-64-2.png +0 -0
- aprsd/web/chat/static/images/globe.svg +3 -0
- aprsd/web/chat/static/js/gps.js +84 -0
- aprsd/web/chat/static/js/main.js +45 -0
- aprsd/web/chat/static/js/send-message.js +585 -0
- aprsd/web/chat/static/js/tabs.js +28 -0
- aprsd/web/chat/static/js/upstream/bootstrap.bundle.min.js +7 -0
- aprsd/web/chat/static/js/upstream/jquery-3.7.1.min.js +2 -0
- aprsd/web/chat/static/js/upstream/jquery-ui.min.js +13 -0
- aprsd/web/chat/static/js/upstream/jquery.toast.js +374 -0
- aprsd/web/chat/static/js/upstream/semantic.min.js +11 -0
- aprsd/web/chat/static/js/upstream/socket.io.min.js +7 -0
- aprsd/web/chat/templates/index.html +139 -0
- aprsd/wsgi.py +315 -0
- aprsd-3.4.2.dist-info/AUTHORS +13 -0
- aprsd-3.4.2.dist-info/LICENSE +175 -0
- aprsd-3.4.2.dist-info/METADATA +793 -0
- aprsd-3.4.2.dist-info/RECORD +133 -0
- {aprsd-1.0.0.dist-info → aprsd-3.4.2.dist-info}/WHEEL +1 -1
- aprsd-3.4.2.dist-info/entry_points.txt +8 -0
- aprsd/fake_aprs.py +0 -83
- aprsd/utils.py +0 -166
- aprsd-1.0.0.dist-info/AUTHORS +0 -6
- aprsd-1.0.0.dist-info/METADATA +0 -181
- aprsd-1.0.0.dist-info/RECORD +0 -13
- aprsd-1.0.0.dist-info/entry_points.txt +0 -4
- aprsd-1.0.0.dist-info/pbr.json +0 -1
- /aprsd/{fuzzyclock.py → utils/fuzzyclock.py} +0 -0
- {aprsd-1.0.0.dist-info → aprsd-3.4.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
3
|
+
import pathlib
|
4
|
+
import pickle
|
5
|
+
import threading
|
6
|
+
|
7
|
+
from oslo_config import cfg
|
8
|
+
|
9
|
+
|
10
|
+
CONF = cfg.CONF
|
11
|
+
LOG = logging.getLogger("APRSD")
|
12
|
+
|
13
|
+
|
14
|
+
class ObjectStoreMixin:
|
15
|
+
"""Class 'MIXIN' intended to save/load object data.
|
16
|
+
|
17
|
+
The asumption of how this mixin is used:
|
18
|
+
The using class has to have a:
|
19
|
+
* data in self.data as a dictionary
|
20
|
+
* a self.lock thread lock
|
21
|
+
* Class must specify self.save_file as the location.
|
22
|
+
|
23
|
+
|
24
|
+
When APRSD quits, it calls save()
|
25
|
+
When APRSD Starts, it calls load()
|
26
|
+
aprsd server -f (flush) will wipe all saved objects.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(self):
|
30
|
+
self.lock = threading.RLock()
|
31
|
+
|
32
|
+
def __len__(self):
|
33
|
+
with self.lock:
|
34
|
+
return len(self.data)
|
35
|
+
|
36
|
+
def __iter__(self):
|
37
|
+
with self.lock:
|
38
|
+
return iter(self.data)
|
39
|
+
|
40
|
+
def get_all(self):
|
41
|
+
with self.lock:
|
42
|
+
return self.data
|
43
|
+
|
44
|
+
def get(self, key):
|
45
|
+
with self.lock:
|
46
|
+
return self.data.get(key)
|
47
|
+
|
48
|
+
def copy(self):
|
49
|
+
with self.lock:
|
50
|
+
return self.data.copy()
|
51
|
+
|
52
|
+
def _init_store(self):
|
53
|
+
if not CONF.enable_save:
|
54
|
+
return
|
55
|
+
sl = CONF.save_location
|
56
|
+
if not os.path.exists(sl):
|
57
|
+
LOG.warning(f"Save location {sl} doesn't exist")
|
58
|
+
try:
|
59
|
+
os.makedirs(sl)
|
60
|
+
except Exception as ex:
|
61
|
+
LOG.exception(ex)
|
62
|
+
|
63
|
+
def _save_filename(self):
|
64
|
+
save_location = CONF.save_location
|
65
|
+
|
66
|
+
return "{}/{}.p".format(
|
67
|
+
save_location,
|
68
|
+
self.__class__.__name__.lower(),
|
69
|
+
)
|
70
|
+
|
71
|
+
def save(self):
|
72
|
+
"""Save any queued to disk?"""
|
73
|
+
if not CONF.enable_save:
|
74
|
+
return
|
75
|
+
self._init_store()
|
76
|
+
save_filename = self._save_filename()
|
77
|
+
if len(self) > 0:
|
78
|
+
LOG.info(
|
79
|
+
f"{self.__class__.__name__}::Saving"
|
80
|
+
f" {len(self)} entries to disk at "
|
81
|
+
f"{save_filename}",
|
82
|
+
)
|
83
|
+
with self.lock:
|
84
|
+
with open(save_filename, "wb+") as fp:
|
85
|
+
pickle.dump(self.data, fp)
|
86
|
+
else:
|
87
|
+
LOG.debug(
|
88
|
+
"{} Nothing to save, flushing old save file '{}'".format(
|
89
|
+
self.__class__.__name__,
|
90
|
+
save_filename,
|
91
|
+
),
|
92
|
+
)
|
93
|
+
self.flush()
|
94
|
+
|
95
|
+
def load(self):
|
96
|
+
if not CONF.enable_save:
|
97
|
+
return
|
98
|
+
if os.path.exists(self._save_filename()):
|
99
|
+
try:
|
100
|
+
with open(self._save_filename(), "rb") as fp:
|
101
|
+
raw = pickle.load(fp)
|
102
|
+
if raw:
|
103
|
+
self.data = raw
|
104
|
+
LOG.debug(
|
105
|
+
f"{self.__class__.__name__}::Loaded {len(self)} entries from disk.",
|
106
|
+
)
|
107
|
+
else:
|
108
|
+
LOG.debug(f"{self.__class__.__name__}::No data to load.")
|
109
|
+
except (pickle.UnpicklingError, Exception) as ex:
|
110
|
+
LOG.error(f"Failed to UnPickle {self._save_filename()}")
|
111
|
+
LOG.error(ex)
|
112
|
+
self.data = {}
|
113
|
+
else:
|
114
|
+
LOG.debug(f"{self.__class__.__name__}::No save file found.")
|
115
|
+
|
116
|
+
def flush(self):
|
117
|
+
"""Nuke the old pickle file that stored the old results from last aprsd run."""
|
118
|
+
if not CONF.enable_save:
|
119
|
+
return
|
120
|
+
if os.path.exists(self._save_filename()):
|
121
|
+
pathlib.Path(self._save_filename()).unlink()
|
122
|
+
with self.lock:
|
123
|
+
self.data = {}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class RingBuffer:
|
2
|
+
"""class that implements a not-yet-full buffer"""
|
3
|
+
|
4
|
+
max: int = 100
|
5
|
+
data: list = []
|
6
|
+
|
7
|
+
def __init__(self, size_max):
|
8
|
+
self.max = size_max
|
9
|
+
self.data = []
|
10
|
+
|
11
|
+
class __Full:
|
12
|
+
"""class that implements a full buffer"""
|
13
|
+
|
14
|
+
def append(self, x):
|
15
|
+
"""Append an element overwriting the oldest one."""
|
16
|
+
self.data[self.cur] = x
|
17
|
+
self.cur = (self.cur + 1) % self.max
|
18
|
+
|
19
|
+
def get(self):
|
20
|
+
"""return list of elements in correct order"""
|
21
|
+
return self.data[self.cur :] + self.data[: self.cur]
|
22
|
+
|
23
|
+
def __len__(self):
|
24
|
+
return len(self.data)
|
25
|
+
|
26
|
+
def append(self, x):
|
27
|
+
"""append an element at the end of the buffer"""
|
28
|
+
|
29
|
+
self.data.append(x)
|
30
|
+
if len(self.data) == self.max:
|
31
|
+
self.cur = 0
|
32
|
+
# Permanently change self's class from non-full to full
|
33
|
+
self.__class__ = self.__Full
|
34
|
+
|
35
|
+
def get(self):
|
36
|
+
"""Return a list of elements from the oldest to the newest."""
|
37
|
+
return self.data
|
38
|
+
|
39
|
+
def __len__(self):
|
40
|
+
return len(self.data)
|
aprsd/utils/trace.py
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
import abc
|
2
|
+
import functools
|
3
|
+
import inspect
|
4
|
+
import logging
|
5
|
+
import time
|
6
|
+
import types
|
7
|
+
|
8
|
+
|
9
|
+
VALID_TRACE_FLAGS = {"method", "api"}
|
10
|
+
TRACE_API = False
|
11
|
+
TRACE_METHOD = False
|
12
|
+
TRACE_ENABLED = False
|
13
|
+
LOG = logging.getLogger("APRSD")
|
14
|
+
|
15
|
+
|
16
|
+
def trace(*dec_args, **dec_kwargs):
|
17
|
+
"""Trace calls to the decorated function.
|
18
|
+
|
19
|
+
This decorator should always be defined as the outermost decorator so it
|
20
|
+
is defined last. This is important so it does not interfere
|
21
|
+
with other decorators.
|
22
|
+
|
23
|
+
Using this decorator on a function will cause its execution to be logged at
|
24
|
+
`DEBUG` level with arguments, return values, and exceptions.
|
25
|
+
|
26
|
+
:returns: a function decorator
|
27
|
+
"""
|
28
|
+
|
29
|
+
def _decorator(f):
|
30
|
+
|
31
|
+
func_name = f.__name__
|
32
|
+
|
33
|
+
@functools.wraps(f)
|
34
|
+
def trace_logging_wrapper(*args, **kwargs):
|
35
|
+
filter_function = dec_kwargs.get("filter_function")
|
36
|
+
logger = LOG
|
37
|
+
|
38
|
+
# NOTE(ameade): Don't bother going any further if DEBUG log level
|
39
|
+
# is not enabled for the logger.
|
40
|
+
if not logger.isEnabledFor(logging.DEBUG) or not TRACE_ENABLED:
|
41
|
+
return f(*args, **kwargs)
|
42
|
+
|
43
|
+
all_args = inspect.getcallargs(f, *args, **kwargs)
|
44
|
+
|
45
|
+
pass_filter = filter_function is None or filter_function(all_args)
|
46
|
+
|
47
|
+
if pass_filter:
|
48
|
+
logger.debug(
|
49
|
+
"==> %(func)s: call %(all_args)r",
|
50
|
+
{
|
51
|
+
"func": func_name,
|
52
|
+
"all_args": str(all_args),
|
53
|
+
},
|
54
|
+
)
|
55
|
+
|
56
|
+
start_time = time.time() * 1000
|
57
|
+
try:
|
58
|
+
result = f(*args, **kwargs)
|
59
|
+
except Exception as exc:
|
60
|
+
total_time = int(round(time.time() * 1000)) - start_time
|
61
|
+
logger.debug(
|
62
|
+
"<== %(func)s: exception (%(time)dms) %(exc)r",
|
63
|
+
{
|
64
|
+
"func": func_name,
|
65
|
+
"time": total_time,
|
66
|
+
"exc": exc,
|
67
|
+
},
|
68
|
+
)
|
69
|
+
raise
|
70
|
+
total_time = int(round(time.time() * 1000)) - start_time
|
71
|
+
|
72
|
+
if isinstance(result, dict):
|
73
|
+
mask_result = result
|
74
|
+
elif isinstance(result, str):
|
75
|
+
mask_result = result
|
76
|
+
else:
|
77
|
+
mask_result = result
|
78
|
+
|
79
|
+
if pass_filter:
|
80
|
+
logger.debug(
|
81
|
+
"<== %(func)s: return (%(time)dms) %(result)r",
|
82
|
+
{
|
83
|
+
"func": func_name,
|
84
|
+
"time": total_time,
|
85
|
+
"result": mask_result,
|
86
|
+
},
|
87
|
+
)
|
88
|
+
return result
|
89
|
+
|
90
|
+
return trace_logging_wrapper
|
91
|
+
|
92
|
+
if len(dec_args) == 0:
|
93
|
+
# filter_function is passed and args does not contain f
|
94
|
+
return _decorator
|
95
|
+
else:
|
96
|
+
# filter_function is not passed
|
97
|
+
return _decorator(dec_args[0])
|
98
|
+
|
99
|
+
|
100
|
+
def trace_api(*dec_args, **dec_kwargs):
|
101
|
+
"""Decorates a function if TRACE_API is true."""
|
102
|
+
|
103
|
+
def _decorator(f):
|
104
|
+
@functools.wraps(f)
|
105
|
+
def trace_api_logging_wrapper(*args, **kwargs):
|
106
|
+
if TRACE_API:
|
107
|
+
return trace(f, *dec_args, **dec_kwargs)(*args, **kwargs)
|
108
|
+
return f(*args, **kwargs)
|
109
|
+
|
110
|
+
return trace_api_logging_wrapper
|
111
|
+
|
112
|
+
if len(dec_args) == 0:
|
113
|
+
# filter_function is passed and args does not contain f
|
114
|
+
return _decorator
|
115
|
+
else:
|
116
|
+
# filter_function is not passed
|
117
|
+
return _decorator(dec_args[0])
|
118
|
+
|
119
|
+
|
120
|
+
def trace_method(f):
|
121
|
+
"""Decorates a function if TRACE_METHOD is true."""
|
122
|
+
|
123
|
+
@functools.wraps(f)
|
124
|
+
def trace_method_logging_wrapper(*args, **kwargs):
|
125
|
+
if TRACE_METHOD:
|
126
|
+
return trace(f)(*args, **kwargs)
|
127
|
+
return f(*args, **kwargs)
|
128
|
+
|
129
|
+
return trace_method_logging_wrapper
|
130
|
+
|
131
|
+
|
132
|
+
class TraceWrapperMetaclass(type):
|
133
|
+
"""Metaclass that wraps all methods of a class with trace_method.
|
134
|
+
|
135
|
+
This metaclass will cause every function inside of the class to be
|
136
|
+
decorated with the trace_method decorator.
|
137
|
+
|
138
|
+
To use the metaclass you define a class like so:
|
139
|
+
class MyClass(object, metaclass=utils.TraceWrapperMetaclass):
|
140
|
+
"""
|
141
|
+
|
142
|
+
def __new__(cls, classname, bases, class_dict):
|
143
|
+
new_class_dict = {}
|
144
|
+
for attribute_name, attribute in class_dict.items():
|
145
|
+
if isinstance(attribute, types.FunctionType):
|
146
|
+
# replace it with a wrapped version
|
147
|
+
attribute = functools.update_wrapper(
|
148
|
+
trace_method(attribute),
|
149
|
+
attribute,
|
150
|
+
)
|
151
|
+
new_class_dict[attribute_name] = attribute
|
152
|
+
|
153
|
+
return type.__new__(cls, classname, bases, new_class_dict)
|
154
|
+
|
155
|
+
|
156
|
+
class TraceWrapperWithABCMetaclass(abc.ABCMeta, TraceWrapperMetaclass):
|
157
|
+
"""Metaclass that wraps all methods of a class with trace."""
|
158
|
+
|
159
|
+
|
160
|
+
def setup_tracing(trace_flags):
|
161
|
+
"""Set global variables for each trace flag.
|
162
|
+
|
163
|
+
Sets variables TRACE_METHOD and TRACE_API, which represent
|
164
|
+
whether to log methods or api traces.
|
165
|
+
|
166
|
+
:param trace_flags: a list of strings
|
167
|
+
"""
|
168
|
+
global TRACE_METHOD
|
169
|
+
global TRACE_API
|
170
|
+
global TRACE_ENABLED
|
171
|
+
|
172
|
+
try:
|
173
|
+
trace_flags = [flag.strip() for flag in trace_flags]
|
174
|
+
except TypeError: # Handle when trace_flags is None or a test mock
|
175
|
+
trace_flags = []
|
176
|
+
for invalid_flag in set(trace_flags) - VALID_TRACE_FLAGS:
|
177
|
+
LOG.warning("Invalid trace flag: %s", invalid_flag)
|
178
|
+
TRACE_METHOD = "method" in trace_flags
|
179
|
+
TRACE_API = "api" in trace_flags
|
180
|
+
TRACE_ENABLED = True
|
aprsd/web/__init__.py
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
body {
|
2
|
+
background: #eeeeee;
|
3
|
+
margin: 2em;
|
4
|
+
text-align: center;
|
5
|
+
font-family: system-ui, sans-serif;
|
6
|
+
}
|
7
|
+
|
8
|
+
footer {
|
9
|
+
padding: 2em;
|
10
|
+
text-align: center;
|
11
|
+
height: 10vh;
|
12
|
+
}
|
13
|
+
|
14
|
+
.ui.segment {
|
15
|
+
background: #eeeeee;
|
16
|
+
}
|
17
|
+
|
18
|
+
#graphs {
|
19
|
+
display: grid;
|
20
|
+
width: 100%;
|
21
|
+
height: 300px;
|
22
|
+
grid-template-columns: 1fr 1fr;
|
23
|
+
}
|
24
|
+
#graphs_center {
|
25
|
+
display: block;
|
26
|
+
margin-top: 10px;
|
27
|
+
margin-bottom: 10px;
|
28
|
+
width: 100%;
|
29
|
+
height: 300px;
|
30
|
+
}
|
31
|
+
#left {
|
32
|
+
margin-right: 2px;
|
33
|
+
height: 300px;
|
34
|
+
}
|
35
|
+
#right {
|
36
|
+
height: 300px;
|
37
|
+
}
|
38
|
+
#center {
|
39
|
+
height: 300px;
|
40
|
+
}
|
41
|
+
#packetsChart, #messageChart, #emailChart, #memChart {
|
42
|
+
border: 1px solid #ccc;
|
43
|
+
background: #ddd;
|
44
|
+
}
|
45
|
+
#stats {
|
46
|
+
margin: auto;
|
47
|
+
width: 80%;
|
48
|
+
}
|
49
|
+
#jsonstats {
|
50
|
+
display: none;
|
51
|
+
}
|
52
|
+
#title {
|
53
|
+
font-size: 4em;
|
54
|
+
}
|
55
|
+
#version{
|
56
|
+
font-size: .5em;
|
57
|
+
}
|
58
|
+
#uptime, #aprsis {
|
59
|
+
font-size: 1em;
|
60
|
+
}
|
61
|
+
#callsign {
|
62
|
+
font-size: 1.4em;
|
63
|
+
color: #00F;
|
64
|
+
padding-top: 8px;
|
65
|
+
margin:10px;
|
66
|
+
}
|
67
|
+
|
68
|
+
#title_rx {
|
69
|
+
background-color: darkseagreen;
|
70
|
+
text-align: left;
|
71
|
+
}
|
72
|
+
|
73
|
+
#title_tx {
|
74
|
+
background-color: lightcoral;
|
75
|
+
text-align: left;
|
76
|
+
}
|
77
|
+
|
78
|
+
.aprsd_1 {
|
79
|
+
background-image: url(/static/images/aprs-symbols-16-0.png);
|
80
|
+
background-repeat: no-repeat;
|
81
|
+
background-position: -160px -48px;
|
82
|
+
width: 16px;
|
83
|
+
height: 16px;
|
84
|
+
}
|
@@ -0,0 +1,4 @@
|
|
1
|
+
/* PrismJS 1.29.0
|
2
|
+
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+json+json5+log&plugins=show-language+toolbar */
|
3
|
+
code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green}
|
4
|
+
div.code-toolbar{position:relative}div.code-toolbar>.toolbar{position:absolute;z-index:10;top:.3em;right:.2em;transition:opacity .3s ease-in-out;opacity:0}div.code-toolbar:hover>.toolbar{opacity:1}div.code-toolbar:focus-within>.toolbar{opacity:1}div.code-toolbar>.toolbar>.toolbar-item{display:inline-block}div.code-toolbar>.toolbar>.toolbar-item>a{cursor:pointer}div.code-toolbar>.toolbar>.toolbar-item>button{background:0 0;border:0;color:inherit;font:inherit;line-height:normal;overflow:visible;padding:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none}div.code-toolbar>.toolbar>.toolbar-item>a,div.code-toolbar>.toolbar>.toolbar-item>button,div.code-toolbar>.toolbar>.toolbar-item>span{color:#bbb;font-size:.8em;padding:0 .5em;background:#f5f2f0;background:rgba(224,224,224,.2);box-shadow:0 2px 0 0 rgba(0,0,0,.2);border-radius:.5em}div.code-toolbar>.toolbar>.toolbar-item>a:focus,div.code-toolbar>.toolbar>.toolbar-item>a:hover,div.code-toolbar>.toolbar>.toolbar-item>button:focus,div.code-toolbar>.toolbar>.toolbar-item>button:hover,div.code-toolbar>.toolbar>.toolbar-item>span:focus,div.code-toolbar>.toolbar>.toolbar-item>span:hover{color:inherit;text-decoration:none}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
/* Style the tab */
|
2
|
+
.tab {
|
3
|
+
overflow: hidden;
|
4
|
+
border: 1px solid #ccc;
|
5
|
+
background-color: #f1f1f1;
|
6
|
+
}
|
7
|
+
|
8
|
+
/* Style the buttons that are used to open the tab content */
|
9
|
+
.tab button {
|
10
|
+
background-color: inherit;
|
11
|
+
float: left;
|
12
|
+
border: none;
|
13
|
+
outline: none;
|
14
|
+
cursor: pointer;
|
15
|
+
padding: 14px 16px;
|
16
|
+
transition: 0.3s;
|
17
|
+
}
|
18
|
+
|
19
|
+
/* Change background color of buttons on hover */
|
20
|
+
.tab button:hover {
|
21
|
+
background-color: #ddd;
|
22
|
+
}
|
23
|
+
|
24
|
+
/* Create an active/current tablink class */
|
25
|
+
.tab button.active {
|
26
|
+
background-color: #ccc;
|
27
|
+
}
|
28
|
+
|
29
|
+
/* Style the tab content */
|
30
|
+
.tabcontent {
|
31
|
+
display: none;
|
32
|
+
padding: 6px 12px;
|
33
|
+
border: 1px solid #ccc;
|
34
|
+
border-top: none;
|
35
|
+
}
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|