diff --git a/.gitignore b/.gitignore
index 87cd9ca..595ef93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ listing.txt
__pycache__
build
*.crt
+venv
diff --git a/config.yml b/config.yml
index 56328e9..dc19e04 100644
--- a/config.yml
+++ b/config.yml
@@ -1,2 +1,2 @@
tool_directory: ./firmware/www/tool/
-app_config_directory: .
+app_config_directory: netoolclient/src/netoolclient/resources
diff --git a/extract.sh b/extract.sh
index 9f6f9e8..93911c4 100644
--- a/extract.sh
+++ b/extract.sh
@@ -16,8 +16,5 @@ mkdir -p $MOUNTPOINT
# Extract the files we need
7z x -o$MOUNTPOINT $IMAGE www/tool/ etc/*.crt -bsp1 -bso0 -bse0 -y
-echo "Extracting certificate..."
-cp $MOUNTPOINT/etc/*.crt ./apicert.crt
-
# Perform the diff and show only the differences
#diff --no-dereference -r $MOUNTPOINT
diff --git a/netoolclient/.gitignore b/netoolclient/.gitignore
new file mode 100644
index 0000000..f6c30e2
--- /dev/null
+++ b/netoolclient/.gitignore
@@ -0,0 +1,62 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# OSX useful to ignore
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.dist-info/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# IntelliJ Idea family of suites
+.idea
+*.iml
+## File-based project format:
+*.ipr
+*.iws
+## mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# Briefcase log files
+logs/
diff --git a/netoolclient/CHANGELOG b/netoolclient/CHANGELOG
new file mode 100644
index 0000000..7dab1af
--- /dev/null
+++ b/netoolclient/CHANGELOG
@@ -0,0 +1,5 @@
+# Netool Client Release Notes
+
+## 0.0.1 (31 May 2024)
+
+* Initial release
diff --git a/netoolclient/LICENSE b/netoolclient/LICENSE
new file mode 100644
index 0000000..c274506
--- /dev/null
+++ b/netoolclient/LICENSE
@@ -0,0 +1,14 @@
+Netool Client: A third-party cross-platform Netool companion app.
+Copyright (C) 2024 Amelia Deck
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of version 3 of the GNU General Public License as
+published by the Free Software Foundation.
+
+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 .
diff --git a/netoolclient/README.rst b/netoolclient/README.rst
new file mode 100644
index 0000000..f889de1
--- /dev/null
+++ b/netoolclient/README.rst
@@ -0,0 +1,8 @@
+Netool Client
+=============
+
+A third-party cross-platform Netool companion app.
+
+.. _`Briefcase`: https://briefcase.readthedocs.io/
+.. _`The BeeWare Project`: https://beeware.org/
+.. _`becoming a financial member of BeeWare`: https://beeware.org/contributing/membership
diff --git a/netoolclient/pyproject.toml b/netoolclient/pyproject.toml
new file mode 100644
index 0000000..f2d39c8
--- /dev/null
+++ b/netoolclient/pyproject.toml
@@ -0,0 +1,184 @@
+# This project was generated with 0.3.18 using template: https://github.com/beeware/briefcase-template@v0.3.18
+[tool.briefcase]
+project_name = "Netool Client"
+bundle = "sh.deck"
+version = "0.0.1"
+url = "https://git.deck.sh/shark/netool-newapp"
+license = "GNU General Public License v3 (GPLv3)"
+author = "Amelia Deck"
+author_email = "amelia@deck.sh"
+
+[tool.briefcase.app.netoolclient]
+formal_name = "Netool Client"
+description = "A third-party cross-platform Netool companion app."
+long_description = """More details about the app should go here.
+"""
+sources = [
+ "src/netoolclient",
+]
+test_sources = [
+ "tests",
+]
+
+requires = [
+ "httpx",
+]
+test_requires = [
+ "pytest",
+]
+
+[tool.briefcase.app.netoolclient.macOS]
+universal_build = true
+requires = [
+ "toga-cocoa~=0.4.0",
+ "std-nslog~=1.0.0",
+]
+
+[tool.briefcase.app.netoolclient.linux]
+requires = [
+ "toga-gtk~=0.4.0",
+]
+
+[tool.briefcase.app.netoolclient.linux.system.debian]
+system_requires = [
+ # Needed to compile pycairo wheel
+ "libcairo2-dev",
+ # Needed to compile PyGObject wheel
+ "libgirepository1.0-dev",
+]
+
+system_runtime_requires = [
+ # Needed to provide GTK and its GI bindings
+ "gir1.2-gtk-3.0",
+ "libgirepository-1.0-1",
+ # Dependencies that GTK looks for at runtime
+ "libcanberra-gtk3-module",
+ # Needed to provide WebKit2 at runtime
+ # Note: Debian 11 and Ubuntu 20.04 require gir1.2-webkit2-4.0 instead
+ # "gir1.2-webkit2-4.1",
+]
+
+[tool.briefcase.app.netoolclient.linux.system.rhel]
+system_requires = [
+ # Needed to compile pycairo wheel
+ "cairo-gobject-devel",
+ # Needed to compile PyGObject wheel
+ "gobject-introspection-devel",
+]
+
+system_runtime_requires = [
+ # Needed to support Python bindings to GTK
+ "gobject-introspection",
+ # Needed to provide GTK
+ "gtk3",
+ # Dependencies that GTK looks for at runtime
+ "libcanberra-gtk3",
+ # Needed to provide WebKit2 at runtime
+ # "webkit2gtk3",
+]
+
+[tool.briefcase.app.netoolclient.linux.system.suse]
+system_requires = [
+ # Needed to compile pycairo wheel
+ "cairo-devel",
+ # Needed to compile PyGObject wheel
+ "gobject-introspection-devel",
+]
+
+system_runtime_requires = [
+ # Needed to provide GTK
+ "gtk3",
+ # Needed to support Python bindings to GTK
+ "gobject-introspection", "typelib(Gtk) = 3.0",
+ # Dependencies that GTK looks for at runtime
+ "libcanberra-gtk3-module",
+ # Needed to provide WebKit2 at runtime
+ # "libwebkit2gtk3", "typelib(WebKit2)",
+]
+
+[tool.briefcase.app.netoolclient.linux.system.arch]
+system_requires = [
+ # Needed to compile pycairo wheel
+ "cairo",
+ # Needed to compile PyGObject wheel
+ "gobject-introspection",
+ # Runtime dependencies that need to exist so that the
+ # Arch package passes final validation.
+ # Needed to provide GTK
+ "gtk3",
+ # Dependencies that GTK looks for at runtime
+ "libcanberra",
+ # Needed to provide WebKit2
+ # "webkit2gtk",
+]
+
+system_runtime_requires = [
+ # Needed to provide GTK
+ "gtk3",
+ # Needed to provide PyGObject bindings
+ "gobject-introspection-runtime",
+ # Dependencies that GTK looks for at runtime
+ "libcanberra",
+ # Needed to provide WebKit2 at runtime
+ # "webkit2gtk",
+]
+
+[tool.briefcase.app.netoolclient.linux.appimage]
+manylinux = "manylinux_2_28"
+
+system_requires = [
+ # Needed to compile pycairo wheel
+ "cairo-gobject-devel",
+ # Needed to compile PyGObject wheel
+ "gobject-introspection-devel",
+ # Needed to provide GTK
+ "gtk3-devel",
+ # Dependencies that GTK looks for at runtime, that need to be
+ # in the build environment to be picked up by linuxdeploy
+ "libcanberra-gtk3",
+ "PackageKit-gtk3-module",
+ "gvfs-client",
+]
+
+linuxdeploy_plugins = [
+ "DEPLOY_GTK_VERSION=3 gtk",
+]
+
+[tool.briefcase.app.netoolclient.linux.flatpak]
+flatpak_runtime = "org.gnome.Platform"
+flatpak_runtime_version = "45"
+flatpak_sdk = "org.gnome.Sdk"
+
+[tool.briefcase.app.netoolclient.windows]
+requires = [
+ "toga-winforms~=0.4.0",
+]
+
+# Mobile deployments
+[tool.briefcase.app.netoolclient.iOS]
+requires = [
+ "toga-iOS~=0.4.0",
+ "std-nslog~=1.0.0",
+]
+
+[tool.briefcase.app.netoolclient.android]
+requires = [
+ "toga-android~=0.4.0",
+]
+
+base_theme = "Theme.MaterialComponents.Light.DarkActionBar"
+
+build_gradle_dependencies = [
+ "androidx.appcompat:appcompat:1.6.1",
+ "com.google.android.material:material:1.11.0",
+ # Needed for DetailedList
+ "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0",
+]
+
+# Web deployments
+[tool.briefcase.app.netoolclient.web]
+requires = [
+ "toga-web~=0.4.0",
+]
+style_framework = "Shoelace v2.3"
+
diff --git a/netoolclient/src/netoolclient/__init__.py b/netoolclient/src/netoolclient/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/netoolclient/src/netoolclient/__main__.py b/netoolclient/src/netoolclient/__main__.py
new file mode 100644
index 0000000..b14af94
--- /dev/null
+++ b/netoolclient/src/netoolclient/__main__.py
@@ -0,0 +1,4 @@
+from netoolclient.app import main
+
+if __name__ == "__main__":
+ main().main_loop()
diff --git a/netoolclient/src/netoolclient/app.py b/netoolclient/src/netoolclient/app.py
new file mode 100644
index 0000000..4407159
--- /dev/null
+++ b/netoolclient/src/netoolclient/app.py
@@ -0,0 +1,272 @@
+"""
+A third-party cross-platform Netool companion app.
+"""
+
+import toga
+from toga.style import Pack
+from toga.style.pack import COLUMN, LEFT, RIGHT, TOP, BOTTOM, CENTER, ROW, Pack
+from netoolclient.call_api import call_api_preloaded
+import asyncio
+import threading
+import json
+import socket
+
+def run_asyncio_event_loop():
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ loop.run_forever()
+
+class NetoolClient(toga.App):
+ ip = "192.168.49.1"
+ conntype = "eth" # or ble
+ connected = False
+ page = "Status"
+ lastpage = "None"
+ page_mode = "DetailedList" # Text, Switches, etc
+ refresh = True
+ def startup(self):
+ """Construct and show the Toga application.
+
+ Usually, you would add your application to a main content box.
+ We then create a main window (with a name matching the app), and
+ show the main window.
+ """
+ self.main_box = toga.Box(style=Pack(direction=COLUMN))
+
+
+ self.status_header = toga.Label(
+ "Connecting to Netool.IO device via WiFi...",
+ style=Pack(padding=(5, 5)),
+ )
+ self.page_box = toga.Box(style=Pack(direction=ROW, padding=(5,5)))
+ # self.page_header = toga.Label(
+ # "",
+ # style=Pack(padding=(5, 5)),
+ # )
+ self.page_selector = toga.Selection(items=["Status", "Discovery Packet (LLDP, CDP...) Details", "STP Details", "ARP Scan", "Traceroute Log", "NTP Status", "PCAP Status", "History", "WiFi Settings", "Ethernet Settings", "Discovery Timers", "Scan Toggle Switches", "About", ], style=Pack(flex=1), on_change=self.set_refresh)
+ self.page_box.add(self.page_selector)
+
+ self.page_loading = toga.ProgressBar(max=None, style=Pack(alignment=RIGHT, flex=1))
+
+ self.info_box = toga.Box(style=Pack(direction=COLUMN, flex=1))
+ # self.info_text = toga.Label(
+ # "",
+ # style=Pack(padding=(5, 5), flex=1),
+ # )
+ self.info_text = toga.MultilineTextInput(readonly=True, value="", style=Pack(padding=(5, 5), flex=1))
+ self.info_list = toga.DetailedList(data=[], style=Pack(padding=(5, 5), flex=1))
+ self.info_box.add(self.info_list)
+ # container = toga.ScrollContainer(content=self.info_box, style=Pack(flex=1), horizontal=False)
+
+ self.main_box.add(self.status_header)
+ # self.page_box.add(self.page_header)
+ self.main_box.add(self.page_loading)
+ self.main_box.add(self.page_box)
+
+ self.main_box.add(self.info_box)
+
+ if toga.platform.current_platform == "android":
+ self.page_refresh = toga.Button(text="Refresh", style=Pack(flex=5, alignment=CENTER), on_press=self.set_refresh)
+ self.auto_refresh = toga.Switch(text=None, style=Pack(padding_top=11, flex=1, alignment=CENTER))
+ else:
+ self.page_refresh = toga.Button(text="Refresh", style=Pack(flex=7, alignment=CENTER), on_press=self.set_refresh)
+ self.auto_refresh = toga.Switch(text="Auto", style=Pack(padding_top=4, padding_left=8, flex=1, alignment=CENTER))
+ self.refresh_box = toga.Box(style=Pack(direction=ROW, padding=(5,5)))
+ self.refresh_box.add(self.page_refresh)
+ self.refresh_box.add(self.auto_refresh)
+ self.main_box.add(self.refresh_box)
+
+ self.main_window = toga.MainWindow(title=self.formal_name)
+ self.main_window.content = self.main_box
+ self.main_window.show()
+
+ #toga.App.add_background_task(self, handler=self.check_online)
+
+ toga.App.add_background_task(self, handler=self.update_page)
+
+ def set_refresh(self, a):
+ self.refresh = True
+ async def check_online(self):
+ # Your periodic task logic here
+ if True or self.conntype == "eth":
+ result = await self.check_online_wifi()
+ if result:
+ text = "Connected to Netool.IO device."
+ self.connected = True
+ else:
+ text = "Connecting to Netool.IO device via WiFi..."
+ self.connected = False
+ #print(text)
+ # Schedule the GUI update on the main thread
+ if self.status_header.text != text:
+ print(text)
+ self.status_header.text = text
+ # Wait for 5 seconds before running again
+ return result
+
+ async def update_page(self, a):
+ counter = 0
+ while True:
+ self.page = self.page_selector.value
+ if self.connected:
+ if (self.refresh) and await self.check_online():
+ self.refresh = False
+ print("Switching to page " + self.page)
+ self.lastpage = self.page
+ #self.page_header.text = self.page
+ self.page_loading.start()
+ data = await self.get_info()
+ if self.page_mode == "Text":
+ self.info_text.value = str(data)
+ if self.page_mode == "DetailedList":
+ self.info_list.data = data
+ self.page_loading.stop()
+ else:
+ await self.check_online()
+ await asyncio.sleep(0.5)
+ if self.auto_refresh.value == True:
+ counter += 1
+ if counter >= 4:
+ counter = 0
+ self.refresh=True
+ else:
+ counter = 0
+
+
+ # render new page
+ else:
+ result = await self.check_online()
+ if not result:
+ await asyncio.sleep(0.25)
+
+ def set_info_mode(self, mode):
+ if mode != self.page_mode:
+ match self.page_mode:
+ case "DetailedList":
+ self.info_box.remove(self.info_list)
+ case "Text":
+ self.info_box.remove(self.info_text)
+ self.page_mode = mode
+ match mode:
+ case "DetailedList":
+ self.info_box.add(self.info_list)
+ case "Text":
+ self.info_box.add(self.info_text)
+
+ async def get_info(self):
+ match self.page:
+ case "Status":
+ call = "operations"
+ case "About":
+ call = "about"
+ case "History":
+ call = "history"
+ case "ARP Scan":
+ call = "arpscan"
+ case "Discovery Packet (LLDP, CDP...) Details":
+ call = "dp_detail"
+ case "STP Details":
+ call = "stp_detail"
+ case "NTP Status":
+ call = "ntp_status"
+ case "PCAP Status":
+ call = "sniff"
+ case "Discovery Timers":
+ call = "scan_time_status"
+ case "WiFi Settings":
+ call = "wifi_status"
+ case "Ethernet Settings":
+ call = "eth_status"
+ case "Scan Toggle Switches":
+ call = "toggle_settings_status"
+ case "Traceroute Log":
+ call = "traceroute_log"
+
+
+ case _:
+ call = None
+
+ if call is not None:
+ res = await self.call_api_safe(call)
+ while res is None:
+ res = await self.call_api_safe(call)
+ #return res
+
+ match self.page:
+ case "Status":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+
+ case "About":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+ case "History":
+ self.set_info_mode("DetailedList")
+ out = []
+ for index, entry in enumerate(res):
+ for key, val in entry.items():
+ out.append({"title": str(index)+" "+key, "subtitle": str(val).replace("\n", " ")})
+ print(out)
+ return out
+ case "ARP Scan":
+ self.set_info_mode("DetailedList")
+ return [{"title": dev["IP"], "subtitle": dev["MAC"]} for dev in res]
+ case "Discovery Packet (LLDP, CDP...) Details":
+ self.set_info_mode("Text")
+ return res.replace("
", "\n")
+ case "STP Details":
+ self.set_info_mode("Text")
+ return res.replace("
", "\n")
+ case "NTP Status":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("_", " ")} for key, val in res.items()]
+ case "PCAP Status":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+ case "Discovery Timers":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ") + " seconds"} for key, val in res.items()]
+ case "WiFi Settings":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+ case "Ethernet Settings":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+ case "Scan Toggle Switches":
+ self.set_info_mode("DetailedList")
+ return [{"title": key, "subtitle": str(val).replace("\n", " ")} for key, val in res.items()]
+ case "Traceroute Log":
+ self.set_info_mode("Text")
+ return res.replace("
", "\n")
+
+
+
+
+ async def call_api_safe(self, callname, method="auto", params={}, baseaddr="https://192.168.49.1/tool/"):
+ try:
+ response = await call_api_preloaded(callname, method, params, baseaddr, conntype=self.conntype, filepath=str(self.paths.app) + "/resources")
+ #print("Response Status Code:", response.status_code)
+ try:
+ #print("Response JSON:", json.dumps(response.json(), indent=2))
+ return response.json()
+ except ValueError:
+ #print("Response Text:", response.text)
+ return response.text
+ except Exception as e:
+ print("Error:", str(e))
+
+ return None
+
+
+ async def check_online_wifi(self):
+ # host = await async_ping(self.ip, count=1, timeout=0.25)
+ # return host.is_alive
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ result = sock.connect_ex((self.ip,443))
+ sock.close()
+ return result == 0
+
+
+def main():
+ return NetoolClient()
diff --git a/call_api.py b/netoolclient/src/netoolclient/call_api.py
similarity index 68%
rename from call_api.py
rename to netoolclient/src/netoolclient/call_api.py
index 636687e..33b61a4 100644
--- a/call_api.py
+++ b/netoolclient/src/netoolclient/call_api.py
@@ -1,7 +1,11 @@
-import requests
-from read_api_details import parse_csv_to_dict
+import httpx
+try:
+ from netoolclient.read_api_details import parse_csv_to_dict
+except:
+ from read_api_details import parse_csv_to_dict
from sys import platform
import subprocess
+import asyncio
def ping(host):
@@ -21,7 +25,7 @@ def ping(host):
-def call_api(api_url, method, params):
+async def call_api(api_url, method, params, filepath):
"""
Function to make an API call.
@@ -36,16 +40,20 @@ def call_api(api_url, method, params):
print("Calling API",api_url,"with method",method,"and parameters",params)
if method.upper() == 'POST':
- response = requests.post(api_url, data=params, verify='./apicert.crt', timeout=8)
+ # response = requests.post(api_url, data=params, verify=filepath + '/apicert.crt', timeout=8)
+ async with httpx.AsyncClient(verify=filepath + '/apicert.crt') as client:
+ response = await client.post(api_url, data=params, timeout=8)
elif method.upper() == 'GET':
- response = requests.get(api_url, params=params, verify='./apicert.crt', timeout=8)
+ # response = requests.get(api_url, params=params, verify=filepath + './apicert.crt', timeout=8)
+ async with httpx.AsyncClient(verify=filepath + '/apicert.crt') as client:
+ response = await client.get(api_url, params=params, timeout=8)
else:
raise ValueError("Method must be 'POST' or 'GET'")
return response
-def call_api_preloaded(callname, method="auto", params={}, baseaddr="https://192.168.49.1/tool/", conntype="eth"):
- details = parse_csv_to_dict("apidetails.csv")
+async def call_api_preloaded(callname, method="auto", params={}, baseaddr="https://192.168.49.1/tool/", conntype="eth", filepath="resources"):
+ details = parse_csv_to_dict(filepath + "/apidetails.csv")
if not callname.find(".php") > 0:
callname += ".php"
full_url = baseaddr + callname
@@ -90,8 +98,8 @@ def call_api_preloaded(callname, method="auto", params={}, baseaddr="https://192
for param in auto_include:
params[param[0]] = param[1]
- if not ping("192.168.49.1"):
- print("Connecting to netool...")
- while not ping("192.168.49.1"):
- pass
- return call_api(full_url, method, params)
\ No newline at end of file
+ # if not ping("192.168.49.1"):
+ # print("Connecting to netool...")
+ # while not ping("192.168.49.1"):
+ # pass
+ return await call_api(full_url, method, params, filepath)
\ No newline at end of file
diff --git a/interactive_api.py b/netoolclient/src/netoolclient/interactive_api.py
similarity index 87%
rename from interactive_api.py
rename to netoolclient/src/netoolclient/interactive_api.py
index fdf2480..30a2e38 100644
--- a/interactive_api.py
+++ b/netoolclient/src/netoolclient/interactive_api.py
@@ -1,9 +1,10 @@
from call_api import *
from read_api_details import parse_csv_to_dict
import json
+import asyncio
def main():
- details = parse_csv_to_dict("apidetails.csv")
+ details = parse_csv_to_dict("resources/apidetails.csv")
print(details.keys())
# Prompt the user for the API call
api_call = input("Enter the API call (e.g., about.php): ").strip()
@@ -24,7 +25,7 @@ def main():
# Call the API
try:
- response = call_api_preloaded(api_call, method, params)
+ response = asyncio.run(call_api_preloaded(api_call, method, params))
print("Response Status Code:", response.status_code)
try:
print("Response JSON:", json.dumps(response.json(), indent=2))
diff --git a/read_api_details.py b/netoolclient/src/netoolclient/read_api_details.py
similarity index 100%
rename from read_api_details.py
rename to netoolclient/src/netoolclient/read_api_details.py
diff --git a/netoolclient/src/netoolclient/resources/README b/netoolclient/src/netoolclient/resources/README
new file mode 100644
index 0000000..4ef2794
--- /dev/null
+++ b/netoolclient/src/netoolclient/resources/README
@@ -0,0 +1,2 @@
+Put any application resources (e.g., icons and resources) here;
+they can be referenced in code as "resources/filename".
diff --git a/netoolclient/tests/__init__.py b/netoolclient/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/netoolclient/tests/netoolclient.py b/netoolclient/tests/netoolclient.py
new file mode 100644
index 0000000..d59e9f3
--- /dev/null
+++ b/netoolclient/tests/netoolclient.py
@@ -0,0 +1,35 @@
+import os
+import sys
+import tempfile
+from pathlib import Path
+
+import pytest
+
+
+def run_tests():
+ project_path = Path(__file__).parent.parent
+ os.chdir(project_path)
+
+ # Determine any args to pass to pytest. If there aren't any,
+ # default to running the whole test suite.
+ args = sys.argv[1:]
+ if len(args) == 0:
+ args = ["tests"]
+
+ returncode = pytest.main(
+ [
+ # Turn up verbosity
+ "-vv",
+ # Disable color
+ "--color=no",
+ # Overwrite the cache directory to somewhere writable
+ "-o",
+ f"cache_dir={tempfile.gettempdir()}/.pytest_cache",
+ ] + args
+ )
+
+ print(f">>>>>>>>>> EXIT {returncode} <<<<<<<<<<")
+
+
+if __name__ == "__main__":
+ run_tests()
diff --git a/netoolclient/tests/test_app.py b/netoolclient/tests/test_app.py
new file mode 100644
index 0000000..e1a335f
--- /dev/null
+++ b/netoolclient/tests/test_app.py
@@ -0,0 +1,3 @@
+def test_first():
+ """An initial test for the app."""
+ assert 1 + 1 == 2
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..fc9bf90
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+# requirements for GUI app
+# not needed for core API
+briefcase
+httpx
+icmplib
\ No newline at end of file
diff --git a/setup-api.sh b/setup-api.sh
index 4a6dc56..7736621 100644
--- a/setup-api.sh
+++ b/setup-api.sh
@@ -2,7 +2,7 @@
set -euo pipefail
-if (! [ -e apidetails.csv ]) || (! [ -e apicert.crt ]); then
+if (! [ -e netoolclient/src/netoolclient/resources/apidetails.csv ]) || (! [ -e netoolclient/src/netoolclient/resources/apicert.crt ]); then
echo "Downloading firmware for device..."
./download-fw.sh
@@ -12,8 +12,9 @@ if (! [ -e apidetails.csv ]) || (! [ -e apicert.crt ]); then
DIR=./firmware/www/tool/
echo "tool_directory: $DIR
-app_config_directory: ." > config.yml
-
+app_config_directory: netoolclient/src/netoolclient/resources" > config.yml
+ echo "Extracting certificate..."
+ cp ./firmware/etc/*.crt netoolclient/src/netoolclient/resources/apicert.crt
echo "Extracting API keys..."
python get_codes.py > /dev/null
echo "Cleaning up..."
@@ -21,4 +22,4 @@ app_config_directory: ." > config.yml
echo "API client is setup."
else
echo "Already setup."
-fi
\ No newline at end of file
+fi