citrascope 0.3.0__tar.gz → 0.4.0__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.
- {citrascope-0.3.0 → citrascope-0.4.0}/.github/copilot-instructions.md +2 -3
- {citrascope-0.3.0 → citrascope-0.4.0}/.gitignore +0 -3
- {citrascope-0.3.0 → citrascope-0.4.0}/.vscode/launch.json +4 -13
- {citrascope-0.3.0 → citrascope-0.4.0}/PKG-INFO +40 -32
- {citrascope-0.3.0 → citrascope-0.4.0}/README.md +38 -23
- citrascope-0.4.0/citrascope/__main__.py +23 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/api/citra_api_client.py +13 -1
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/citra_scope_daemon.py +10 -19
- citrascope-0.4.0/citrascope/constants.py +23 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/nina_adv_http_adapter.py +2 -2
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/logging/web_log_handler.py +9 -8
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/settings/citrascope_settings.py +14 -19
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/tasks/runner.py +6 -2
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/app.py +17 -8
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/server.py +10 -1
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/app.js +3 -1
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/config.js +80 -4
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/style.css +32 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/templates/dashboard.html +53 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/pyproject.toml +3 -10
- citrascope-0.3.0/citrascope/__main__.py +0 -26
- citrascope-0.3.0/docs/index.md +0 -47
- {citrascope-0.3.0 → citrascope-0.4.0}/.devcontainer/devcontainer.json +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.flake8 +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.github/dependabot.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.github/workflows/pypi-publish.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.github/workflows/pytest.yml +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.pre-commit-config.yaml +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/.python-version +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/api/abstract_api_client.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/abstract_astro_hardware_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/adapter_registry.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/indi_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/kstars_dbus_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/nina_adv_http_survey_template.json +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/logging/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/logging/_citrascope_logger.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/settings/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/settings/settings_file_manager.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/tasks/scope/base_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/tasks/scope/static_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/tasks/scope/tracking_telescope_task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/tasks/task.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/__init__.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/api.js +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/img/citra.png +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/img/favicon.png +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/citrascope/web/static/websocket.js +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/tests/unit/test_api_client.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/tests/unit/test_hardware_adapter.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/tests/unit/test_task_manager.py +0 -0
- {citrascope-0.3.0 → citrascope-0.4.0}/tests/unit/utils.py +0 -0
|
@@ -25,7 +25,6 @@ This project is a Python package for interacting with astronomical data and serv
|
|
|
25
25
|
- `citrascope/web/templates/`: HTML templates
|
|
26
26
|
- `citrascope/web/static/`: CSS and JavaScript files
|
|
27
27
|
- `tests/`: Unit and integration tests
|
|
28
|
-
- `docs/`: Project documentation
|
|
29
28
|
|
|
30
29
|
## Testing
|
|
31
30
|
- All new features and bug fixes should include corresponding tests in `tests/`.
|
|
@@ -77,7 +76,7 @@ The web interface provides real-time monitoring and configuration for telescope
|
|
|
77
76
|
|
|
78
77
|
This project relies on several key Python packages. Below are some of the most important ones and their roles:
|
|
79
78
|
|
|
80
|
-
- **Click**: Used for building the command-line interface (CLI). The main entry point for the application (`python -m citrascope
|
|
79
|
+
- **Click**: Used for building the command-line interface (CLI). The main entry point for the application (`python -m citrascope`) is implemented using Click.
|
|
81
80
|
- **Pydantic-Settings**: Manages configuration and settings, ensuring type safety and validation for environment variables.
|
|
82
81
|
- **Requests** and **HTTPX**: Handle HTTP requests for interacting with the Citra.space API.
|
|
83
82
|
- **Python-Dateutil**: Provides robust date and time parsing utilities.
|
|
@@ -102,5 +101,5 @@ For a complete list of dependencies, refer to the `pyproject.toml` file.
|
|
|
102
101
|
|
|
103
102
|
## Additional Notes
|
|
104
103
|
- Keep dependencies minimal and update `pyproject.toml` as needed.
|
|
105
|
-
-
|
|
104
|
+
- Documentation is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository under `docs/citrascope/`.
|
|
106
105
|
- Use pre-commit hooks for code quality.
|
|
@@ -4,29 +4,20 @@
|
|
|
4
4
|
"version": "0.2.0",
|
|
5
5
|
"configurations": [
|
|
6
6
|
{
|
|
7
|
-
"name": "Python: citrascope
|
|
7
|
+
"name": "Python: citrascope",
|
|
8
8
|
"type": "debugpy",
|
|
9
9
|
"request": "launch",
|
|
10
10
|
"module": "citrascope",
|
|
11
|
-
"args": [
|
|
11
|
+
"args": [],
|
|
12
12
|
"console": "integratedTerminal",
|
|
13
13
|
"justMyCode": false
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
"name": "Python: citrascope
|
|
16
|
+
"name": "Python: citrascope (custom port)",
|
|
17
17
|
"type": "debugpy",
|
|
18
18
|
"request": "launch",
|
|
19
19
|
"module": "citrascope",
|
|
20
|
-
"args": ["--
|
|
21
|
-
"console": "integratedTerminal",
|
|
22
|
-
"justMyCode": false
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"name": "Python: citrascope dev start DEBUG logging",
|
|
26
|
-
"type": "debugpy",
|
|
27
|
-
"request": "launch",
|
|
28
|
-
"module": "citrascope",
|
|
29
|
-
"args": ["--dev", "--log-level", "DEBUG", "start"],
|
|
20
|
+
"args": ["--web-port", "8080"],
|
|
30
21
|
"console": "integratedTerminal",
|
|
31
22
|
"justMyCode": false
|
|
32
23
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: citrascope
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Remotely control a telescope while it polls for tasks, collects and edge processes data, and delivers results and data for further processing.
|
|
5
5
|
Author-email: Patrick McDavid <patrick@citra.space>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,13 +9,9 @@ Requires-Dist: click
|
|
|
9
9
|
Requires-Dist: fastapi>=0.104.0
|
|
10
10
|
Requires-Dist: httpx
|
|
11
11
|
Requires-Dist: platformdirs>=4.0.0
|
|
12
|
-
Requires-Dist: pydantic-settings
|
|
13
|
-
Requires-Dist: pytest-cov
|
|
14
12
|
Requires-Dist: python-dateutil
|
|
15
|
-
Requires-Dist: python-json-logger
|
|
16
13
|
Requires-Dist: requests
|
|
17
14
|
Requires-Dist: skyfield
|
|
18
|
-
Requires-Dist: types-python-dateutil
|
|
19
15
|
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
20
16
|
Requires-Dist: websockets>=12.0
|
|
21
17
|
Provides-Extra: all
|
|
@@ -38,10 +34,7 @@ Requires-Dist: mypy; extra == 'dev'
|
|
|
38
34
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
39
35
|
Requires-Dist: pytest; extra == 'dev'
|
|
40
36
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
41
|
-
|
|
42
|
-
Requires-Dist: sphinx; extra == 'docs'
|
|
43
|
-
Requires-Dist: sphinx-autodoc-typehints; extra == 'docs'
|
|
44
|
-
Requires-Dist: sphinx-markdown-builder; extra == 'docs'
|
|
37
|
+
Requires-Dist: types-python-dateutil; extra == 'dev'
|
|
45
38
|
Provides-Extra: indi
|
|
46
39
|
Requires-Dist: pixelemon; extra == 'indi'
|
|
47
40
|
Requires-Dist: plotly; extra == 'indi'
|
|
@@ -65,33 +58,46 @@ Remotely control a telescope while it polls for tasks, collects observations, an
|
|
|
65
58
|
- Connects to configured telescope and camera hardware
|
|
66
59
|
- Acts as a task daemon carrying out and remitting photography tasks
|
|
67
60
|
|
|
61
|
+
## Documentation
|
|
62
|
+
|
|
63
|
+
Full documentation is available at [docs.citra.space](https://docs.citra.space/citrascope/).
|
|
64
|
+
|
|
65
|
+
Documentation source is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository.
|
|
66
|
+
|
|
68
67
|
## Installation
|
|
69
68
|
|
|
70
|
-
|
|
69
|
+
**Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
### Check Your Python Version
|
|
73
72
|
|
|
74
73
|
```sh
|
|
75
|
-
|
|
74
|
+
python3 --version
|
|
76
75
|
```
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
|
|
79
78
|
|
|
80
|
-
|
|
79
|
+
```sh
|
|
80
|
+
pyenv install 3.12.0
|
|
81
|
+
pyenv local 3.12.0 # Sets Python 3.12.0 for the current directory
|
|
82
|
+
```
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
```sh
|
|
84
|
-
pipx install citrascope[indi]
|
|
85
|
-
# or with pip: pip install citrascope[indi]
|
|
86
|
-
```
|
|
84
|
+
### Install CitraScope
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
```sh
|
|
90
|
-
pipx install citrascope[all]
|
|
91
|
-
# or with pip: pip install citrascope[all]
|
|
92
|
-
```
|
|
86
|
+
**Recommended: Using pip in a virtual environment**
|
|
93
87
|
|
|
94
|
-
|
|
88
|
+
```sh
|
|
89
|
+
python3 -m venv citrascope-env
|
|
90
|
+
source citrascope-env/bin/activate # On Windows: citrascope-env\Scripts\activate
|
|
91
|
+
pip install citrascope
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Optional Dependencies
|
|
95
|
+
|
|
96
|
+
For Linux-based telescope control (INDI):
|
|
97
|
+
|
|
98
|
+
```sh
|
|
99
|
+
pip install citrascope[indi]
|
|
100
|
+
```
|
|
95
101
|
|
|
96
102
|
This provides the `citrascope` command-line tool. To see available commands:
|
|
97
103
|
|
|
@@ -101,16 +107,18 @@ citrascope --help
|
|
|
101
107
|
|
|
102
108
|
## Usage
|
|
103
109
|
|
|
104
|
-
|
|
110
|
+
### Starting the Daemon
|
|
111
|
+
|
|
112
|
+
Run the daemon with:
|
|
105
113
|
|
|
106
114
|
```sh
|
|
107
|
-
citrascope
|
|
115
|
+
citrascope
|
|
108
116
|
```
|
|
109
117
|
|
|
110
|
-
|
|
118
|
+
By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
|
|
111
119
|
|
|
112
120
|
```sh
|
|
113
|
-
citrascope
|
|
121
|
+
citrascope --web-port 8080
|
|
114
122
|
```
|
|
115
123
|
|
|
116
124
|
## Developer Setup
|
|
@@ -174,10 +182,10 @@ Then create a release in the GitHub UI from the new tag. This triggers automatic
|
|
|
174
182
|
|
|
175
183
|
If you are using Visual Studio Code, you can run or debug the project directly using the pre-configured launch options in `.vscode/launch.json`:
|
|
176
184
|
|
|
177
|
-
- **Python: citrascope
|
|
178
|
-
- **Python: citrascope
|
|
185
|
+
- **Python: citrascope** — Runs the daemon with default settings
|
|
186
|
+
- **Python: citrascope (custom port)** — Runs with web interface on port 8080
|
|
179
187
|
|
|
180
|
-
To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
|
|
188
|
+
To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
|
|
181
189
|
|
|
182
190
|
## Running Tests
|
|
183
191
|
|
|
@@ -9,33 +9,46 @@ Remotely control a telescope while it polls for tasks, collects observations, an
|
|
|
9
9
|
- Connects to configured telescope and camera hardware
|
|
10
10
|
- Acts as a task daemon carrying out and remitting photography tasks
|
|
11
11
|
|
|
12
|
+
## Documentation
|
|
13
|
+
|
|
14
|
+
Full documentation is available at [docs.citra.space](https://docs.citra.space/citrascope/).
|
|
15
|
+
|
|
16
|
+
Documentation source is maintained in the [citra-space/docs](https://github.com/citra-space/docs) repository.
|
|
17
|
+
|
|
12
18
|
## Installation
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
**Important:** CitraScope requires Python 3.10, 3.11, or 3.12.
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
### Check Your Python Version
|
|
17
23
|
|
|
18
24
|
```sh
|
|
19
|
-
|
|
25
|
+
python3 --version
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
If you don't have a compatible version, install one with [pyenv](https://github.com/pyenv/pyenv):
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
```sh
|
|
31
|
+
pyenv install 3.12.0
|
|
32
|
+
pyenv local 3.12.0 # Sets Python 3.12.0 for the current directory
|
|
33
|
+
```
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
```sh
|
|
28
|
-
pipx install citrascope[indi]
|
|
29
|
-
# or with pip: pip install citrascope[indi]
|
|
30
|
-
```
|
|
35
|
+
### Install CitraScope
|
|
31
36
|
|
|
32
|
-
|
|
33
|
-
```sh
|
|
34
|
-
pipx install citrascope[all]
|
|
35
|
-
# or with pip: pip install citrascope[all]
|
|
36
|
-
```
|
|
37
|
+
**Recommended: Using pip in a virtual environment**
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
```sh
|
|
40
|
+
python3 -m venv citrascope-env
|
|
41
|
+
source citrascope-env/bin/activate # On Windows: citrascope-env\Scripts\activate
|
|
42
|
+
pip install citrascope
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Optional Dependencies
|
|
46
|
+
|
|
47
|
+
For Linux-based telescope control (INDI):
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
pip install citrascope[indi]
|
|
51
|
+
```
|
|
39
52
|
|
|
40
53
|
This provides the `citrascope` command-line tool. To see available commands:
|
|
41
54
|
|
|
@@ -45,16 +58,18 @@ citrascope --help
|
|
|
45
58
|
|
|
46
59
|
## Usage
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
### Starting the Daemon
|
|
62
|
+
|
|
63
|
+
Run the daemon with:
|
|
49
64
|
|
|
50
65
|
```sh
|
|
51
|
-
citrascope
|
|
66
|
+
citrascope
|
|
52
67
|
```
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
By default, this starts the web interface on `http://localhost:24872`. You can customize the port:
|
|
55
70
|
|
|
56
71
|
```sh
|
|
57
|
-
citrascope
|
|
72
|
+
citrascope --web-port 8080
|
|
58
73
|
```
|
|
59
74
|
|
|
60
75
|
## Developer Setup
|
|
@@ -118,10 +133,10 @@ Then create a release in the GitHub UI from the new tag. This triggers automatic
|
|
|
118
133
|
|
|
119
134
|
If you are using Visual Studio Code, you can run or debug the project directly using the pre-configured launch options in `.vscode/launch.json`:
|
|
120
135
|
|
|
121
|
-
- **Python: citrascope
|
|
122
|
-
- **Python: citrascope
|
|
136
|
+
- **Python: citrascope** — Runs the daemon with default settings
|
|
137
|
+
- **Python: citrascope (custom port)** — Runs with web interface on port 8080
|
|
123
138
|
|
|
124
|
-
To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
|
|
139
|
+
To use these, open the Run and Debug panel in VS Code, select the desired configuration, and click the Run or Debug button.
|
|
125
140
|
|
|
126
141
|
## Running Tests
|
|
127
142
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from citrascope.citra_scope_daemon import CitraScopeDaemon
|
|
4
|
+
from citrascope.constants import DEFAULT_WEB_PORT
|
|
5
|
+
from citrascope.settings.citrascope_settings import CitraScopeSettings
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click.option(
|
|
10
|
+
"--web-port",
|
|
11
|
+
default=DEFAULT_WEB_PORT,
|
|
12
|
+
type=int,
|
|
13
|
+
help=f"Web server port (default: {DEFAULT_WEB_PORT})",
|
|
14
|
+
)
|
|
15
|
+
def cli(web_port):
|
|
16
|
+
"""CitraScope daemon - configure via web UI at http://localhost:24872"""
|
|
17
|
+
settings = CitraScopeSettings(web_port=web_port)
|
|
18
|
+
daemon = CitraScopeDaemon(settings)
|
|
19
|
+
daemon.run()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
if __name__ == "__main__":
|
|
23
|
+
cli()
|
|
@@ -41,7 +41,19 @@ class CitraApiClient(AbstractCitraApiClient):
|
|
|
41
41
|
return resp.json()
|
|
42
42
|
except httpx.HTTPStatusError as e:
|
|
43
43
|
if self.logger:
|
|
44
|
-
|
|
44
|
+
# Check if response is HTML (e.g., Cloudflare error pages)
|
|
45
|
+
content_type = e.response.headers.get("content-type", "")
|
|
46
|
+
response_text = e.response.text
|
|
47
|
+
|
|
48
|
+
if "text/html" in content_type or response_text.strip().startswith("<"):
|
|
49
|
+
# Log only status and a brief message for HTML responses, sometimes we get Cloudflare error pages
|
|
50
|
+
self.logger.error(
|
|
51
|
+
f"HTTP error: {e.response.status_code} - "
|
|
52
|
+
f"Received HTML error page (likely Cloudflare or server error) for {method} {endpoint}"
|
|
53
|
+
)
|
|
54
|
+
else:
|
|
55
|
+
# Log full response for non-HTML errors (JSON, plain text, etc.)
|
|
56
|
+
self.logger.error(f"HTTP error: {e.response.status_code} {response_text}")
|
|
45
57
|
return None
|
|
46
58
|
except Exception as e:
|
|
47
59
|
if self.logger:
|
|
@@ -17,9 +17,6 @@ class CitraScopeDaemon:
|
|
|
17
17
|
settings: CitraScopeSettings,
|
|
18
18
|
api_client: Optional[AbstractCitraApiClient] = None,
|
|
19
19
|
hardware_adapter: Optional[AbstractAstroHardwareAdapter] = None,
|
|
20
|
-
enable_web: bool = True,
|
|
21
|
-
web_host: str = "0.0.0.0",
|
|
22
|
-
web_port: int = 24872,
|
|
23
20
|
):
|
|
24
21
|
self.settings = settings
|
|
25
22
|
CITRASCOPE_LOGGER.setLevel(self.settings.log_level)
|
|
@@ -33,16 +30,14 @@ class CitraScopeDaemon:
|
|
|
33
30
|
|
|
34
31
|
self.api_client = api_client
|
|
35
32
|
self.hardware_adapter = hardware_adapter
|
|
36
|
-
self.enable_web = enable_web
|
|
37
33
|
self.web_server = None
|
|
38
34
|
self.task_manager = None
|
|
39
35
|
self.ground_station = None
|
|
40
36
|
self.telescope_record = None
|
|
41
37
|
self.configuration_error: Optional[str] = None
|
|
42
38
|
|
|
43
|
-
# Create web server instance
|
|
44
|
-
|
|
45
|
-
self.web_server = CitraScopeWebServer(daemon=self, host=web_host, port=web_port)
|
|
39
|
+
# Create web server instance (always enabled)
|
|
40
|
+
self.web_server = CitraScopeWebServer(daemon=self, host="0.0.0.0", port=self.settings.web_port)
|
|
46
41
|
|
|
47
42
|
def _create_hardware_adapter(self) -> AbstractAstroHardwareAdapter:
|
|
48
43
|
"""Factory method to create the appropriate hardware adapter based on settings."""
|
|
@@ -73,12 +68,8 @@ class CitraScopeDaemon:
|
|
|
73
68
|
try:
|
|
74
69
|
if reload_settings:
|
|
75
70
|
CITRASCOPE_LOGGER.info("Reloading configuration...")
|
|
76
|
-
# Reload settings from file
|
|
77
|
-
new_settings = CitraScopeSettings(
|
|
78
|
-
dev=("dev.api" in self.settings.host),
|
|
79
|
-
log_level=self.settings.log_level,
|
|
80
|
-
keep_images=self.settings.keep_images,
|
|
81
|
-
)
|
|
71
|
+
# Reload settings from file (preserving web_port)
|
|
72
|
+
new_settings = CitraScopeSettings(web_port=self.settings.web_port)
|
|
82
73
|
self.settings = new_settings
|
|
83
74
|
CITRASCOPE_LOGGER.setLevel(self.settings.log_level)
|
|
84
75
|
|
|
@@ -206,11 +197,10 @@ class CitraScopeDaemon:
|
|
|
206
197
|
return False, error_msg
|
|
207
198
|
|
|
208
199
|
def run(self):
|
|
209
|
-
# Start web server FIRST
|
|
200
|
+
# Start web server FIRST, so users can monitor/configure
|
|
210
201
|
# The web interface will remain available even if configuration is incomplete
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
CITRASCOPE_LOGGER.info(f"Web interface available at http://{self.web_server.host}:{self.web_server.port}")
|
|
202
|
+
self.web_server.start()
|
|
203
|
+
CITRASCOPE_LOGGER.info(f"Web interface available at http://{self.web_server.host}:{self.web_server.port}")
|
|
214
204
|
|
|
215
205
|
try:
|
|
216
206
|
# Try to initialize components
|
|
@@ -238,6 +228,7 @@ class CitraScopeDaemon:
|
|
|
238
228
|
"""Clean up resources on shutdown."""
|
|
239
229
|
if self.task_manager:
|
|
240
230
|
self.task_manager.stop()
|
|
241
|
-
if self.
|
|
231
|
+
if self.web_server:
|
|
242
232
|
CITRASCOPE_LOGGER.info("Stopping web server...")
|
|
243
|
-
self.web_server.
|
|
233
|
+
if self.web_server.web_log_handler:
|
|
234
|
+
CITRASCOPE_LOGGER.removeHandler(self.web_server.web_log_handler)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Application-wide constants for CitraScope.
|
|
2
|
+
|
|
3
|
+
This module contains all shared constants used across different parts of the application.
|
|
4
|
+
Centralizing these values prevents duplication and circular import issues.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# ============================================================================
|
|
8
|
+
# API ENDPOINTS
|
|
9
|
+
# ============================================================================
|
|
10
|
+
PROD_API_HOST = "api.citra.space"
|
|
11
|
+
DEV_API_HOST = "dev.api.citra.space"
|
|
12
|
+
DEFAULT_API_PORT = 443
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# WEB APP URLs
|
|
16
|
+
# ============================================================================
|
|
17
|
+
PROD_APP_URL = "https://app.citra.space"
|
|
18
|
+
DEV_APP_URL = "https://dev.app.citra.space"
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# WEB SERVER
|
|
22
|
+
# ============================================================================
|
|
23
|
+
DEFAULT_WEB_PORT = 24872 # "CITRA" on phone keypad
|
|
@@ -422,8 +422,8 @@ class NinaAdvancedHttpAdapter(AbstractAstroHardwareAdapter):
|
|
|
422
422
|
|
|
423
423
|
nina_sequence_name = f"Citra Target: {satellite_data['name']}, Task: {task_id}"
|
|
424
424
|
|
|
425
|
-
# Replace basic placeholders
|
|
426
|
-
tle_data = f"{elset['tle'][0]}\n{elset['tle'][1]}"
|
|
425
|
+
# Replace basic placeholders (use \r\n for Windows NINA compatibility)
|
|
426
|
+
tle_data = f"{elset['tle'][0]}\r\n{elset['tle'][1]}"
|
|
427
427
|
sequence_json["Name"] = nina_sequence_name
|
|
428
428
|
|
|
429
429
|
# Navigate to the TLE container (ID 20 in the template)
|
|
@@ -23,14 +23,15 @@ class WebLogHandler(logging.Handler):
|
|
|
23
23
|
def emit(self, record):
|
|
24
24
|
"""Emit a log record."""
|
|
25
25
|
try:
|
|
26
|
-
# Filter out web-related logs from the web UI
|
|
27
|
-
#
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
# Filter out routine web-related logs from the web UI
|
|
27
|
+
# but keep ERROR and CRITICAL level messages
|
|
28
|
+
if record.levelno < logging.ERROR:
|
|
29
|
+
if (
|
|
30
|
+
record.name.startswith("uvicorn")
|
|
31
|
+
or "WebSocket" in record.getMessage()
|
|
32
|
+
or "HTTP Request:" in record.getMessage()
|
|
33
|
+
):
|
|
34
|
+
return
|
|
34
35
|
|
|
35
36
|
# Get the original levelname without color codes
|
|
36
37
|
# Record.levelname might have ANSI codes from ColoredFormatter
|
|
@@ -10,6 +10,7 @@ import platformdirs
|
|
|
10
10
|
APP_NAME = "citrascope"
|
|
11
11
|
APP_AUTHOR = "citra-space"
|
|
12
12
|
|
|
13
|
+
from citrascope.constants import DEFAULT_API_PORT, DEFAULT_WEB_PORT, PROD_API_HOST
|
|
13
14
|
from citrascope.logging import CITRASCOPE_LOGGER
|
|
14
15
|
from citrascope.settings.settings_file_manager import SettingsFileManager
|
|
15
16
|
|
|
@@ -17,18 +18,11 @@ from citrascope.settings.settings_file_manager import SettingsFileManager
|
|
|
17
18
|
class CitraScopeSettings:
|
|
18
19
|
"""Settings for CitraScope loaded from JSON configuration file."""
|
|
19
20
|
|
|
20
|
-
def __init__(
|
|
21
|
-
self,
|
|
22
|
-
dev: bool = False,
|
|
23
|
-
log_level: str = "INFO",
|
|
24
|
-
keep_images: bool = False,
|
|
25
|
-
):
|
|
21
|
+
def __init__(self, web_port: int = DEFAULT_WEB_PORT):
|
|
26
22
|
"""Initialize settings from JSON config file.
|
|
27
23
|
|
|
28
24
|
Args:
|
|
29
|
-
|
|
30
|
-
log_level: Logging level (DEBUG, INFO, WARNING, ERROR)
|
|
31
|
-
keep_images: If True, preserve captured images
|
|
25
|
+
web_port: Port for web interface (default: 24872) - bootstrap option only
|
|
32
26
|
"""
|
|
33
27
|
self.config_manager = SettingsFileManager()
|
|
34
28
|
|
|
@@ -38,9 +32,9 @@ class CitraScopeSettings:
|
|
|
38
32
|
# Application data directories
|
|
39
33
|
self._images_dir = Path(platformdirs.user_data_dir(APP_NAME, appauthor=APP_AUTHOR)) / "images"
|
|
40
34
|
|
|
41
|
-
# API Settings
|
|
42
|
-
self.host: str = config.get("host",
|
|
43
|
-
self.port: int = config.get("port",
|
|
35
|
+
# API Settings (all loaded from config file)
|
|
36
|
+
self.host: str = config.get("host", PROD_API_HOST)
|
|
37
|
+
self.port: int = config.get("port", DEFAULT_API_PORT)
|
|
44
38
|
self.use_ssl: bool = config.get("use_ssl", True)
|
|
45
39
|
self.personal_access_token: str = config.get("personal_access_token", "")
|
|
46
40
|
self.telescope_id: str = config.get("telescope_id", "")
|
|
@@ -51,9 +45,12 @@ class CitraScopeSettings:
|
|
|
51
45
|
# Hardware adapter-specific settings stored as dict
|
|
52
46
|
self.adapter_settings: Dict[str, Any] = config.get("adapter_settings", {})
|
|
53
47
|
|
|
54
|
-
# Runtime settings (
|
|
55
|
-
self.log_level: str =
|
|
56
|
-
self.keep_images: bool =
|
|
48
|
+
# Runtime settings (all loaded from config file, configurable via web UI)
|
|
49
|
+
self.log_level: str = config.get("log_level", "INFO")
|
|
50
|
+
self.keep_images: bool = config.get("keep_images", False)
|
|
51
|
+
|
|
52
|
+
# Web port: CLI override if non-default, otherwise use config file
|
|
53
|
+
self.web_port: int = web_port if web_port != DEFAULT_WEB_PORT else config.get("web_port", DEFAULT_WEB_PORT)
|
|
57
54
|
|
|
58
55
|
# Task retry configuration
|
|
59
56
|
self.max_task_retries: int = config.get("max_task_retries", 3)
|
|
@@ -64,10 +61,6 @@ class CitraScopeSettings:
|
|
|
64
61
|
self.file_logging_enabled: bool = config.get("file_logging_enabled", True)
|
|
65
62
|
self.log_retention_days: int = config.get("log_retention_days", 30)
|
|
66
63
|
|
|
67
|
-
if dev:
|
|
68
|
-
self.host = "dev.api.citra.space"
|
|
69
|
-
CITRASCOPE_LOGGER.info("Using development API endpoint.")
|
|
70
|
-
|
|
71
64
|
def get_images_dir(self) -> Path:
|
|
72
65
|
"""Get the path to the images directory.
|
|
73
66
|
|
|
@@ -105,6 +98,7 @@ class CitraScopeSettings:
|
|
|
105
98
|
"adapter_settings": self.adapter_settings,
|
|
106
99
|
"log_level": self.log_level,
|
|
107
100
|
"keep_images": self.keep_images,
|
|
101
|
+
"web_port": self.web_port,
|
|
108
102
|
"max_task_retries": self.max_task_retries,
|
|
109
103
|
"initial_retry_delay_seconds": self.initial_retry_delay_seconds,
|
|
110
104
|
"max_retry_delay_seconds": self.max_retry_delay_seconds,
|
|
@@ -137,6 +131,7 @@ class CitraScopeSettings:
|
|
|
137
131
|
settings.adapter_settings = config.get("adapter_settings", {})
|
|
138
132
|
settings.log_level = config.get("log_level", "INFO")
|
|
139
133
|
settings.keep_images = config.get("keep_images", False)
|
|
134
|
+
settings.web_port = config.get("web_port", DEFAULT_WEB_PORT)
|
|
140
135
|
settings.max_task_retries = config.get("max_task_retries", 3)
|
|
141
136
|
settings.initial_retry_delay_seconds = config.get("initial_retry_delay_seconds", 30)
|
|
142
137
|
settings.max_retry_delay_seconds = config.get("max_retry_delay_seconds", 300)
|
|
@@ -11,6 +11,9 @@ from citrascope.tasks.scope.static_telescope_task import StaticTelescopeTask
|
|
|
11
11
|
from citrascope.tasks.scope.tracking_telescope_task import TrackingTelescopeTask
|
|
12
12
|
from citrascope.tasks.task import Task
|
|
13
13
|
|
|
14
|
+
# Task polling interval in seconds
|
|
15
|
+
TASK_POLL_INTERVAL_SECONDS = 15
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
class TaskManager:
|
|
16
19
|
def __init__(
|
|
@@ -44,8 +47,9 @@ class TaskManager:
|
|
|
44
47
|
self._report_online()
|
|
45
48
|
tasks = self.api_client.get_telescope_tasks(self.telescope_record["id"])
|
|
46
49
|
|
|
47
|
-
# If API call failed (timeout, network error, etc.),
|
|
50
|
+
# If API call failed (timeout, network error, etc.), wait before retrying
|
|
48
51
|
if tasks is None:
|
|
52
|
+
self._stop_event.wait(TASK_POLL_INTERVAL_SECONDS)
|
|
49
53
|
continue
|
|
50
54
|
|
|
51
55
|
added = 0
|
|
@@ -113,7 +117,7 @@ class TaskManager:
|
|
|
113
117
|
except Exception as e:
|
|
114
118
|
self.logger.error(f"Exception in poll_tasks loop: {e}", exc_info=True)
|
|
115
119
|
time.sleep(5) # avoid tight error loop
|
|
116
|
-
self._stop_event.wait(
|
|
120
|
+
self._stop_event.wait(TASK_POLL_INTERVAL_SECONDS)
|
|
117
121
|
|
|
118
122
|
def _report_online(self):
|
|
119
123
|
"""
|
|
@@ -4,6 +4,7 @@ import asyncio
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any, Dict, List, Optional
|
|
9
10
|
|
|
@@ -13,15 +14,14 @@ from fastapi.responses import HTMLResponse, JSONResponse
|
|
|
13
14
|
from fastapi.staticfiles import StaticFiles
|
|
14
15
|
from pydantic import BaseModel
|
|
15
16
|
|
|
17
|
+
from citrascope.constants import (
|
|
18
|
+
DEV_API_HOST,
|
|
19
|
+
DEV_APP_URL,
|
|
20
|
+
PROD_API_HOST,
|
|
21
|
+
PROD_APP_URL,
|
|
22
|
+
)
|
|
16
23
|
from citrascope.logging import CITRASCOPE_LOGGER
|
|
17
24
|
|
|
18
|
-
# ============================================================================
|
|
19
|
-
# URL CONSTANTS - Single source of truth for Citra web URLs
|
|
20
|
-
# ============================================================================
|
|
21
|
-
PROD_APP_URL = "https://app.citra.space"
|
|
22
|
-
DEV_APP_URL = "https://dev.app.citra.space"
|
|
23
|
-
# ============================================================================
|
|
24
|
-
|
|
25
25
|
|
|
26
26
|
class SystemStatus(BaseModel):
|
|
27
27
|
"""Current system status."""
|
|
@@ -155,7 +155,7 @@ class CitraScopeWebApp:
|
|
|
155
155
|
|
|
156
156
|
settings = self.daemon.settings
|
|
157
157
|
# Determine app URL based on API host
|
|
158
|
-
app_url = DEV_APP_URL if
|
|
158
|
+
app_url = DEV_APP_URL if settings.host == DEV_API_HOST else PROD_APP_URL
|
|
159
159
|
|
|
160
160
|
# Get config file path
|
|
161
161
|
config_path = str(settings.config_manager.get_config_path())
|
|
@@ -198,6 +198,15 @@ class CitraScopeWebApp:
|
|
|
198
198
|
"error": getattr(self.daemon, "configuration_error", None),
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
@self.app.get("/api/version")
|
|
202
|
+
async def get_version():
|
|
203
|
+
"""Get CitraScope version."""
|
|
204
|
+
try:
|
|
205
|
+
pkg_version = version("citrascope")
|
|
206
|
+
return {"version": pkg_version}
|
|
207
|
+
except PackageNotFoundError:
|
|
208
|
+
return {"version": "development"}
|
|
209
|
+
|
|
201
210
|
@self.app.get("/api/hardware-adapters")
|
|
202
211
|
async def get_hardware_adapters():
|
|
203
212
|
"""Get list of available hardware adapters."""
|
|
@@ -7,13 +7,14 @@ import time
|
|
|
7
7
|
|
|
8
8
|
import uvicorn
|
|
9
9
|
|
|
10
|
+
from citrascope.constants import DEFAULT_WEB_PORT
|
|
10
11
|
from citrascope.logging import CITRASCOPE_LOGGER, WebLogHandler
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class CitraScopeWebServer:
|
|
14
15
|
"""Manages the web server and its configuration."""
|
|
15
16
|
|
|
16
|
-
def __init__(self, daemon, host: str = "0.0.0.0", port: int =
|
|
17
|
+
def __init__(self, daemon, host: str = "0.0.0.0", port: int = DEFAULT_WEB_PORT):
|
|
17
18
|
self.daemon = daemon
|
|
18
19
|
self.host = host
|
|
19
20
|
self.port = port
|
|
@@ -101,6 +102,14 @@ class CitraScopeWebServer:
|
|
|
101
102
|
asyncio.create_task(self._status_broadcast_loop())
|
|
102
103
|
|
|
103
104
|
await server.serve()
|
|
105
|
+
except OSError as e:
|
|
106
|
+
if e.errno == 48: # Address already in use
|
|
107
|
+
CITRASCOPE_LOGGER.error(
|
|
108
|
+
f"Port {self.port} is already in use. Please stop any other services using this port "
|
|
109
|
+
f"or use --web-port to specify a different port."
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
CITRASCOPE_LOGGER.error(f"Web server OS error: {e}", exc_info=True)
|
|
104
113
|
except Exception as e:
|
|
105
114
|
CITRASCOPE_LOGGER.error(f"Web server error: {e}", exc_info=True)
|
|
106
115
|
|
|
@@ -412,12 +412,14 @@ function updateLatestLogLine() {
|
|
|
412
412
|
|
|
413
413
|
const timestamp = new Date(latestLog.timestamp).toLocaleTimeString();
|
|
414
414
|
const cleanMessage = stripAnsiCodes(latestLog.message);
|
|
415
|
+
// Truncate message to ~150 chars for collapsed header (approx 2 lines)
|
|
416
|
+
const truncatedMessage = cleanMessage.length > 150 ? cleanMessage.substring(0, 150) + '...' : cleanMessage;
|
|
415
417
|
|
|
416
418
|
content.querySelector('.log-timestamp').textContent = timestamp;
|
|
417
419
|
const levelSpan = content.querySelector('.log-level');
|
|
418
420
|
levelSpan.classList.add(`log-level-${latestLog.level}`);
|
|
419
421
|
levelSpan.textContent = latestLog.level;
|
|
420
|
-
content.querySelector('.log-message').textContent =
|
|
422
|
+
content.querySelector('.log-message').textContent = truncatedMessage;
|
|
421
423
|
|
|
422
424
|
latestLogLine.innerHTML = '';
|
|
423
425
|
latestLogLine.appendChild(content);
|
|
@@ -2,12 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
import { getConfig, saveConfig, getConfigStatus, getHardwareAdapters, getAdapterSchema } from './api.js';
|
|
4
4
|
|
|
5
|
+
// API Host constants - must match backend constants in app.py
|
|
6
|
+
const PROD_API_HOST = 'api.citra.space';
|
|
7
|
+
const DEV_API_HOST = 'dev.api.citra.space';
|
|
8
|
+
const DEFAULT_API_PORT = 443;
|
|
9
|
+
|
|
5
10
|
let currentAdapterSchema = [];
|
|
6
11
|
export let currentConfig = {};
|
|
7
12
|
|
|
8
13
|
/**
|
|
9
14
|
* Initialize configuration management
|
|
10
15
|
*/
|
|
16
|
+
async function fetchVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch('/api/version');
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
const versionEl = document.getElementById('citraScopeVersion');
|
|
21
|
+
if (versionEl && data.version) {
|
|
22
|
+
versionEl.textContent = data.version;
|
|
23
|
+
}
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Error fetching version:', error);
|
|
26
|
+
const versionEl = document.getElementById('citraScopeVersion');
|
|
27
|
+
if (versionEl) {
|
|
28
|
+
versionEl.textContent = 'unknown';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
export async function initConfig() {
|
|
12
34
|
// Populate hardware adapter dropdown
|
|
13
35
|
await loadAdapterOptions();
|
|
@@ -25,6 +47,19 @@ export async function initConfig() {
|
|
|
25
47
|
});
|
|
26
48
|
}
|
|
27
49
|
|
|
50
|
+
// API endpoint selection change
|
|
51
|
+
const apiEndpointSelect = document.getElementById('apiEndpoint');
|
|
52
|
+
if (apiEndpointSelect) {
|
|
53
|
+
apiEndpointSelect.addEventListener('change', function(e) {
|
|
54
|
+
const customContainer = document.getElementById('customHostContainer');
|
|
55
|
+
if (e.target.value === 'custom') {
|
|
56
|
+
customContainer.style.display = 'block';
|
|
57
|
+
} else {
|
|
58
|
+
customContainer.style.display = 'none';
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
// Config form submission
|
|
29
64
|
const configForm = document.getElementById('configForm');
|
|
30
65
|
if (configForm) {
|
|
@@ -34,6 +69,7 @@ export async function initConfig() {
|
|
|
34
69
|
// Load initial config
|
|
35
70
|
await loadConfiguration();
|
|
36
71
|
checkConfigStatus();
|
|
72
|
+
fetchVersion();
|
|
37
73
|
}
|
|
38
74
|
|
|
39
75
|
/**
|
|
@@ -114,6 +150,27 @@ async function loadConfiguration() {
|
|
|
114
150
|
imagesDirElement.textContent = config.images_dir_path;
|
|
115
151
|
}
|
|
116
152
|
|
|
153
|
+
// API endpoint selector
|
|
154
|
+
const apiEndpointSelect = document.getElementById('apiEndpoint');
|
|
155
|
+
const customHostContainer = document.getElementById('customHostContainer');
|
|
156
|
+
const customHost = document.getElementById('customHost');
|
|
157
|
+
const customPort = document.getElementById('customPort');
|
|
158
|
+
const customUseSsl = document.getElementById('customUseSsl');
|
|
159
|
+
|
|
160
|
+
if (config.host === PROD_API_HOST) {
|
|
161
|
+
apiEndpointSelect.value = 'production';
|
|
162
|
+
customHostContainer.style.display = 'none';
|
|
163
|
+
} else if (config.host === DEV_API_HOST) {
|
|
164
|
+
apiEndpointSelect.value = 'development';
|
|
165
|
+
customHostContainer.style.display = 'none';
|
|
166
|
+
} else {
|
|
167
|
+
apiEndpointSelect.value = 'custom';
|
|
168
|
+
customHostContainer.style.display = 'block';
|
|
169
|
+
customHost.value = config.host || '';
|
|
170
|
+
customPort.value = config.port || DEFAULT_API_PORT;
|
|
171
|
+
customUseSsl.checked = config.use_ssl !== undefined ? config.use_ssl : true;
|
|
172
|
+
}
|
|
173
|
+
|
|
117
174
|
// Core fields
|
|
118
175
|
document.getElementById('personal_access_token').value = config.personal_access_token || '';
|
|
119
176
|
document.getElementById('telescopeId').value = config.telescope_id || '';
|
|
@@ -274,6 +331,24 @@ async function saveConfiguration(event) {
|
|
|
274
331
|
// Hide previous messages
|
|
275
332
|
hideConfigMessages();
|
|
276
333
|
|
|
334
|
+
// Determine API host settings based on endpoint selection
|
|
335
|
+
const apiEndpoint = document.getElementById('apiEndpoint').value;
|
|
336
|
+
let host, port, use_ssl;
|
|
337
|
+
|
|
338
|
+
if (apiEndpoint === 'production') {
|
|
339
|
+
host = PROD_API_HOST;
|
|
340
|
+
port = DEFAULT_API_PORT;
|
|
341
|
+
use_ssl = true;
|
|
342
|
+
} else if (apiEndpoint === 'development') {
|
|
343
|
+
host = DEV_API_HOST;
|
|
344
|
+
port = DEFAULT_API_PORT;
|
|
345
|
+
use_ssl = true;
|
|
346
|
+
} else { // custom
|
|
347
|
+
host = document.getElementById('customHost').value;
|
|
348
|
+
port = parseInt(document.getElementById('customPort').value, 10);
|
|
349
|
+
use_ssl = document.getElementById('customUseSsl').checked;
|
|
350
|
+
}
|
|
351
|
+
|
|
277
352
|
const config = {
|
|
278
353
|
personal_access_token: document.getElementById('personal_access_token').value,
|
|
279
354
|
telescope_id: document.getElementById('telescopeId').value,
|
|
@@ -282,10 +357,11 @@ async function saveConfiguration(event) {
|
|
|
282
357
|
log_level: document.getElementById('logLevel').value,
|
|
283
358
|
keep_images: document.getElementById('keep_images').checked,
|
|
284
359
|
file_logging_enabled: document.getElementById('file_logging_enabled').checked,
|
|
285
|
-
//
|
|
286
|
-
host:
|
|
287
|
-
port:
|
|
288
|
-
use_ssl:
|
|
360
|
+
// API settings from endpoint selector
|
|
361
|
+
host: host,
|
|
362
|
+
port: port,
|
|
363
|
+
use_ssl: use_ssl,
|
|
364
|
+
// Preserve other settings from loaded config
|
|
289
365
|
max_task_retries: currentConfig.max_task_retries || 3,
|
|
290
366
|
initial_retry_delay_seconds: currentConfig.initial_retry_delay_seconds || 30,
|
|
291
367
|
max_retry_delay_seconds: currentConfig.max_retry_delay_seconds || 300,
|
|
@@ -57,6 +57,29 @@ body {
|
|
|
57
57
|
/* Log accordion customization */
|
|
58
58
|
.log-accordion-button {
|
|
59
59
|
border-bottom: 1px solid #444 !important;
|
|
60
|
+
position: relative;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.accordion-social-links {
|
|
64
|
+
position: absolute;
|
|
65
|
+
right: 50px; /* Position to the left of the accordion arrow */
|
|
66
|
+
top: 50%;
|
|
67
|
+
transform: translateY(-50%);
|
|
68
|
+
display: flex;
|
|
69
|
+
gap: 12px;
|
|
70
|
+
align-items: center;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.social-link {
|
|
74
|
+
color: #a0aec0;
|
|
75
|
+
transition: color 0.2s ease;
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.social-link:hover {
|
|
82
|
+
color: #e2e8f0;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
.log-accordion-body {
|
|
@@ -72,6 +95,15 @@ body {
|
|
|
72
95
|
.log-latest-line {
|
|
73
96
|
font-family: monospace;
|
|
74
97
|
color: #e2e8f0;
|
|
98
|
+
display: block;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
text-overflow: ellipsis;
|
|
101
|
+
display: -webkit-box;
|
|
102
|
+
-webkit-line-clamp: 2;
|
|
103
|
+
-webkit-box-orient: vertical;
|
|
104
|
+
line-height: 1.4em;
|
|
105
|
+
max-height: 2.8em;
|
|
106
|
+
padding-right: 130px; /* Make room for 3 social icons and accordion arrow */
|
|
75
107
|
}
|
|
76
108
|
|
|
77
109
|
/* Task display */
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<li class="nav-item"><a href="#" class="nav-link px-2 text-white" data-section="monitoring"
|
|
35
35
|
aria-current="page">Monitoring</a></li>
|
|
36
36
|
<li class="nav-item"><a href="#" class="nav-link px-2" data-section="config">Config</a></li>
|
|
37
|
+
<li class="nav-item"><a href="https://docs.citra.space/citrascope/" class="nav-link px-2" target="_blank">Docs</a></li>
|
|
37
38
|
<li class="nav-item"><a href="#" class="nav-link bg-success text-white btn" style="display: none;"
|
|
38
39
|
id="taskScopeButton" target="_blank">Task my Scope</a></li>
|
|
39
40
|
</ul>
|
|
@@ -165,6 +166,32 @@
|
|
|
165
166
|
</div>
|
|
166
167
|
<div class="card-body">
|
|
167
168
|
<div class="row g-3">
|
|
169
|
+
<div class="col-12">
|
|
170
|
+
<label for="apiEndpoint" class="form-label">API Endpoint</label>
|
|
171
|
+
<select id="apiEndpoint" class="form-select">
|
|
172
|
+
<option value="production">Production (api.citra.space)</option>
|
|
173
|
+
<option value="development">Development (dev.api.citra.space)</option>
|
|
174
|
+
<option value="custom">Custom</option>
|
|
175
|
+
</select>
|
|
176
|
+
</div>
|
|
177
|
+
<div class="col-12" id="customHostContainer" style="display: none;">
|
|
178
|
+
<label for="customHost" class="form-label">Custom API Host</label>
|
|
179
|
+
<input type="text" id="customHost" class="form-control" placeholder="api.example.com">
|
|
180
|
+
<div class="row g-2 mt-2">
|
|
181
|
+
<div class="col-6">
|
|
182
|
+
<label for="customPort" class="form-label small">Port</label>
|
|
183
|
+
<input type="number" id="customPort" class="form-control" value="443" placeholder="443">
|
|
184
|
+
</div>
|
|
185
|
+
<div class="col-6 d-flex align-items-end">
|
|
186
|
+
<div class="form-check">
|
|
187
|
+
<input class="form-check-input" type="checkbox" id="customUseSsl" checked>
|
|
188
|
+
<label class="form-check-label" for="customUseSsl">
|
|
189
|
+
Use SSL
|
|
190
|
+
</label>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
168
195
|
<div class="col-12 col-md-6">
|
|
169
196
|
<label for="personal_access_token" class="form-label">Personal Access Token <span class="text-danger">*</span></label>
|
|
170
197
|
<input type="password" id="personal_access_token" class="form-control" placeholder="Enter your Citra API token" required>
|
|
@@ -277,6 +304,15 @@
|
|
|
277
304
|
</small>
|
|
278
305
|
</div>
|
|
279
306
|
</div>
|
|
307
|
+
|
|
308
|
+
<!-- Version Info -->
|
|
309
|
+
<div class="row mt-3">
|
|
310
|
+
<div class="col">
|
|
311
|
+
<small class="text-muted">
|
|
312
|
+
CitraScope Version: <span id="citraScopeVersion" class="text-secondary">Loading...</span>
|
|
313
|
+
</small>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
280
316
|
</form>
|
|
281
317
|
</div>
|
|
282
318
|
|
|
@@ -315,6 +351,23 @@
|
|
|
315
351
|
data-bs-toggle="collapse" data-bs-target="#logAccordionCollapse" aria-expanded="false"
|
|
316
352
|
aria-controls="logAccordionCollapse">
|
|
317
353
|
<span id="latestLogLine" class="log-latest-line">Log Terminal</span>
|
|
354
|
+
<div class="accordion-social-links">
|
|
355
|
+
<a href="https://github.com/citra-space/citrascope/issues/new" target="_blank" class="social-link" title="Report an Issue" onclick="event.stopPropagation();">
|
|
356
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
|
357
|
+
<path d="M16 8c0 3.866-3.582 7-8 7a9 9 0 0 1-2.347-.306c-.584.296-1.925.864-4.181 1.234-.2.032-.352-.176-.273-.362.354-.836.674-1.95.77-2.966C.744 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7M5 8a1 1 0 1 0-2 0 1 1 0 0 0 2 0m4 0a1 1 0 1 0-2 0 1 1 0 0 0 2 0m3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
|
|
358
|
+
</svg>
|
|
359
|
+
</a>
|
|
360
|
+
<a href="https://discord.gg/STgJQkWe9y" target="_blank" class="social-link" title="Join Discord" onclick="event.stopPropagation();">
|
|
361
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
|
362
|
+
<path d="M13.545 2.907a13.227 13.227 0 0 0-3.257-1.011.05.05 0 0 0-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 0 0-3.658 0 8.258 8.258 0 0 0-.412-.833.051.051 0 0 0-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 0 0-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 0 0 3.995 2.02.05.05 0 0 0 .056-.019c.308-.42.582-.863.818-1.329a.05.05 0 0 0-.01-.059.051.051 0 0 0-.018-.011 8.875 8.875 0 0 1-1.248-.595.05.05 0 0 1-.02-.066.051.051 0 0 1 .015-.019c.084-.063.168-.129.248-.195a.05.05 0 0 1 .051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 0 1 .053.007c.08.066.164.132.248.195a.051.051 0 0 1-.004.085 8.254 8.254 0 0 1-1.249.594.05.05 0 0 0-.03.03.052.052 0 0 0 .003.041c.24.465.515.909.817 1.329a.05.05 0 0 0 .056.019 13.235 13.235 0 0 0 4.001-2.02.049.049 0 0 0 .021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 0 0-.02-.019Zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612Zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612Z"/>
|
|
363
|
+
</svg>
|
|
364
|
+
</a>
|
|
365
|
+
<a href="https://github.com/citra-space/citrascope" target="_blank" class="social-link" title="View on GitHub" onclick="event.stopPropagation();">
|
|
366
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
|
367
|
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
|
|
368
|
+
</svg>
|
|
369
|
+
</a>
|
|
370
|
+
</div>
|
|
318
371
|
</button>
|
|
319
372
|
</h2>
|
|
320
373
|
<div id="logAccordionCollapse" class="accordion-collapse collapse" aria-labelledby="logAccordionHeader"
|
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "citrascope"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Remotely control a telescope while it polls for tasks, collects and edge processes data, and delivers results and data for further processing."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10,<3.13"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
authors = [{ name = "Patrick McDavid", email = "patrick@citra.space" }]
|
|
9
9
|
dependencies = [
|
|
10
|
-
"pydantic-settings",
|
|
11
|
-
"python-json-logger",
|
|
12
10
|
"click",
|
|
13
11
|
"requests",
|
|
14
12
|
"httpx",
|
|
15
13
|
"python-dateutil",
|
|
16
|
-
"types-python-dateutil",
|
|
17
14
|
"skyfield",
|
|
18
|
-
"pytest-cov",
|
|
19
15
|
"fastapi>=0.104.0",
|
|
20
16
|
"uvicorn[standard]>=0.24.0",
|
|
21
17
|
"websockets>=12.0",
|
|
@@ -42,6 +38,7 @@ dev = [
|
|
|
42
38
|
"pre-commit",
|
|
43
39
|
"isort",
|
|
44
40
|
"mypy",
|
|
41
|
+
"types-python-dateutil",
|
|
45
42
|
"flake8",
|
|
46
43
|
"flake8-pytest-style",
|
|
47
44
|
"bump-my-version",
|
|
@@ -49,16 +46,12 @@ dev = [
|
|
|
49
46
|
]
|
|
50
47
|
test = ["pytest", "pytest-cov", "mockito"]
|
|
51
48
|
build = ["build"]
|
|
52
|
-
docs = ["sphinx", "sphinx-autodoc-typehints", "sphinx-markdown-builder"]
|
|
53
49
|
deploy = ["twine"]
|
|
54
50
|
|
|
55
51
|
[build-system]
|
|
56
52
|
requires = ["hatchling"]
|
|
57
53
|
build-backend = "hatchling.build"
|
|
58
54
|
|
|
59
|
-
[tool.hatch.build.force-include]
|
|
60
|
-
"docs" = "docs"
|
|
61
|
-
|
|
62
55
|
[tool.black]
|
|
63
56
|
line-length = 120
|
|
64
57
|
force-exclude = '''
|
|
@@ -97,7 +90,7 @@ omit = ["tests/*", "__init__.py"]
|
|
|
97
90
|
allow-direct-references = true
|
|
98
91
|
|
|
99
92
|
[tool.bumpversion]
|
|
100
|
-
current_version = "0.
|
|
93
|
+
current_version = "0.4.0"
|
|
101
94
|
commit = true
|
|
102
95
|
tag = true
|
|
103
96
|
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
|
|
3
|
-
from citrascope.citra_scope_daemon import CitraScopeDaemon
|
|
4
|
-
from citrascope.settings.citrascope_settings import CitraScopeSettings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@click.group()
|
|
8
|
-
@click.option("--dev", is_flag=True, default=False, help="Use the development API (dev.app.citra.space)")
|
|
9
|
-
@click.option("--log-level", default="INFO", help="Set logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)")
|
|
10
|
-
@click.option("--keep-images", is_flag=True, default=False, help="Keep image files after upload (do not delete)")
|
|
11
|
-
@click.pass_context
|
|
12
|
-
def cli(ctx, dev, log_level, keep_images):
|
|
13
|
-
ctx.obj = {"settings": CitraScopeSettings(dev=dev, log_level=log_level, keep_images=keep_images)}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@cli.command("start")
|
|
17
|
-
@click.option("--web-host", default="0.0.0.0", help="Web server host address (default: 0.0.0.0)")
|
|
18
|
-
@click.option("--web-port", default=24872, type=int, help="Web server port (default: 24872)")
|
|
19
|
-
@click.pass_context
|
|
20
|
-
def start(ctx, web_host, web_port):
|
|
21
|
-
daemon = CitraScopeDaemon(ctx.obj["settings"], enable_web=True, web_host=web_host, web_port=web_port)
|
|
22
|
-
daemon.run()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if __name__ == "__main__":
|
|
26
|
-
cli()
|
citrascope-0.3.0/docs/index.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# CitraScope Documentation
|
|
2
|
-
|
|
3
|
-
Welcome to the CitraScope documentation.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
CitraScope is a Python application for remote telescope control, task automation, and data collection.
|
|
8
|
-
It connects to the Citra.space API and INDI hardware to execute observation tasks.
|
|
9
|
-
|
|
10
|
-
## Architecture
|
|
11
|
-
|
|
12
|
-
- **CLI Entrypoint:** `citrascope/__main__.py`
|
|
13
|
-
Handles configuration, authentication, and starts the task daemon.
|
|
14
|
-
- **API Client:** `citrascope/api/client.py`
|
|
15
|
-
Communicates with Citra.space for authentication, telescope, satellite, and ground station data.
|
|
16
|
-
- **Task Management:** `citrascope/tasks/runner.py`
|
|
17
|
-
Polls for tasks, schedules, and executes observations.
|
|
18
|
-
- **Settings:** `citrascope/settings/_citrascope_settings.py`
|
|
19
|
-
Loads configuration from environment variables.
|
|
20
|
-
|
|
21
|
-
## Configuration
|
|
22
|
-
|
|
23
|
-
See [README.md](../README.md) for installation and environment setup.
|
|
24
|
-
Environment variables are documented in `.env.example`.
|
|
25
|
-
|
|
26
|
-
## Usage
|
|
27
|
-
|
|
28
|
-
Run the CLI:
|
|
29
|
-
```sh
|
|
30
|
-
python -m citrascope start
|
|
31
|
-
```
|
|
32
|
-
Or use VS Code launch configurations for development and debugging.
|
|
33
|
-
|
|
34
|
-
## Testing
|
|
35
|
-
|
|
36
|
-
- **Unit tests** are written using [pytest](https://pytest.org/) and are located in the `tests/` directory.
|
|
37
|
-
- To run tests manually, use:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
pytest
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Further Documentation
|
|
44
|
-
|
|
45
|
-
- [API Reference](https://api.citra.space/docs)
|
|
46
|
-
- [Contributing Guide](contributing.md) (coming soon)
|
|
47
|
-
- [Troubleshooting](troubleshooting.md) (coming soon)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/abstract_astro_hardware_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{citrascope-0.3.0 → citrascope-0.4.0}/citrascope/hardware/nina_adv_http_survey_template.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|