├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .gitmodules ├── .tmux-session ├── .travis.yml ├── .vimrc ├── CMakeLists.txt ├── Changelog.md ├── LICENSE ├── README.md ├── backend ├── .gitignore ├── .vimrc ├── AstroPhotoPlus.service ├── CMakeLists.txt ├── api_decorators.py ├── api_entrypoint.py ├── api_utils.py ├── app.py ├── broadcast_service.py ├── catalogs │ ├── __init__.py │ ├── catalogs.py │ └── import_catalogs.py ├── errors │ ├── __init__.py │ └── model_exceptions.py ├── image_processing │ ├── clean │ ├── compile │ ├── image_processing.cpp │ ├── image_processing.h │ ├── image_processing.i │ ├── image_processing.py │ ├── image_processing_wrap.cxx │ ├── setup.py │ └── update_swig ├── images │ ├── __init__.py │ ├── image.py │ └── images_db.py ├── indi │ ├── __init__.py │ ├── blob_client.py │ ├── camera.py │ ├── device.py │ ├── filter_wheel.py │ ├── guider.py │ ├── indi_profile.py │ ├── indi_property.py │ ├── indi_service.py │ ├── parse_md5_file │ ├── server.py │ └── telescope.py ├── models │ ├── __init__.py │ ├── model.py │ └── saved_list.py ├── network │ ├── __init__.py │ ├── network_manager.py │ └── network_service.py ├── phd2 │ ├── __init__.py │ ├── errors.py │ ├── phd2.py │ ├── phd2_client.py │ ├── phd2_process.py │ ├── phd2_service.py │ └── phd2_socket.py ├── platesolving │ ├── __init__.py │ ├── astrometry_downloader.py │ ├── astrometry_indexes.py │ ├── astrometry_indexes_tycho.py │ └── platesolving.py ├── polar_alignment │ ├── .gitignore │ ├── __init__.py │ ├── darv.py │ ├── pa_platesolving_capture.py │ ├── pa_platesolving_drift.py │ └── test.py ├── redis_client.py ├── redis_server.py ├── requirements.txt ├── sequences │ ├── __init__.py │ ├── autoguider.py │ ├── command_sequence_job.py │ ├── exposure_sequence_job.py │ ├── exposure_sequence_job_runner.py │ ├── filter_wheel_sequence_job.py │ ├── pause_sequence_job.py │ ├── property_sequence_job.py │ ├── save_async_fits.py │ ├── sequence.py │ ├── sequence_job.py │ ├── sequences_runner.py │ ├── shot.py │ └── stop_sequence.py ├── service.py ├── skychart │ ├── __init__.py │ ├── astropy_monkeypatch.py │ ├── marker.py │ ├── skychart.py │ ├── skyframe.py │ ├── star.py │ ├── stars.db │ ├── svg.py │ └── utils │ │ ├── import_json_to_sql.py │ │ ├── plot_rigel_region_sqlite.py │ │ ├── split_stars_json.py │ │ ├── stars_mag7.json.gz │ │ ├── stars_mag9.json.gz │ │ ├── stars_over_mag9.json.gz │ │ └── vizier_catalog_downloader.py ├── start-debug-server ├── start-server ├── static_settings.py ├── system │ ├── __init__.py │ ├── commands.py │ ├── controller.py │ ├── displays.py │ ├── event_listener.py │ ├── server_sent_events.py │ └── settings.py ├── utils │ ├── benchmark_log.py │ ├── cleanup_venv.py │ ├── mp.py │ ├── system_time.py │ ├── test_processworker.py │ ├── threads.py │ └── worker.py └── version.json ├── config ├── CMakeLists.txt ├── debian_based │ ├── CMakeLists.txt │ ├── astrophotoplus-deb-updater │ ├── commands.json.in │ ├── etc_AstroPhotoPlus-commands.json.in │ ├── postinst.in │ └── postrm.in ├── dev-webserver-conf │ └── nginx │ │ └── astrophotoplus.conf ├── linux_generic │ ├── CMakeLists.txt │ └── commands.json └── prod-webserver-conf │ ├── apache2 │ └── astrophotoplus.conf │ └── nginx │ └── astrophotoplus.conf ├── docker-compose └── dev-server │ ├── .env │ ├── backend │ ├── Dockerfile │ ├── astrophotoplus-wifi-helper │ ├── commands.json │ ├── entrypoint │ ├── indi-ppa.list │ └── shutdown-docker-helper │ ├── docker-compose.yml │ ├── frontend │ ├── .gitignore │ ├── Dockerfile │ └── entrypoint │ └── nginx-conf.d │ └── astrophotoplus.conf ├── frontend ├── .babelrc ├── .buckconfig ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .vimrc ├── .watchmanconfig ├── CMakeLists.txt ├── README.md ├── android │ ├── app │ │ ├── BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── AntDesign.ttf │ │ │ │ ├── Entypo.ttf │ │ │ │ ├── EvilIcons.ttf │ │ │ │ ├── Feather.ttf │ │ │ │ ├── FontAwesome.ttf │ │ │ │ ├── FontAwesome5_Brands.ttf │ │ │ │ ├── FontAwesome5_Regular.ttf │ │ │ │ ├── FontAwesome5_Solid.ttf │ │ │ │ ├── Foundation.ttf │ │ │ │ ├── Ionicons.ttf │ │ │ │ ├── MaterialCommunityIcons.ttf │ │ │ │ ├── MaterialIcons.ttf │ │ │ │ ├── Octicons.ttf │ │ │ │ ├── Roboto.ttf │ │ │ │ ├── Roboto_medium.ttf │ │ │ │ ├── SimpleLineIcons.ttf │ │ │ │ ├── Zocial.ttf │ │ │ │ └── rubicon-icon-font.ttf │ │ │ ├── java │ │ │ └── com │ │ │ │ └── astrophotoplus_frontend │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── keystores │ │ ├── BUCK │ │ └── debug.keystore.properties │ └── settings.gradle ├── app.json ├── babel.config.js ├── index.native.js ├── ios │ ├── AstroPhotoPlus_frontend-tvOS │ │ └── Info.plist │ ├── AstroPhotoPlus_frontend-tvOSTests │ │ └── Info.plist │ ├── AstroPhotoPlus_frontend.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── AstroPhotoPlus_frontend-tvOS.xcscheme │ │ │ └── AstroPhotoPlus_frontend.xcscheme │ ├── AstroPhotoPlus_frontend │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ └── LaunchScreen.xib │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ └── main.m │ └── AstroPhotoPlus_frontendTests │ │ ├── AstroPhotoPlus_frontendTests.m │ │ └── Info.plist ├── package.json ├── public │ ├── icon-144.png │ ├── icon-16.png │ ├── icon-168.png │ ├── icon-192.png │ ├── icon-256.png │ ├── icon-32.png │ ├── icon-48.png │ ├── icon-512.png │ ├── icon-64.png │ ├── icon-72.png │ ├── icon-96.png │ ├── images │ │ ├── site-logo-dark-bg-source.svg │ │ └── site-logo-dark-bg.svg │ ├── index.html │ ├── manifest.json │ └── spinner.svg ├── semantic.json ├── src │ ├── App │ │ ├── App.css │ │ ├── App.js │ │ ├── App.native.js │ │ ├── AppContainer.js │ │ ├── TimeSyncModal.js │ │ ├── actions.js │ │ ├── appContainerSelector.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Autoguider │ │ ├── DitheringOptions.js │ │ └── selectors.js │ ├── BackendSelection │ │ ├── BackendSelectionContainer.js │ │ ├── BackendSelectionPage.js │ │ ├── actions.js │ │ ├── actions.native.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Camera │ │ ├── AutoExposureContainer.js │ │ ├── Camera.js │ │ ├── CameraBinning.js │ │ ├── CameraBinningContainer.js │ │ ├── CameraContainer.js │ │ ├── CameraSectionMenuEntries.js │ │ ├── CameraSectionMenuEntriesContainer.js │ │ ├── CurrentImageViewerContainer.js │ │ ├── ExposureInput.js │ │ ├── ExposureInputContainer.js │ │ ├── FilterSelection.js │ │ ├── FilterSelectionContainer.js │ │ ├── ImageViewer.js │ │ ├── SelectFilter.js │ │ ├── SelectFilterContainer.js │ │ ├── actions.js │ │ ├── reducer.js │ │ ├── sections.js │ │ └── selectors.js │ ├── Catalogs │ │ ├── CatalogSearch.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Commands │ │ ├── Commands.js │ │ ├── CommandsContainer.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Errors │ │ ├── ErrorPage.js │ │ ├── ErrorPageContainer.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Gear │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Home │ │ ├── Home.js │ │ ├── HomeContainer.js │ │ └── selectors.js │ ├── INDI-Server │ │ ├── INDIDeviceContainer.js │ │ ├── INDIDeviceGroup.js │ │ ├── INDIDeviceGroupContainer.js │ │ ├── INDIDevicePage.js │ │ ├── INDIInputProperty.js │ │ ├── INDILight.js │ │ ├── INDILightProperty.js │ │ ├── INDIMessagesPanel.js │ │ ├── INDINumber.js │ │ ├── INDIPropertyContainer.js │ │ ├── INDIPropertyRow.js │ │ ├── INDIPropertyRowContainer.js │ │ ├── INDIServerContainer.js │ │ ├── INDIServerDetailsContainer.js │ │ ├── INDIServerDetailsPage.js │ │ ├── INDIServerPage.js │ │ ├── INDISwitch.js │ │ ├── INDISwitchProperty.js │ │ ├── INDIText.js │ │ ├── INDIValueContainer.js │ │ ├── INDIValueContainers.js │ │ ├── actions.js │ │ ├── reducer.js │ │ ├── selectors.js │ │ └── utils.js │ ├── INDI-Service │ │ ├── INDIServiceContainer.js │ │ ├── INDIServiceDriversContainer.js │ │ ├── INDIServiceDriversPage.js │ │ ├── INDIServicePage.js │ │ ├── INDIServiceProfilesContainer.js │ │ ├── INDIServiceProfilesPage.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Image │ │ ├── Histogram.js │ │ ├── HistogramContainer.js │ │ ├── Image.js │ │ ├── ImageContainer.js │ │ ├── ImagePage.js │ │ ├── ImageViewOptions.js │ │ ├── ImageViewOptionsContainer.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Modals │ │ └── ModalDialog.js │ ├── Navigation │ │ ├── HistoryLandingContainer.js │ │ ├── HistoryLandingPage.js │ │ ├── Navbar.js │ │ ├── Navbar.native.js │ │ ├── NavbarContainer.js │ │ ├── NavbarMenu.js │ │ ├── NavbarMenuItems.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Network │ │ ├── .gitignore │ │ ├── actions.js │ │ └── reducer.js │ ├── Notifications │ │ ├── Notifications.js │ │ ├── NotificationsContainer.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── PHD2 │ │ ├── PHD2.js │ │ ├── PHD2DevicesProfiles.js │ │ ├── PHD2Graph.js │ │ ├── PHD2StartProcess.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── PlateSolving │ │ ├── PlateSolving.js │ │ ├── PlateSolvingContainer.js │ │ ├── PlateSolvingPage.js │ │ ├── PlateSolvingPageContainer.js │ │ ├── actions.js │ │ ├── reducer.js │ │ ├── selectors.js │ │ └── utils.js │ ├── PolarAlignment │ │ ├── DARV.js │ │ ├── PlatesolvingDrift.js │ │ ├── PolarAlignmentPage.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── SemanticTheme │ │ ├── theme.config │ │ └── themes │ │ │ └── astrophotoplus │ │ │ ├── collections │ │ │ ├── form.variables │ │ │ ├── menu.variables │ │ │ ├── message.variables │ │ │ └── table.variables │ │ │ ├── elements │ │ │ ├── button.variables │ │ │ ├── label.variables │ │ │ ├── list.variables │ │ │ └── segment.variables │ │ │ ├── globals │ │ │ ├── site.overrides │ │ │ └── site.variables │ │ │ ├── modules │ │ │ ├── accordion.variables │ │ │ ├── checkbox.overrides │ │ │ ├── checkbox.variables │ │ │ ├── dropdown.variables │ │ │ └── modal.variables │ │ │ └── views │ │ │ └── card.variables │ ├── SequenceJobs │ │ ├── AddSequenceJobModal.js │ │ ├── Command │ │ │ └── CommandSequenceJob.js │ │ ├── Exposure │ │ │ ├── ExposureSequenceJob.js │ │ │ └── ExposureSequenceJobContainer.js │ │ ├── FilterWheel │ │ │ ├── FilterSequenceJob.js │ │ │ ├── FilterSequenceJobContainer.js │ │ │ ├── FilterSequenceJobItem.js │ │ │ └── FilterSequenceJobItemContainer.js │ │ ├── INDIProperty │ │ │ ├── INDIPropertySequenceJob.js │ │ │ ├── INDIPropertySequenceJobContainer.js │ │ │ ├── INDIPropertySequenceJobMenu.js │ │ │ ├── INDIPropertySequenceJobMenuContainer.js │ │ │ └── INDIPropertySequenceJobMenuItem.js │ │ ├── Images.js │ │ ├── ImagesContainer.js │ │ ├── Pause │ │ │ └── PauseSequenceJob.js │ │ ├── SequenceJob.js │ │ ├── SequenceJobButtons.js │ │ ├── SequenceJobButtonsContainer.js │ │ ├── SequenceJobContainer.js │ │ ├── SequenceJobListItem.js │ │ ├── SequenceJobListItemContainer.js │ │ ├── SequenceJobsList.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Sequences │ │ ├── AddSequenceModal.js │ │ ├── AddSequenceModalContainer.js │ │ ├── ImportSequence.js │ │ ├── ImportSequenceContainer.js │ │ ├── LastCapturedSequenceImage.js │ │ ├── LastCapturedSequenceImageContainer.js │ │ ├── Sequence.js │ │ ├── SequenceContainer.js │ │ ├── SequenceListItem.js │ │ ├── SequenceListItemContainer.js │ │ ├── SequenceStatusCards.js │ │ ├── SequenceStatusCardsContainer.js │ │ ├── SequencesList.js │ │ ├── SequencesListContainer.js │ │ ├── SequencesPage.js │ │ ├── actions.js │ │ ├── inputSelectors.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── Settings │ │ ├── CheckboxSetting.js │ │ ├── DownloadIndexesModal.js │ │ ├── DownloadIndexesModalContainer.js │ │ ├── InputActions.js │ │ ├── InputSetting.js │ │ ├── NetworkManager.js │ │ ├── SettingButtonChoice.js │ │ ├── Settings.js │ │ ├── SettingsContainer.js │ │ ├── ValueChangedWarning.js │ │ ├── actions.js │ │ ├── reducer.js │ │ └── selectors.js │ ├── actions.js │ ├── components │ │ ├── CheckButton.js │ │ ├── DateTime.js │ │ ├── DirectoryPicker.js │ │ ├── Filesize.js │ │ ├── LoadingPage.js │ │ ├── NotFoundPage.js │ │ ├── NumericInput.js │ │ ├── SkyChartsComponent.js │ │ ├── StickyPortal.js │ │ └── UploadFileDialog.js │ ├── containers │ │ └── LoadingContainer.js │ ├── index.js │ ├── initialiseApp.js │ ├── initialiseApp.native.js │ ├── middleware │ │ ├── api.js │ │ ├── events.js │ │ ├── polyfills.js │ │ ├── polyfills.native.js │ │ ├── request.js │ │ ├── request.native.js │ │ └── schemas.js │ ├── reducers.js │ ├── registerServiceWorker.js │ ├── routes.js │ ├── setupProxy.js │ └── utils │ │ └── index.js └── yarn.lock ├── project_version.cmake ├── scripts ├── AstroPhotoPlus-ctl ├── raspbian-gen │ ├── .gitignore │ ├── check-release.py │ ├── create-image │ ├── deploy-image-release.py │ ├── files │ │ ├── copy-astrophotoplus-version │ │ ├── copy-provisioning │ │ └── provisioning-launcher │ ├── raspbian-gen │ └── requirements.txt ├── sequence_stats.py ├── set_version.py ├── setup │ └── ubuntu-raspbian.sh └── version.sh.in └── vagrant ├── bionic-vanilla └── Vagrantfile ├── bionic ├── Vagrantfile └── astrophotoplus.conf └── focal-vanilla └── Vagrantfile /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: GuLinux 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, user-reported 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Server Information** 27 | - AstroPhoto Plus Version 28 | - Server hardware type (e.g. generic x86 or x86_64 computer, Raspberry, other ARM devices, Intel Compute Stick) 29 | - Operating system (e.g. Ubuntu Linux 18.04) 30 | - Python version in use 31 | - Remember to attach server logs created with `AstroPhotoPlus-ctl logs` if applicable. 32 | 33 | **Client** 34 | - Device: [e.g. iPhone6, desktop] 35 | - OS: [e.g. iOS8.1, Windows, Linux] 36 | - Browser [e.g. Chrome, Firefox] 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement, user-reported 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rdb 2 | TODO 3 | .DS_store 4 | build 5 | *.code-workspace 6 | .DS_Store 7 | .vscode/ 8 | *.log 9 | .vagrant 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "backend/indi-lite-tools"] 2 | path = backend/indi-lite-tools 3 | url = https://github.com/GuLinux/indi-lite-tools 4 | -------------------------------------------------------------------------------- /.tmux-session: -------------------------------------------------------------------------------- 1 | rename-window -t :0 gunicorn 2 | send-keys -t :0 'cd backend' Enter 3 | send-keys -t :0 './start-debug-server' Enter 4 | new-window -n node 5 | send-keys -t :1 'cd frontend' Enter 6 | send-keys -t :1 'BROWSER=none yarn start' Enter 7 | new-window -n backend 8 | send-keys -t :2 'cd backend' Enter 9 | send-keys -t :2 'vim' Enter 10 | new-window -n frontend 11 | send-keys -t :3 'cd frontend' Enter 12 | send-keys -t :3 'vim' Enter 13 | select-window -t :0 14 | 15 | 16 | -------------------------------------------------------------------------------- /.vimrc: -------------------------------------------------------------------------------- 1 | let g:ctrlp_working_path_mode = 'a' 2 | let g:ctrlp_user_command = 'cd %s && git ls-files -co --exclude-standard' 3 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | access.log 3 | __pycache__ 4 | dev_sample_fits 5 | 6 | -------------------------------------------------------------------------------- /backend/.vimrc: -------------------------------------------------------------------------------- 1 | let g:ctrlp_working_path_mode = 'a' 2 | let g:ctrlp_user_command = 'cd %s && git ls-files -co --exclude-standard' 3 | -------------------------------------------------------------------------------- /backend/AstroPhotoPlus.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Astrophotography sequence server 3 | Wants=redis.service 4 | 5 | [Service] 6 | Type=simple 7 | ExecStart=@ASTRO_PHOTO_PLUS_INSTALL_PREFIX@/backend/start-server --log-level warning --log-syslog --log-syslog-prefix AstroPhotoPlus --disable-redirect-access-to-syslog 8 | ExecStartPre=@ASTRO_PHOTO_PLUS_INSTALL_PREFIX@/backend/start-server --check-user 9 | WorkingDirectory=@ASTRO_PHOTO_PLUS_INSTALL_PREFIX@/backend 10 | Environment="SYSTEM_CONFDIR=@CONFDIR@" 11 | SyslogIdentifier=AstroPhoto-Plus 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /backend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/AstroPhotoPlus.service ${CMAKE_CURRENT_BINARY_DIR}/AstroPhotoPlus.service @ONLY) 2 | install( 3 | DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 4 | DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/ 5 | PATTERN "*pyc" EXCLUDE 6 | PATTERN "__pycache__" EXCLUDE 7 | PATTERN "start-server" EXCLUDE 8 | PATTERN "access.log" EXCLUDE 9 | PATTERN "*.json.gz" EXCLUDE 10 | PATTERN "network_manager.py" EXCLUDE 11 | ) 12 | 13 | INSTALL( 14 | FILES ${CMAKE_CURRENT_BINARY_DIR}/AstroPhotoPlus.service 15 | DESTINATION lib/systemd/system/ 16 | ) 17 | install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/start-server DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/backend) 18 | install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/start-debug-server DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/backend) 19 | install(PROGRAMS ${CMAKE_CURRENT_SOURCE_DIR}/network/network_manager.py DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/backend/network) 20 | -------------------------------------------------------------------------------- /backend/api_utils.py: -------------------------------------------------------------------------------- 1 | from flask import jsonify 2 | from system import controller 3 | 4 | 5 | def api_error(code, title, message=None, payload=None): 6 | return jsonify({ 7 | 'error': title, 8 | 'error_message': message, 9 | 'payload': payload, 10 | }), code 11 | 12 | def notify(event_type, event_name, payload, is_error=False): 13 | controller.notification(event_type, event_name, payload, is_error) 14 | return payload 15 | 16 | def api_bad_json_error(message=None, payload=None): 17 | return api_error(400, 'Bad Request', message if message else 'Invalid JSON request', payload=payload) 18 | 19 | def api_bad_request_error(message=None, payload=None): 20 | return api_error(400, 'Bad Request', message if message else 'Invalid Request', payload=payload) 21 | 22 | def api_not_found_error(message=None, payload=None): 23 | return api_error(404, 'Resource Not Found', message if message else 'The requested resource was not found on the server', payload=payload) 24 | 25 | def api_failed_method_error(message=None, payload=None): 26 | return api_error(500, 'Failed method', message if message else 'An error occured processing the request', payload=payload) 27 | -------------------------------------------------------------------------------- /backend/app.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | import json 3 | import os 4 | from utils.system_time import set_ntp 5 | 6 | 7 | 8 | 9 | static_folder = os.path.abspath(os.environ.get('ASTROPHOTO_PLUS_STATIC_FOLDER', '../frontend')) 10 | has_static_folder = os.path.isfile(os.path.join(static_folder, 'index.html')) 11 | 12 | 13 | app = Flask('AstroPhoto Plus', static_folder=None) 14 | app.config['has_static_folder'] = has_static_folder 15 | app.config['static_folder'] = static_folder 16 | 17 | 18 | with open('version.json', 'r') as version_json: 19 | app.config['version'] = json.load(version_json) 20 | 21 | logger = app.logger 22 | 23 | from redis_server import RedisServer 24 | redis_server = RedisServer() 25 | redis_server.start() 26 | 27 | REDIS_HOST = os.environ.get('REDIS_SERVER', '127.0.0.1') 28 | REDIS_PORT = redis_server.port 29 | app.config['REDIS_URL'] = 'redis://{}:{}/'.format(REDIS_HOST, REDIS_PORT) 30 | 31 | set_ntp(True) 32 | 33 | 34 | 35 | from broadcast_service import broadcast_service 36 | broadcast_service() 37 | from network import network_service 38 | network_service.start() 39 | -------------------------------------------------------------------------------- /backend/broadcast_service.py: -------------------------------------------------------------------------------- 1 | from utils.threads import start_thread 2 | import socket 3 | import time 4 | from system import settings, syslog 5 | import psutil 6 | 7 | BROADCAST_PORT = 27181 8 | 9 | def __broadcast_loop(): 10 | server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) 11 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 12 | server.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) 13 | # Set a timeout so the socket does not block 14 | # indefinitely when trying to receive data. 15 | server.settimeout(0.2) 16 | message = '\x1f'.join(['astrophoto+', str(settings.web_application_port), settings.server_name, settings.web_protocol]).encode() 17 | while True: 18 | try: 19 | for iface, addresses in psutil.net_if_addrs().items(): 20 | for address in addresses: 21 | if address.broadcast: 22 | # syslog('Sending broadcast to interface {} on broadcast address {}'.format(iface, address.broadcast)) 23 | written = server.sendto(message, (address.broadcast, BROADCAST_PORT)) 24 | except OSError as e: 25 | # Ignore errors when disconnected from network 26 | # syslog('Error while sending broadcast: {}'.format(e)) 27 | pass 28 | time.sleep(2) 29 | 30 | def broadcast_service(): 31 | start_thread(__broadcast_loop) 32 | 33 | 34 | -------------------------------------------------------------------------------- /backend/catalogs/__init__.py: -------------------------------------------------------------------------------- 1 | from .import_catalogs import catalog_importer 2 | from .catalogs import catalogs, Catalogs 3 | 4 | -------------------------------------------------------------------------------- /backend/errors/__init__.py: -------------------------------------------------------------------------------- 1 | from .model_exceptions import * 2 | -------------------------------------------------------------------------------- /backend/errors/model_exceptions.py: -------------------------------------------------------------------------------- 1 | class BadRequestError(Exception): 2 | def __init__(self, message=None, payload=None): 3 | Exception.__init__(self,message) 4 | self.message = message 5 | self.payload = payload 6 | 7 | class NotFoundError(Exception): 8 | def __init__(self, message=None, payload=None): 9 | Exception.__init__(self, message) 10 | self.message = message 11 | self.payload = payload 12 | 13 | class FailedMethodError(Exception): 14 | def __init__(self, message=None, payload=None): 15 | Exception.__init__(self, message) 16 | self.message = message 17 | self.payload = payload 18 | -------------------------------------------------------------------------------- /backend/image_processing/clean: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$( dirname "$0")" 3 | rm -rf *wrap* build *.so image_processing.py build/ 4 | 5 | -------------------------------------------------------------------------------- /backend/image_processing/compile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$( dirname "$0")" 3 | echo "****** compiling $PWD" 4 | python3 setup.py build_ext -b "$HOME/.local/share/AstroPhotoPlus/native_modules" -t /tmp/astrophoto+-native-build 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /backend/image_processing/image_processing.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGE_PROCESSING_H 2 | #define IMAGE_PROCESSING_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "image_processing.h" 10 | 11 | class ImageProcessing { 12 | public: 13 | ImageProcessing(const std::string &fitsfile, bool debug_log); 14 | ~ImageProcessing(); 15 | void save(const std::string &filename); 16 | void autostretch(); 17 | void clip(float min, float max); 18 | void resize(int width, int height, const std::string &interpolation); 19 | int width() const; 20 | int height() const; 21 | int bpp() const; 22 | void debayer(std::string pattern); 23 | cv::Mat to8Bit(const cv::Mat &source); 24 | class logger; 25 | private: 26 | std::string bayerPattern; 27 | bool debug_log; 28 | cv::Mat image; 29 | std::vector image_data; 30 | }; 31 | 32 | #endif 33 | 34 | -------------------------------------------------------------------------------- /backend/image_processing/image_processing.i: -------------------------------------------------------------------------------- 1 | %module image_processing 2 | %{ 3 | #include "image_processing.h" 4 | %} 5 | %include "std_string.i" 6 | %include "image_processing.h" 7 | -------------------------------------------------------------------------------- /backend/image_processing/update_swig: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$( dirname "$0")" 3 | 4 | swig -c++ -python image_processing.i 5 | 6 | -------------------------------------------------------------------------------- /backend/images/__init__.py: -------------------------------------------------------------------------------- 1 | from .images_db import ImagesDatabase, main_images_db, camera_images_db 2 | from .image import Image 3 | 4 | -------------------------------------------------------------------------------- /backend/indi/__init__.py: -------------------------------------------------------------------------------- 1 | from .server import Server 2 | from .device import Device 3 | from .indi_property import Property 4 | from .indi_service import INDIService 5 | from .indi_profile import INDIProfile 6 | 7 | -------------------------------------------------------------------------------- /backend/indi/filter_wheel.py: -------------------------------------------------------------------------------- 1 | from .device import Device 2 | from errors import NotFoundError 3 | 4 | class FilterWheel: 5 | def __init__(self, client, logger, device=None, filter_wheel=None): 6 | self.client = client 7 | self.logger = logger 8 | if device: 9 | self.device = device 10 | self.filter_wheel = [c for c in self.client.filter_wheels() if c.name == device.name] 11 | if not self.filter_wheel: 12 | raise NotFoundError('FilterWheel {} not found'.format(device.name)) 13 | self.filter_wheel = self.filter_wheel[0] 14 | 15 | elif filter_wheel: 16 | self.filter_wheel = filter_wheel 17 | self.device = Device(client, logger, name=filter_wheel.name) 18 | 19 | @property 20 | def id(self): 21 | return self.device.id 22 | 23 | def to_map(self): 24 | return { 25 | 'id': self.id, 26 | 'device': self.device.to_map(), 27 | 'connected': self.filter_wheel.connected, 28 | } 29 | 30 | 31 | def indi_sequence_filter_wheel(self): 32 | return self.filter_wheel 33 | -------------------------------------------------------------------------------- /backend/indi/indi_profile.py: -------------------------------------------------------------------------------- 1 | from models import random_id 2 | from errors import BadRequestError 3 | 4 | class INDIProfile: 5 | def __init__(self, id=None, name='', drivers=[]): 6 | self.name = name 7 | self.drivers = drivers 8 | self.id = random_id(id) 9 | 10 | @staticmethod 11 | def from_map(json): 12 | return INDIProfile(**json) 13 | 14 | def to_map(self): 15 | return { 16 | 'id': self.id, 17 | 'name': self.name, 18 | 'drivers': self.drivers, 19 | } 20 | 21 | def update(self, data): 22 | if 'name' not in data and 'drivers' not in data: 23 | raise BadRequestError('Invalid json: either name or drivers must be specified') 24 | if 'name' in data: 25 | self.name = data['name'] 26 | if 'drivers' in data: 27 | self.drivers = data['drivers'] 28 | 29 | 30 | -------------------------------------------------------------------------------- /backend/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .saved_list import SavedList 2 | from .model import * 3 | 4 | -------------------------------------------------------------------------------- /backend/models/model.py: -------------------------------------------------------------------------------- 1 | import six 2 | import functools 3 | import hashlib 4 | import uuid 5 | 6 | def random_id(id_or_none=None): 7 | if(id_or_none): 8 | return id_or_none 9 | return uuid.uuid4().hex 10 | 11 | 12 | def id_by_properties(properties): 13 | module_name = properties if isinstance(properties, six.string_types) else '-'.join(properties) 14 | m = hashlib.md5() 15 | m.update(str.encode(module_name)) 16 | return m.hexdigest() 17 | 18 | 19 | 20 | def with_attrs(attrs): 21 | def with_attrs_decorator(f): 22 | @functools.wraps(f) 23 | def f_wrapper(self, *args, **kwargs): 24 | for attr in attrs: 25 | if not hasattr(self, attr) or not getattr(self, attr): 26 | raise RuntimeError('Method called without {}'.format(attr)) 27 | return f(self, *args, **kwargs) 28 | return f_wrapper 29 | return with_attrs_decorator 30 | 31 | 32 | -------------------------------------------------------------------------------- /backend/network/__init__.py: -------------------------------------------------------------------------------- 1 | from .network_service import network_service 2 | -------------------------------------------------------------------------------- /backend/phd2/__init__.py: -------------------------------------------------------------------------------- 1 | from .phd2 import phd2 2 | -------------------------------------------------------------------------------- /backend/phd2/errors.py: -------------------------------------------------------------------------------- 1 | class PHD2ConnectionError(Exception): 2 | def __init__(self, message, parent=None): 3 | self.message = message 4 | self.parent = parent 5 | 6 | class PHD2MethodError(Exception): 7 | def __init__(self, method, message, code): 8 | self.method = method 9 | self.message = message 10 | self.code = code 11 | 12 | def get_message(self): 13 | return 'Error code {} calling PHD2 method {}: {}'.format(self.code, self.method, self.message) 14 | 15 | def __str__(self): 16 | return self.get_message() 17 | 18 | def __repr__(self): 19 | return self.__str__() 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /backend/phd2/phd2_client.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys 3 | import json 4 | 5 | async def stream_as_generator(loop, stream): 6 | reader = asyncio.StreamReader(loop=loop) 7 | reader_protocol = asyncio.StreamReaderProtocol(reader) 8 | await loop.connect_read_pipe(lambda: reader_protocol, stream) 9 | 10 | while True: 11 | line = await reader.readline() 12 | if not line: # EOF. 13 | break 14 | yield line 15 | 16 | 17 | 18 | async def write(writer): 19 | id = 0 20 | loop = asyncio.get_event_loop() 21 | async for line in stream_as_generator(loop, sys.stdin): 22 | id += 1 23 | line = line.decode().strip().split(',') 24 | method = json.dumps({ 'method': line[0], 'params': line[1:], 'id': id }) 25 | print(' >>>: {}'.format(method)) 26 | writer.write((method + '\r\n').encode()) 27 | 28 | 29 | 30 | 31 | async def read(reader): 32 | while True: 33 | data = await reader.readline() 34 | if not data: 35 | raise RuntimeError('Socket not connected') 36 | print(' <<<: {}'.format(data.decode().strip())) 37 | 38 | async def main(): 39 | stdin_queue = asyncio.Queue() 40 | reader, writer = await asyncio.open_connection('127.0.0.1', 4400) 41 | 42 | read_task = asyncio.create_task(read(reader)) 43 | write_task = asyncio.create_task(write(writer)) 44 | 45 | await asyncio.gather(*[read_task, write_task]) 46 | 47 | asyncio.run(main()) 48 | -------------------------------------------------------------------------------- /backend/phd2/phd2_process.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from errors import FailedMethodError, BadRequestError 3 | import os 4 | 5 | class PHD2Process: 6 | def __init__(self): 7 | self.__process = None 8 | 9 | def start(self, binary_path='/usr/bin/phd2', display=None): 10 | if self.running: 11 | raise BadRequestError('PHD2 is already running') 12 | try: 13 | if not binary_path or not display: 14 | raise BadRequestError('Both PHD2 binary path and display must be specified') 15 | 16 | phd2_env = os.environ.copy() 17 | phd2_env['DISPLAY'] = display 18 | self.__process = subprocess.Popen(['phd2'], env=phd2_env) 19 | return { 'started': self.running } 20 | except Exception as e: 21 | raise FailedMethodError(str(e)) 22 | 23 | @property 24 | def running(self): 25 | return self.__process and self.__process.poll() is None 26 | 27 | def stop(self): 28 | if not self.running: 29 | raise BadRequestError('PHD2 is not running') 30 | 31 | self.__process.terminate() 32 | try: 33 | self.__process.wait(timeout=5) 34 | return { 'stopped': not self.running } 35 | except Exception as e: 36 | raise FailedMethodError(str(e)) 37 | 38 | 39 | -------------------------------------------------------------------------------- /backend/platesolving/__init__.py: -------------------------------------------------------------------------------- 1 | from .platesolving import PlateSolving 2 | from .astrometry_downloader import AstrometryIndexDownloader 3 | 4 | -------------------------------------------------------------------------------- /backend/polar_alignment/.gitignore: -------------------------------------------------------------------------------- 1 | PolarAlignDemo 2 | -------------------------------------------------------------------------------- /backend/polar_alignment/__init__.py: -------------------------------------------------------------------------------- 1 | from .darv import darv 2 | from .pa_platesolving_drift import polar_alignment_platesolving_drift 3 | 4 | -------------------------------------------------------------------------------- /backend/polar_alignment/darv.py: -------------------------------------------------------------------------------- 1 | import time 2 | from errors import BadRequestError 3 | from system import controller 4 | from utils.threads import start_thread 5 | 6 | class DARV: 7 | def shoot(self, guider_id, exposure, initial_pause): 8 | if exposure < 30: 9 | raise BadRequestError('Exposure should be at least 30 seconds') 10 | guider = [g for g in controller.indi_server.guiders() if g.id == guider_id] 11 | if not guider: 12 | raise BadRequestError('Guider not found: {} (guiders: {})'.format(guider_id, ', '.join([g.id for g in controller.indi_server.guiders()]))) 13 | start_thread(self.__darv, guider[0], exposure, initial_pause) 14 | return { 'started': 'ok' } 15 | 16 | def __darv(self, guider, exposure, initial_pause): 17 | step_exposure = (exposure - initial_pause) / 2.0 18 | time.sleep(initial_pause) 19 | guider.guide('west', step_exposure) 20 | time.sleep(step_exposure) 21 | guider.guide('east', step_exposure) 22 | time.sleep(step_exposure) 23 | 24 | 25 | 26 | 27 | darv = DARV() 28 | 29 | -------------------------------------------------------------------------------- /backend/polar_alignment/pa_platesolving_drift.py: -------------------------------------------------------------------------------- 1 | from .pa_platesolving_capture import polar_alignment_platesolving_capture 2 | from errors import BadRequestError 3 | 4 | class PolarAlignmentPlatesolvingDrift: 5 | def first_capture(self, *args, **kwargs): 6 | polar_alignment_platesolving_capture.reset() 7 | return polar_alignment_platesolving_capture.capture(*args, **kwargs) 8 | 9 | def second_capture(self, *args, **kwargs): 10 | return polar_alignment_platesolving_capture.capture(*args, **kwargs) 11 | 12 | def get_drift(self): 13 | if len(polar_alignment_platesolving_capture.captures) != 2: 14 | raise BadRequestError('You need to execute two captures') 15 | dec_drift = polar_alignment_platesolving_capture.coordinates(0).dec - polar_alignment_platesolving_capture.coordinates(1).dec 16 | return { 'declination_drift': dec_drift.deg } 17 | 18 | 19 | 20 | polar_alignment_platesolving_drift = PolarAlignmentPlatesolvingDrift() 21 | 22 | -------------------------------------------------------------------------------- /backend/redis_server.py: -------------------------------------------------------------------------------- 1 | from static_settings import StaticSettings, syslog 2 | from service import Service 3 | import sys 4 | import os 5 | 6 | class RedisServer: 7 | def __init__(self): 8 | self.port = Service.find_free_port() 9 | self.service = Service('redis', StaticSettings.REDIS_SERVICE_LOGS) 10 | 11 | def start(self): 12 | os.makedirs(StaticSettings.CONFIG_DIR, exist_ok=True) 13 | syslog('**** Initialising Redis server on port {}'.format(self.port)) 14 | if self.service.is_running(): 15 | raise RuntimeError('Service is already running') 16 | arguments = [ 17 | '--port', str(self.port), 18 | '--dbfilename', StaticSettings.REDIS_DB_FILENAME, 19 | '--dir', StaticSettings.CONFIG_DIR, 20 | '--appendfsync', 'everysec', 21 | '--daemonize', 'no', 22 | '--always-show-logo', 'no', 23 | '--save', '60', '1', 24 | '--save', '30', '10', 25 | '--save', '15', '1000', 26 | ] 27 | self.service.start('redis-server', arguments) 28 | 29 | def stop(self, *args, **kwargs): 30 | self.service.stop(*args, **kwargs) 31 | 32 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | pyindi-client>=0.2.3 2 | astropy 3 | flask 4 | flask-sse 5 | gunicorn 6 | gevent 7 | greenlet 8 | numpy 9 | pillow >= 7.0.0 10 | six 11 | redis 12 | requests 13 | pkgconfig 14 | astroquery 15 | healpy 16 | svgwrite 17 | python-networkmanager 18 | -------------------------------------------------------------------------------- /backend/sequences/__init__.py: -------------------------------------------------------------------------------- 1 | from .sequence import Sequence 2 | from .sequence_job import SequenceJob 3 | from .sequences_runner import SequencesRunner 4 | from .shot import Shot 5 | -------------------------------------------------------------------------------- /backend/sequences/autoguider.py: -------------------------------------------------------------------------------- 1 | from app import logger 2 | from phd2 import phd2 3 | from system import settings 4 | 5 | class Autoguider: 6 | def dither(self): 7 | if settings.autoguider_engine == 'phd2' and settings.dithering_enabled: 8 | self.dither_phd2() 9 | 10 | def dither_phd2(self): 11 | if phd2.is_guiding(): 12 | phd2.dither(settings.dithering_pixels, settings.autoguider_settle_pixels, settings.autoguider_settle_time, settings.autoguider_settle_timeout, ra_only=settings.dithering_ra_only) 13 | 14 | 15 | 16 | autoguider = Autoguider() 17 | 18 | -------------------------------------------------------------------------------- /backend/sequences/filter_wheel_sequence_job.py: -------------------------------------------------------------------------------- 1 | from app import logger 2 | import time 3 | 4 | 5 | class FilterWheelSequenceJob: 6 | def __init__(self, data): 7 | self.filter_number = data.get('filterNumber') 8 | 9 | def to_map(self): 10 | return { 11 | 'filterNumber': self.filter_number, 12 | } 13 | 14 | def run(self, server, devices, root_path, event_listener, on_update, index): 15 | wheel = devices['filter_wheel'] 16 | retry = 0 17 | while True: 18 | try: 19 | self.__change_filter(wheel) 20 | return 21 | except RuntimeError as e: 22 | if retry == 5: 23 | raise e 24 | retry += 1 25 | logger.error('{} - retrying ({} of 5)'.format(e, retry)) 26 | time.sleep(2 * retry) 27 | 28 | 29 | def __change_filter(self, wheel): 30 | logger.info('Changing filter to {} on filter wheel {}'.format(self.filter_number, wheel)) 31 | wheel.indi_sequence_filter_wheel().set_filter_number(self.filter_number) 32 | 33 | if wheel.indi_sequence_filter_wheel().current_filter()[0] != self.filter_number: 34 | raise RuntimeError('Error changing filter wheel to filter {}'.format(self.filter_number)) 35 | 36 | 37 | 38 | def reset(self, remove_files=False): 39 | pass 40 | 41 | -------------------------------------------------------------------------------- /backend/sequences/pause_sequence_job.py: -------------------------------------------------------------------------------- 1 | from app import logger 2 | import time 3 | from .stop_sequence import StopSequence 4 | 5 | 6 | class PauseSequenceJob: 7 | def __init__(self, data): 8 | self.id = data['id'] 9 | self.autoresume = data.get('autoresume', 0) 10 | self.show_notification = data.get('showNotification', False) 11 | self.notification_message = data.get('notificationMessage') 12 | 13 | 14 | def to_map(self): 15 | return { 16 | 'autoresume': self.autoresume, 17 | 'showNotification': self.show_notification, 18 | 'notificationMessage': self.notification_message, 19 | } 20 | 21 | def run(self, server, devices, root_path, event_listener, on_update, index): 22 | if self.show_notification: 23 | event_listener.on_sequence_paused(self.id, self.notification_message, self.autoresume) 24 | if self.autoresume: 25 | time.sleep(self.autoresume) 26 | else: 27 | raise StopSequence('stopping sequence by sequence job') 28 | 29 | def stop(self): 30 | return 'finished' 31 | 32 | def reset(self, remove_files=False): 33 | pass 34 | 35 | -------------------------------------------------------------------------------- /backend/sequences/property_sequence_job.py: -------------------------------------------------------------------------------- 1 | from indi import Property 2 | from app import logger 3 | import time 4 | 5 | class PropertySequenceJob: 6 | def __init__(self, data): 7 | self.device = data['device'] 8 | self.property = data['property'] 9 | self.group = data.get('group') 10 | self.values = data['values'] 11 | self.wait = data.get('wait', 0) 12 | 13 | def to_map(self): 14 | return { 15 | 'device': self.device, 16 | 'property': self.property, 17 | 'values': self.values, 18 | 'group': self.group, 19 | 'wait': self.wait, 20 | } 21 | 22 | def run(self, server, devices, root_path, event_listener, on_update, index): 23 | property = server.property(device=self.device, name=self.property) 24 | sync = self.wait != 0 25 | timeout = self.wait if self.wait > 0 else 999999999 26 | property.set_values(self.values, sync=sync, timeout=timeout) 27 | 28 | def reset(self, remove_files=False): 29 | pass 30 | 31 | -------------------------------------------------------------------------------- /backend/sequences/stop_sequence.py: -------------------------------------------------------------------------------- 1 | class StopSequence(Exception): 2 | def __init__(self, message): 3 | Exception.__init__(self, message) 4 | self.message = message 5 | -------------------------------------------------------------------------------- /backend/skychart/__init__.py: -------------------------------------------------------------------------------- 1 | from .skychart import skychart 2 | -------------------------------------------------------------------------------- /backend/skychart/stars.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/backend/skychart/stars.db -------------------------------------------------------------------------------- /backend/skychart/utils/import_json_to_sql.py: -------------------------------------------------------------------------------- 1 | import healpy 2 | import sqlite3 3 | import json 4 | import sys 5 | import os 6 | import gzip 7 | 8 | if len(sys.argv) < 2 or not os.path.exists(sys.argv[1]): 9 | raise RuntimeError('Usage: {} file.json'.format(sys.argv[0])) 10 | 11 | con = sqlite3.connect("stars.db") 12 | con.row_factory = sqlite3.Row 13 | 14 | cur = con.cursor() 15 | #cur.execute('DROP TABLE IF EXISTS stars') 16 | create_table_stmt = 'CREATE TABLE IF NOT EXISTS stars (ra real, dec real, mag real, hipparcos integer, hd integer, tycho text, bayer text, constellation text, flamsteed text, names text, healpix_128 integer)' 17 | cur.execute(create_table_stmt) 18 | 19 | filename = sys.argv[1] 20 | stars = {} 21 | 22 | if filename.endswith('.gz'): 23 | with gzip.open(filename) as j: 24 | stars = json.load(j) 25 | else: 26 | with open(filename) as j: 27 | stars = json.load(j) 28 | 29 | for star in stars.values(): 30 | cur.execute('INSERT INTO stars (ra, dec, mag, hipparcos, hd, tycho, bayer, constellation, flamsteed, names, healpix_128) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', ( 31 | star['RA'], 32 | star['DEC'], 33 | star['mag'], 34 | star.get('HIP'), 35 | star.get('HD'), 36 | star.get('TYC'), 37 | star.get('Bayer'), 38 | star.get('Constellation'), 39 | star.get('Flamsteed'), 40 | ';'.join(star.get('names', [])), 41 | int(healpy.ang2pix(128, star['RA'], star['DEC'], nest=True, lonlat=True)) 42 | )) 43 | con.commit() 44 | con.close() 45 | 46 | -------------------------------------------------------------------------------- /backend/skychart/utils/split_stars_json.py: -------------------------------------------------------------------------------- 1 | import json 2 | with open('stars.json') as f: 3 | stars = json.load(f) 4 | 5 | stars_mag7, stars_mag9, stars_over_mag9 = {}, {}, {} 6 | for key, star in stars.items(): 7 | if star['mag'] <= 7: 8 | stars_mag7[key] = star 9 | elif star['mag'] <= 9: 10 | stars_mag9[key] = star 11 | else: 12 | stars_over_mag9[key] = star 13 | 14 | with open('stars_mag7.json', 'w') as f: 15 | json.dump(stars_mag7, f) 16 | with open('stars_mag9.json', 'w') as f: 17 | json.dump(stars_mag9, f) 18 | with open('stars_over_mag9.json', 'w') as f: 19 | json.dump(stars_over_mag9, f) 20 | -------------------------------------------------------------------------------- /backend/skychart/utils/stars_mag7.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/backend/skychart/utils/stars_mag7.json.gz -------------------------------------------------------------------------------- /backend/skychart/utils/stars_mag9.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/backend/skychart/utils/stars_mag9.json.gz -------------------------------------------------------------------------------- /backend/skychart/utils/stars_over_mag9.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/backend/skychart/utils/stars_over_mag9.json.gz -------------------------------------------------------------------------------- /backend/start-debug-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DEV_MODE=1 SAMPLE_FITS_PATH="${SAMPLE_FITS_PATH:-"$PWD/dev_sample_fits"}" ./start-server --log-level debug --access-logfile "${ACCESS_LOG_FILE:-./access.log}" "$@" 3 | 4 | -------------------------------------------------------------------------------- /backend/static_settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | def syslog(s): 5 | sys.stderr.write('{}\n'.format(s)) 6 | sys.stderr.flush() 7 | 8 | 9 | 10 | class StaticSettings: 11 | CONFIG_DIR = os.path.join(os.environ['HOME'], '.config', 'AstroPhotoPlus') 12 | SYSTEM_CONFIG_DIR = os.environ.get('SYSTEM_CONFDIR', '/etc') 13 | 14 | @staticmethod 15 | def build_path(components, root=None, isdir=False): 16 | path = os.path.join(root if root else os.environ['HOME'], *components) 17 | os.makedirs(path if isdir else os.path.dirname(path), exist_ok=True) 18 | return path 19 | 20 | StaticSettings.REDIS_DB_FILENAME = 'redis.rdb' 21 | StaticSettings.REDIS_SERVICE_LOGS = StaticSettings.build_path(['.cache', 'AstroPhotoPlus', 'logs', 'redis_service'], isdir=True) 22 | StaticSettings.INDI_SERVICE_LOGS = StaticSettings.build_path(['.cache', 'AstroPhotoPlus', 'logs', 'indi_service'], isdir=True) 23 | StaticSettings.ASTROMETRY_TEMP_PATH = StaticSettings.build_path(['.cache', 'AstroPhotoPlus', 'astrometry_cache'], isdir=True) 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/system/__init__.py: -------------------------------------------------------------------------------- 1 | from .settings import settings, syslog 2 | from .commands import commands 3 | from .server_sent_events import sse 4 | from .event_listener import event_listener 5 | from .controller import controller 6 | from .displays import x11_displays 7 | -------------------------------------------------------------------------------- /backend/system/displays.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def x11_display_object(socket): 4 | if socket.startswith('X'): 5 | return { 6 | 'socket': socket, 7 | 'display_number': socket[1:], 8 | 'display_variable': ':{}'.format(socket[1:]), 9 | } 10 | return None 11 | 12 | def x11_displays(): 13 | objects = [x11_display_object(socket) for socket in os.listdir('/tmp/.X11-unix')] 14 | return [x11_display for x11_display in objects if x11_display] 15 | 16 | -------------------------------------------------------------------------------- /backend/utils/benchmark_log.py: -------------------------------------------------------------------------------- 1 | from app import logger 2 | import time 3 | 4 | 5 | class BenchmarkLog: 6 | def __init__(self): 7 | self.benchs = {} 8 | def reset(self, tag): 9 | self.benchs[tag] = time.time() 10 | 11 | def log(self, tag, logstring, reset=False): 12 | started = self.benchs.get(tag, time.time()) 13 | if reset: 14 | started = time.time() 15 | self.benchs[tag] = time.time() 16 | logger.debug('[{:>30s}] [{:010.3f}] {}'.format(tag, self.benchs[tag] - started, logstring)) 17 | 18 | benchlogger = BenchmarkLog() 19 | 20 | -------------------------------------------------------------------------------- /backend/utils/cleanup_venv.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | def clean_environment(): 4 | env = dict(os.environ) 5 | del env['VIRTUAL_ENV'] 6 | del env['PYTHONPATH'] 7 | env['PATH'] = os.pathsep.join([p for p in env['PATH'].split(os.pathsep) if os.path.join('AstroPhotoPlus', 'python-venv') not in p]) 8 | return env 9 | 10 | 11 | -------------------------------------------------------------------------------- /backend/utils/mp.py: -------------------------------------------------------------------------------- 1 | import multiprocessing 2 | import queue 3 | 4 | import time 5 | 6 | def mp_queue(maxsize=0): 7 | return multiprocessing.Queue(maxsize) 8 | 9 | def mp_process(target, *args, **kwargs): 10 | return multiprocessing.Process(target=target, args=args, kwargs=kwargs) 11 | 12 | def mp_start_process(target, *args, **kwargs): 13 | process = mp_process(target, *args, **kwargs) 14 | process.start() 15 | return process 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /backend/utils/system_time.py: -------------------------------------------------------------------------------- 1 | import time 2 | import subprocess 3 | from errors import FailedMethodError 4 | 5 | def set_ntp(enabled): 6 | return subprocess.run(['sudo', 'timedatectl', 'set-ntp', 'true' if enabled else 'false']).returncode == 0 7 | 8 | def get_timestamp(): 9 | return { 'utc_timestamp': time.time() } 10 | 11 | def set_timestamp(timestamp): 12 | timestamp = int(timestamp) 13 | if not __set_timestamp_timedatectl(timestamp): 14 | if not __set_timestamp_date(timestamp): 15 | raise FailedMethodError('Unable to set system time') 16 | return get_timestamp() 17 | 18 | def __set_timestamp_timedatectl(timestamp): 19 | return set_ntp(False) and subprocess.run(['sudo', 'timedatectl', 'set-time', '@{}'.format(timestamp)]).returncode == 0 20 | 21 | 22 | def __set_timestamp_date(timestamp): 23 | return subprocess.run(['sudo', 'date', '-s', '@{}'.format(timestamp)]).returncode == 0 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/utils/test_processworker.py: -------------------------------------------------------------------------------- 1 | import mp 2 | 3 | @mp.ProcessWorker 4 | class Foo: 5 | def __init__(self, tag): 6 | self.tag = tag 7 | 8 | def print_foo(self): 9 | print('[{}]: foo'.format(self.tag)) 10 | 11 | def print_args(self, arg1, arg2=''): 12 | print('[{}]: {}, {}'.format(self.tag, arg1, arg2)) 13 | 14 | def raise_error(self): 15 | raise RuntimeError('hello world') 16 | 17 | def get_tag(self): 18 | return self.tag 19 | 20 | def on_run(self): 21 | pass 22 | 23 | def set_tag(self, tag): 24 | self.tag = tag 25 | 26 | foo = Foo('Foo-Tag') 27 | foo.start() 28 | 29 | -------------------------------------------------------------------------------- /backend/utils/threads.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import queue 3 | 4 | def thread_queue(maxsize=0): 5 | return queue.Queue(maxsize) 6 | 7 | def new_thread(target, *args, **kwargs): 8 | return threading.Thread(target=target, args=args, kwargs=kwargs) 9 | 10 | def start_thread(target, *args, **kwargs): 11 | t = new_thread(target, *args, **kwargs) 12 | t.start() 13 | return t 14 | 15 | def lock(timeout=-1): 16 | return threading.Lock() 17 | 18 | -------------------------------------------------------------------------------- /backend/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.1", 3 | "version_major": "1", 4 | "version_minor": "0", 5 | "version_patch": "1" 6 | } -------------------------------------------------------------------------------- /config/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(ETC_COMMANDS_FILE ${CONFDIR}/AstroPhotoPlus-commands.json) 2 | 3 | add_subdirectory(linux_generic) 4 | add_subdirectory(debian_based) 5 | install(DIRECTORY dev-webserver-conf prod-webserver-conf DESTINATION share/AstroPhotoPlus/config) 6 | 7 | -------------------------------------------------------------------------------- /config/debian_based/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(DEBIAN_COMMANDS_FILE ${CMAKE_INSTALL_PREFIX}/share/AstroPhotoPlus/config/debian_based/etc_AstroPhotoPlus-commands.json) 2 | 3 | configure_file(commands.json.in ${CMAKE_CURRENT_BINARY_DIR}/commands.json) 4 | configure_file(etc_AstroPhotoPlus-commands.json.in ${CMAKE_CURRENT_BINARY_DIR}/etc_AstroPhotoPlus-commands.json) 5 | configure_file(postinst.in ${CMAKE_CURRENT_BINARY_DIR}/postinst @ONLY) 6 | configure_file(postrm.in ${CMAKE_CURRENT_BINARY_DIR}/postrm @ONLY) 7 | 8 | set( 9 | DEBIAN_SOURCES 10 | ${CMAKE_CURRENT_BINARY_DIR}/commands.json 11 | ${CMAKE_CURRENT_BINARY_DIR}/etc_AstroPhotoPlus-commands.json 12 | ) 13 | install(FILES ${DEBIAN_SOURCES} DESTINATION share/AstroPhotoPlus/config/debian_based) 14 | install(PROGRAMS astrophotoplus-deb-updater DESTINATION share/AstroPhotoPlus/config/debian_based) 15 | set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_BINARY_DIR}/postinst;${CMAKE_CURRENT_BINARY_DIR}/postrm" CACHE INTERNAL "debian scripts") 16 | 17 | 18 | -------------------------------------------------------------------------------- /config/debian_based/commands.json.in: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "include", 4 | "filename": "${CMAKE_INSTALL_PREFIX}/share/AstroPhotoPlus/config/linux_generic/commands.json" 5 | }, 6 | { 7 | "id": "10", 8 | "name": "Update AstroPhoto Plus", 9 | "arguments": ["sudo", "${CMAKE_INSTALL_PREFIX}/share/AstroPhotoPlus/config/debian_based/astrophotoplus-deb-updater", "update"], 10 | "category": "Updates", 11 | "check": ["sudo", "${CMAKE_INSTALL_PREFIX}/share/AstroPhotoPlus/config/debian_based/astrophotoplus-deb-updater", "check-update-available"], 12 | "confirmation_message": "This will restart AstroPhotoPlus, and any job in progress. Are you sure?", 13 | "ui_properties": { 14 | "icon": "sync" 15 | } 16 | }, 17 | { 18 | "id": "11", 19 | "name": "Update System", 20 | "arguments": ["sudo", "bash", "-c", "apt-get update && apt-get dist-upgrade -y"], 21 | "category": "Updates", 22 | "check": ["sudo", "bash", "-c", "apt-get check"], 23 | "ui_properties": { 24 | "icon": "sync" 25 | } 26 | } 27 | ] 28 | -------------------------------------------------------------------------------- /config/debian_based/etc_AstroPhotoPlus-commands.json.in: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "include", 4 | "filename": "${CMAKE_INSTALL_PREFIX}/share/AstroPhotoPlus/config/debian_based/commands.json" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /config/debian_based/postinst.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if ! [ -r "@ETC_COMMANDS_FILE@" ]; then 4 | conf_file="@DEBIAN_COMMANDS_FILE@" 5 | echo "Linking $conf_file to @ETC_COMMANDS_FILE@" 6 | ln -sf "$conf_file" "@ETC_COMMANDS_FILE@" 7 | fi 8 | 9 | -------------------------------------------------------------------------------- /config/debian_based/postrm.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Removing @ETC_COMMANDS_FILE@" 3 | rm -f "@ETC_COMMANDS_FILE@" 4 | 5 | -------------------------------------------------------------------------------- /config/dev-webserver-conf/nginx/astrophotoplus.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 3080; 3 | server_name localhost; 4 | client_max_body_size 200M; 5 | 6 | location / { 7 | proxy_pass http://127.0.0.1:3000; 8 | proxy_read_timeout 99999s; 9 | add_header Last-Modified $date_gmt; 10 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 11 | } 12 | 13 | location /api { 14 | proxy_pass http://127.0.0.1:5000; 15 | proxy_read_timeout 99999s; 16 | add_header Last-Modified $date_gmt; 17 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 18 | } 19 | 20 | location /api/events { 21 | gzip off; 22 | proxy_pass http://127.0.0.1:5000/api/events; 23 | proxy_read_timeout 99999s; 24 | proxy_buffering off; 25 | add_header Last-Modified $date_gmt; 26 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /config/linux_generic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | install(FILES commands.json DESTINATION share/AstroPhotoPlus/config/linux_generic) 2 | -------------------------------------------------------------------------------- /config/linux_generic/commands.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "name": "Shutdown", 5 | "arguments": ["sudo", "systemctl", "poweroff"], 6 | "category": "System", 7 | "confirmation_message": "Are you sure you want to shutdown?", 8 | "check": ["sudo", "systemctl", "get-default"], 9 | "ui_properties": { 10 | "icon": "shutdown", 11 | "color": "red" 12 | } 13 | }, 14 | { 15 | "id": "2", 16 | "name": "Reboot", 17 | "confirmation_message": "Are you sure you want to reboot?", 18 | "arguments": ["sudo", "systemctl", "reboot"], 19 | "check": ["sudo", "systemctl", "get-default"], 20 | "category": "System", 21 | "ui_properties": { 22 | "icon": "redo", 23 | "color": "red" 24 | } 25 | }, 26 | 27 | { 28 | "id": "5", 29 | "name": "Start VNC server", 30 | "arguments": ["tigervncserver", "-SecurityTypes", "None", "-localhost", "no", "--I-KNOW-THIS-IS-INSECURE", "-noxstartup"], 31 | "check": ["bash", "-c", "which tigervncserver && ! tigervncserver -kill --dry-run"], 32 | "category": "System", 33 | "ui_properties": { 34 | "icon": "computer" 35 | } 36 | }, 37 | { 38 | "id": "6", 39 | "name": "Stop VNC server", 40 | "arguments": ["tigervncserver", "-kill"], 41 | "check": ["tigervncserver", "-kill", "--dry-run"], 42 | "category": "System", 43 | "ui_properties": { 44 | "icon": "computer" 45 | } 46 | } 47 | 48 | ] 49 | -------------------------------------------------------------------------------- /config/prod-webserver-conf/apache2/astrophotoplus.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin webmaster@localhost 3 | DocumentRoot ${FRONTEND_ROOTDIR} 4 | ProxyPass "/api/" "http://127.0.0.1:5000/api/" 5 | ProxyPassReverse "/api/" "http://127.0.0.1:5000/api/" 6 | ProxyTimeout 99999 7 | Timeout 99999 8 | 9 | ErrorLog ${APACHE_LOG_DIR}/error.log 10 | CustomLog ${APACHE_LOG_DIR}/access.log combined 11 | 12 | RewriteEngine on 13 | RewriteCond %{REQUEST_FILENAME} -f [OR] 14 | RewriteCond %{REQUEST_FILENAME} -d 15 | RewriteRule ^ - [L] 16 | # Rewrite everything else to index.html to allow html5 state links 17 | RewriteRule ^ index.html [L] 18 | 19 | Require all granted 20 | 21 | 22 | 23 | RewriteEngine off 24 | 25 | 26 | -------------------------------------------------------------------------------- /config/prod-webserver-conf/nginx/astrophotoplus.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | client_max_body_size 200M; 4 | 5 | location / { 6 | root ${FRONTEND_ROOTDIR}; 7 | try_files $uri /index.html; 8 | add_header Last-Modified $date_gmt; 9 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 10 | } 11 | 12 | location /api { 13 | proxy_pass http://127.0.0.1:5000; 14 | proxy_read_timeout 99999s; 15 | add_header Last-Modified $date_gmt; 16 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 17 | } 18 | 19 | location /api/events { 20 | gzip off; 21 | proxy_pass http://127.0.0.1:5000/api/events; 22 | proxy_read_timeout 99999s; 23 | proxy_buffering off; 24 | add_header Last-Modified $date_gmt; 25 | add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /docker-compose/dev-server/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=AstroPhotoPlus-dev 2 | WEB_THREADS=4 3 | EXTERNAL_WEB_PORT=3000 4 | ENABLE_PTVSD=false 5 | 6 | -------------------------------------------------------------------------------- /docker-compose/dev-server/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic 2 | VOLUME /app 3 | 4 | ENV PYINDI_CLIENT_VERSION=0.2.2 5 | 6 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y swig build-essential libnova-dev python3 python3-pip python3-venv dirmngr swig build-essential libnova-dev libcfitsio-dev zlib1g-dev curl libopencv-dev libccfits-dev 7 | COPY indi-ppa.list /etc/apt/sources.list.d/ 8 | RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3F33A288 && apt-get update && apt-get install -y libindi-dev astrometry.net indi-bin gsc 9 | RUN curl -o pyindi-client-$PYINDI_CLIENT_VERSION.tar.gz "https://files.pythonhosted.org/packages/3d/2c/66e96ab58e2cb5137986c53d8747edc8fb3001340120c62a4dab998f0a2b/pyindi-client-${PYINDI_CLIENT_VERSION}.tar.gz" && tar xzf pyindi-client-$PYINDI_CLIENT_VERSION.tar.gz && cd pyindi-client-$PYINDI_CLIENT_VERSION && python3 setup.py install && cd / && rm -rf pyindi-client-$PYINDI_CLIENT_VERSION 10 | VOLUME /usr/local/lib/python3.6/dist-packages 11 | 12 | WORKDIR /app 13 | ENV PYTHONPATH="/app/indi-lite-tools" LANG=C.UTF-8 LC_ALL=C.UTF-8 WEB_THREADS=4 ENABLE_PTVSD=false 14 | COPY astrophotoplus-wifi-helper /usr/bin 15 | COPY shutdown-docker-helper /usr/bin 16 | RUN mkdir -p /root/.config/AstroPhotoPlus 17 | COPY commands.json /root/.config/AstroPhotoPlus/commands.json 18 | COPY entrypoint /entrypoint 19 | CMD /entrypoint 20 | -------------------------------------------------------------------------------- /docker-compose/dev-server/backend/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | set -x 4 | # Old stuff, it should be obsolete by now with the new start-server 5 | # requirements_checksum="$( md5sum requirements.txt | cut -d' ' -f1 )" 6 | # last_requirements_checksum_file="/usr/local/lib/python3.6/dist-packages/last_requirements_checksum" 7 | # if [ "$requirements_checksum" != "$( cat "$last_requirements_checksum_file" 2>/dev/null )" ]; then 8 | # pip3 install -r requirements.txt 9 | # fi 10 | # echo "$requirements_checksum" > "$last_requirements_checksum_file" 11 | ACCESS_LOG_FILE=/tmp/access.log ./start-debug-server 12 | -------------------------------------------------------------------------------- /docker-compose/dev-server/backend/indi-ppa.list: -------------------------------------------------------------------------------- 1 | deb http://ppa.launchpad.net/mutlaqja/ppa/ubuntu bionic main 2 | deb-src http://ppa.launchpad.net/mutlaqja/ppa/ubuntu bionic main 3 | -------------------------------------------------------------------------------- /docker-compose/dev-server/backend/shutdown-docker-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "$0 $@ $( date) - stdout test" 3 | echo "$0 $@ $( date) - stderr test" >&2 4 | 5 | -------------------------------------------------------------------------------- /docker-compose/dev-server/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | nginx: 4 | image: nginx:stable-alpine 5 | ports: 6 | - "${EXTERNAL_WEB_PORT}:80" 7 | volumes: 8 | - ./nginx-conf.d:/etc/nginx/conf.d:ro 9 | depends_on: 10 | - frontend 11 | - backend 12 | frontend: 13 | build: frontend 14 | volumes: 15 | - ../../frontend:/app 16 | - node_modules:/app/node_modules 17 | expose: 18 | - "3000" 19 | backend: 20 | build: backend 21 | volumes: 22 | - ../../backend:/app 23 | expose: 24 | - "5000" 25 | - "5678" 26 | ports: 27 | - "5678:5678" 28 | environment: 29 | - INDI_SERVER_HOST 30 | - INDI_SERVER_PORT 31 | - WEB_THREADS 32 | - REDIS_SERVER=redis 33 | - ENABLE_PTVSD 34 | - ASTROPHOTOPLUS_WEB_PORT=${EXTERNAL_WEB_PORT} 35 | redis: 36 | image: redis:alpine 37 | command: redis-server --appendonly yes 38 | volumes: 39 | node_modules: 40 | -------------------------------------------------------------------------------- /docker-compose/dev-server/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package*.json 3 | -------------------------------------------------------------------------------- /docker-compose/dev-server/frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | VOLUME /app 3 | WORKDIR /app 4 | RUN apk --no-cache add git python3 5 | VOLUME /app/node_modules 6 | VOLUME /app/public/themes 7 | VOLUME /app/public/celestial 8 | COPY entrypoint / 9 | ENV HOST=0.0.0.0 10 | CMD /entrypoint 11 | 12 | -------------------------------------------------------------------------------- /docker-compose/dev-server/frontend/entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -x 3 | package_json_checksum="$( md5sum package.json | cut -d' ' -f1 )" 4 | 5 | if [ "$package_json_checksum" != "$( cat node_modules/last_package_json_checksum 2>/dev/null )" ]; then 6 | yarn install 7 | fi 8 | 9 | echo "$package_json_checksum" > "node_modules/last_package_json_checksum" 10 | 11 | yarn start 12 | 13 | 14 | -------------------------------------------------------------------------------- /docker-compose/dev-server/nginx-conf.d/astrophotoplus.conf: -------------------------------------------------------------------------------- 1 | ## 2 | 3 | server { 4 | listen 80; 5 | client_max_body_size 200M; 6 | 7 | location / { 8 | proxy_pass http://frontend:3000; 9 | proxy_read_timeout 99999s; 10 | } 11 | 12 | location /api { 13 | proxy_pass http://backend:5000; 14 | proxy_read_timeout 99999s; 15 | } 16 | 17 | location /api/events { 18 | gzip off; 19 | proxy_pass http://backend:5000/api/events; 20 | proxy_read_timeout 99999s; 21 | proxy_buffering off; 22 | } 23 | 24 | location /sockjs-node/ { 25 | proxy_pass http://frontend:3000; 26 | proxy_http_version 1.1; 27 | proxy_set_header Upgrade $http_upgrade; 28 | proxy_set_header Connection "Upgrade"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"], 3 | "env": { 4 | "production": { 5 | "plugins": ["transform-remove-console"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /frontend/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /frontend/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # production 10 | build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # OSX 24 | # 25 | .DS_Store 26 | 27 | # Xcode 28 | # 29 | build/ 30 | *.pbxuser 31 | !default.pbxuser 32 | *.mode1v3 33 | !default.mode1v3 34 | *.mode2v3 35 | !default.mode2v3 36 | *.perspectivev3 37 | !default.perspectivev3 38 | xcuserdata 39 | *.xccheckout 40 | *.moved-aside 41 | DerivedData 42 | *.hmap 43 | *.ipa 44 | *.xcuserstate 45 | project.xcworkspace 46 | 47 | # Android/IntelliJ 48 | # 49 | build/ 50 | .idea 51 | .gradle 52 | local.properties 53 | *.iml 54 | 55 | # node.js 56 | # 57 | node_modules/ 58 | npm-debug.log 59 | yarn-error.log 60 | 61 | # BUCK 62 | buck-out/ 63 | \.buckd/ 64 | *.keystore 65 | 66 | # fastlane 67 | # 68 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 69 | # screenshots whenever they are needed. 70 | # For more information about the recommended setup visit: 71 | # https://docs.fastlane.tools/best-practices/source-control/ 72 | 73 | */fastlane/report.xml 74 | */fastlane/Preview.html 75 | */fastlane/screenshots 76 | 77 | # Bundle artifact 78 | *.jsbundle 79 | 80 | # librares static files 81 | public/celestial 82 | public/themes 83 | 84 | src/semantic/ 85 | gulpfile.js 86 | -------------------------------------------------------------------------------- /frontend/.vimrc: -------------------------------------------------------------------------------- 1 | let g:ctrlp_working_path_mode = 'a' 2 | let g:ctrlp_user_command = 'cd %s && git ls-files -co --exclude-standard' 3 | -------------------------------------------------------------------------------- /frontend/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /frontend/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_program(YARN yarn) 2 | file( 3 | COPY ${CMAKE_CURRENT_SOURCE_DIR} 4 | DESTINATION ${CMAKE_BINARY_DIR} 5 | PATTERN node_modules EXCLUDE 6 | PATTERN build EXCLUDE 7 | PATTERN webserver-conf/nginx/* EXCLUDE 8 | ) 9 | 10 | set(FRONTEND_ROOTDIR ${ASTRO_PHOTO_PLUS_INSTALL_PREFIX}/frontend) 11 | configure_file(${CMAKE_SOURCE_DIR}/config/prod-webserver-conf/nginx/astrophotoplus.conf ${CMAKE_CURRENT_BINARY_DIR}/webserver-conf/nginx/astrophotoplus.conf) 12 | configure_file(${CMAKE_SOURCE_DIR}/config/prod-webserver-conf/apache2/astrophotoplus.conf ${CMAKE_CURRENT_BINARY_DIR}/webserver-conf/apache2/astrophotoplus.conf) 13 | 14 | add_custom_target( 15 | yarn-dependencies 16 | COMMAND yarn install 17 | ) 18 | 19 | add_custom_target( 20 | frontend-bundle ALL 21 | COMMAND yarn run build 22 | DEPENDS yarn-dependencies 23 | ) 24 | 25 | install( 26 | DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/build/ 27 | DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/frontend 28 | ) 29 | 30 | install( 31 | DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/webserver-conf 32 | DESTINATION ${ASTRO_PHOTO_PLUS_HOME}/frontend 33 | ) 34 | -------------------------------------------------------------------------------- /frontend/android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.astrophotoplus_frontend", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.astrophotoplus_frontend", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /frontend/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /frontend/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/AntDesign.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/AntDesign.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Entypo.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Entypo.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/EvilIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/EvilIcons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Feather.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Feather.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/FontAwesome5_Brands.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/FontAwesome5_Regular.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/FontAwesome5_Solid.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Foundation.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Foundation.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Ionicons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/MaterialCommunityIcons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/MaterialIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/MaterialIcons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Octicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Octicons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Roboto.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Roboto.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Roboto_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Roboto_medium.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/SimpleLineIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/SimpleLineIcons.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/Zocial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/Zocial.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/assets/fonts/rubicon-icon-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/assets/fonts/rubicon-icon-font.ttf -------------------------------------------------------------------------------- /frontend/android/app/src/main/java/com/astrophotoplus_frontend/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.astrophotoplus_frontend; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.ReactRootView; 6 | import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView; 7 | 8 | 9 | public class MainActivity extends ReactActivity { 10 | 11 | /** 12 | * Returns the name of the main component registered from JavaScript. 13 | * This is used to schedule rendering of the component. 14 | */ 15 | @Override 16 | protected String getMainComponentName() { 17 | return "AstroPhotoPlus_frontend"; 18 | } 19 | @Override 20 | protected ReactActivityDelegate createReactActivityDelegate() { 21 | return new ReactActivityDelegate(this, getMainComponentName()) { 22 | @Override 23 | protected ReactRootView createRootView() { 24 | return new RNGestureHandlerEnabledRootView(MainActivity.this); 25 | } 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/java/com/astrophotoplus_frontend/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.astrophotoplus_frontend; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.swmansion.gesturehandler.react.RNGestureHandlerPackage; 7 | import com.facebook.react.ReactNativeHost; 8 | import com.facebook.react.ReactPackage; 9 | import com.facebook.react.shell.MainReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 18 | @Override 19 | public boolean getUseDeveloperSupport() { 20 | return BuildConfig.DEBUG; 21 | } 22 | 23 | @Override 24 | protected List getPackages() { 25 | return Arrays.asList( 26 | new MainReactPackage(), 27 | new RNGestureHandlerPackage() 28 | ); 29 | } 30 | 31 | @Override 32 | protected String getJSMainModuleName() { 33 | return "index.native"; 34 | } 35 | }; 36 | 37 | @Override 38 | public ReactNativeHost getReactNativeHost() { 39 | return mReactNativeHost; 40 | } 41 | 42 | @Override 43 | public void onCreate() { 44 | super.onCreate(); 45 | SoLoader.init(this, /* native exopackage */ false); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AstroPhotoPlus_frontend 3 | 4 | -------------------------------------------------------------------------------- /frontend/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "28.0.3" 6 | minSdkVersion = 16 7 | compileSdkVersion = 28 8 | targetSdkVersion = 27 9 | supportLibVersion = "28.0.0" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath 'com.android.tools.build:gradle:3.3.1' 17 | 18 | // NOTE: Do not place your application dependencies here; they belong 19 | // in the individual module build.gradle files 20 | } 21 | } 22 | 23 | allprojects { 24 | repositories { 25 | mavenLocal() 26 | google() 27 | jcenter() 28 | maven { 29 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 30 | url "$rootDir/../node_modules/react-native/android" 31 | } 32 | } 33 | } 34 | 35 | 36 | task wrapper(type: Wrapper) { 37 | gradleVersion = '4.7' 38 | distributionUrl = distributionUrl.replace("bin", "all") 39 | } 40 | -------------------------------------------------------------------------------- /frontend/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | -------------------------------------------------------------------------------- /frontend/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /frontend/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 19 21:19:49 GMT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /frontend/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = "debug", 3 | properties = "debug.keystore.properties", 4 | store = "debug.keystore", 5 | visibility = [ 6 | "PUBLIC", 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /frontend/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /frontend/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'AstroPhotoPlus_frontend' 2 | include ':react-native-gesture-handler' 3 | project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-gesture-handler/android') 4 | 5 | include ':app' 6 | -------------------------------------------------------------------------------- /frontend/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AstroPhotoPlus_frontend", 3 | "displayName": "AstroPhotoPlus_frontend" 4 | } -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /frontend/index.native.js: -------------------------------------------------------------------------------- 1 | import "./src/index"; 2 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | @interface AppDelegate : UIResponder 11 | 12 | @property (nonatomic, strong) UIWindow *window; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "AppDelegate.h" 9 | 10 | #import 11 | #import 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | NSURL *jsCodeLocation; 18 | 19 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.native" fallbackResource:nil]; 20 | 21 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 22 | moduleName:@"AstroPhotoPlus_frontend" 23 | initialProperties:nil 24 | launchOptions:launchOptions]; 25 | rootView.backgroundColor = [UIColor blackColor]; 26 | 27 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 28 | UIViewController *rootViewController = [UIViewController new]; 29 | rootViewController.view = rootView; 30 | self.window.rootViewController = rootViewController; 31 | [self.window makeKeyAndVisible]; 32 | return YES; 33 | } 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontend/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/ios/AstroPhotoPlus_frontendTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/public/icon-144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-144.png -------------------------------------------------------------------------------- /frontend/public/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-16.png -------------------------------------------------------------------------------- /frontend/public/icon-168.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-168.png -------------------------------------------------------------------------------- /frontend/public/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-192.png -------------------------------------------------------------------------------- /frontend/public/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-256.png -------------------------------------------------------------------------------- /frontend/public/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-32.png -------------------------------------------------------------------------------- /frontend/public/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-48.png -------------------------------------------------------------------------------- /frontend/public/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-512.png -------------------------------------------------------------------------------- /frontend/public/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-64.png -------------------------------------------------------------------------------- /frontend/public/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-72.png -------------------------------------------------------------------------------- /frontend/public/icon-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GuLinux/AstroPhoto-Plus/00bf0ae8079a281f241a8b4f67535e977bb99cc9/frontend/public/icon-96.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "AstroPhoto Plus", 3 | "name": "AstroPhoto Plus", 4 | "icons": [ 5 | { 6 | "src": "icon-512.png", 7 | "sizes": "512x512", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "icon-256.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "icon-192.png", 17 | "sizes": "192x192", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "icon-96.png", 22 | "sizes": "96x96", 23 | "type": "image/png" 24 | }, 25 | { 26 | "src": "icon-168.png", 27 | "sizes": "168x168", 28 | "type": "image/png" 29 | }, 30 | { 31 | "src": "icon-144.png", 32 | "sizes": "144x144", 33 | "type": "image/png" 34 | }, 35 | { 36 | "src": "icon-72.png", 37 | "sizes": "72x72", 38 | "type": "image/png" 39 | }, 40 | { 41 | "src": "icon-48.png", 42 | "sizes": "48x48", 43 | "type": "image/png" 44 | }, 45 | { 46 | "src": "icon-32.png", 47 | "sizes": "32x32", 48 | "type": "image/png" 49 | }, 50 | { 51 | "src": "icon-16.png", 52 | "sizes": "16x16", 53 | "type": "image/png" 54 | } 55 | ], 56 | "start_url": "./index.html", 57 | "display": "fullscreen", 58 | "theme_color": "#ffffff", 59 | "background_color": "#272b30" 60 | } 61 | -------------------------------------------------------------------------------- /frontend/semantic.json: -------------------------------------------------------------------------------- 1 | { 2 | "base": "src/semantic", 3 | "paths": { 4 | "source": { 5 | "config": "src/theme.config", 6 | "definitions": "src/definitions/", 7 | "site": "src/site/", 8 | "themes": "src/themes/" 9 | }, 10 | "output": { 11 | "packaged": "dist/", 12 | "uncompressed": "dist/components/", 13 | "compressed": "dist/components/", 14 | "themes": "dist/themes/" 15 | }, 16 | "clean": "dist/" 17 | }, 18 | "permission": false, 19 | "autoInstall": true, 20 | "rtl": false, 21 | "components": ["reset", "site", "button", "container", "divider", "flag", "header", "icon", "image", "input", "label", "list", "loader", "placeholder", "rail", "reveal", "segment", "step", "breadcrumb", "form", "grid", "menu", "message", "table", "ad", "card", "comment", "feed", "item", "statistic", "accordion", "checkbox", "dimmer", "dropdown", "embed", "modal", "nag", "popup", "progress", "rating", "search", "shape", "sidebar", "sticky", "tab", "transition", "api", "form", "state", "visibility"], 22 | "version": "2.4.2" 23 | } -------------------------------------------------------------------------------- /frontend/src/App/App.native.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Content, Text, Button } from "native-base"; 3 | import { createDrawerNavigator, createAppContainer } from "react-navigation"; 4 | import { BackendSelectionContainer } from '../BackendSelection/BackendSelectionContainer'; 5 | import { Navbar } from '../Navigation/Navbar.native'; 6 | 7 | 8 | const HomeScreen = ({navigation}) => ( 9 | 10 | 11 | 12 | 15 | 16 | 17 | ); 18 | 19 | const AppNavigator = createDrawerNavigator({ 20 | Home: HomeScreen, 21 | BackendSelection: BackendSelectionContainer, 22 | },{ 23 | initialRouteName: 'Home', 24 | }); 25 | 26 | export const App = createAppContainer(AppNavigator); 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/App/AppContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { App } from './App'; 3 | import { appSelector } from './appContainerSelector'; 4 | 5 | export const AppContainer = connect(appSelector)(App); 6 | -------------------------------------------------------------------------------- /frontend/src/App/TimeSyncModal.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { dismissTimeSyncModal, backendTimeSync } from './actions'; 4 | import { timeSyncModalSelector } from './selectors'; 5 | import { Button, Modal } from 'semantic-ui-react'; 6 | 7 | const TimeSyncModalComponent = ({ showTimeSyncModal, dismissTimeSyncModal, backendTimeSync }) => showTimeSyncModal && ( 8 | 9 | Time Synchronisation 10 | 11 |

Your backend server time seems to be out of sync.

12 |

Do you want to synchronise it?

13 |
14 | 15 | 16 | 17 | 18 |
19 | ) 20 | 21 | export const TimeSyncModal = connect(timeSyncModalSelector, { dismissTimeSyncModal, backendTimeSync })(TimeSyncModalComponent); 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/App/appContainerSelector.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { getServerName } from '../Settings/selectors'; 3 | 4 | // Moved here to avoid circular dependencies 5 | export const appSelector = createSelector(getServerName, serverName => ({ serverName })); 6 | -------------------------------------------------------------------------------- /frontend/src/App/reducer.js: -------------------------------------------------------------------------------- 1 | import PackageInfo from '../../package.json'; 2 | 3 | const defaultState = { 4 | frontend: { version: PackageInfo.version }, 5 | backend: {}, 6 | timeSyncNeeded: false, 7 | timeSyncModalDismissed: false, 8 | }; 9 | 10 | export const app = (state = defaultState, action) => { 11 | switch (action.type) { 12 | case 'BACKEND_VERSION_FETCHED': 13 | return { ...state, backend: action.version }; 14 | case 'TIME_SYNC_MODAL_DISMISS': 15 | return {...state, timeSyncModalDismissed: true }; 16 | case 'BACKEND_TIMESTAMP_FETCHED': 17 | return { ...state, timeSyncNeeded: Math.abs( (new Date().getTime() / 1000.0) - action.payload.utc_timestamp ) > 30 }; 18 | default: 19 | return state; 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/src/App/selectors.js: -------------------------------------------------------------------------------- 1 | import { get } from 'lodash'; 2 | import { createSelector } from 'reselect'; 3 | 4 | export const getFrontendVersion = state => get(state, 'app.frontend.version'); 5 | export const getBackendVersion = state => get(state, 'app.backend.version'); 6 | 7 | const getTimeSyncModalDismissed = state => state.app.timeSyncModalDismissed; 8 | const getTimeSyncNeeded = state => state.app.timeSyncNeeded; 9 | 10 | export const timeSyncModalSelector = createSelector([getTimeSyncModalDismissed, getTimeSyncNeeded], (timeSyncModalDismissed, timeSyncNeeded) => ({ 11 | showTimeSyncModal: timeSyncNeeded && ! timeSyncModalDismissed, 12 | })); 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/Autoguider/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { getCurrentSettings } from '../Settings/selectors'; 3 | import { get } from 'lodash'; 4 | 5 | const isDitheringEnabled = state => get(getCurrentSettings(state), 'dithering_enabled', false); 6 | 7 | export const getDitheringOptionsSelector = createSelector([ 8 | isDitheringEnabled, 9 | ], (isDitheringEnabled) => ({ 10 | isDitheringEnabled: !!isDitheringEnabled, 11 | })); 12 | -------------------------------------------------------------------------------- /frontend/src/BackendSelection/BackendSelectionContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { backendSelectionSelector } from './selectors'; 3 | import { BackendSelectionPage } from './BackendSelectionPage'; 4 | import Actions from '../actions'; 5 | 6 | export const BackendSelectionContainer = connect(backendSelectionSelector, { 7 | saveBackend: Actions.BackendSelection.saveBackend, 8 | })(BackendSelectionPage); -------------------------------------------------------------------------------- /frontend/src/BackendSelection/BackendSelectionPage.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Content, Input, Button, Text, H3 } from 'native-base'; 3 | import { Navbar } from '../Navigation/Navbar.native'; 4 | export class BackendSelectionPage extends React.Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { address: props.address }; 8 | } 9 | 10 | setBackend = () => { 11 | this.props.saveBackend(this.state.address); 12 | } 13 | 14 | onBackendChanged = ({ nativeEvent }) => this.setState({...this.state, address: nativeEvent.text}); 15 | 16 | render = () => { 17 | const { navigation } = this.props; 18 | return ( 19 | 20 | 21 | 22 |

Set the address of the backend server

23 | 24 | 27 |
28 |
29 | ); 30 | } 31 | } -------------------------------------------------------------------------------- /frontend/src/BackendSelection/actions.js: -------------------------------------------------------------------------------- 1 | import { API } from "../middleware/api"; 2 | 3 | export const BackendSelection = { 4 | getAddress: async () => { 5 | API.setBackendURL(''); 6 | return ''; 7 | } 8 | } -------------------------------------------------------------------------------- /frontend/src/BackendSelection/actions.native.js: -------------------------------------------------------------------------------- 1 | import Actions from '../actions'; 2 | import {AsyncStorage} from 'react-native'; 3 | import { API } from '../middleware/api'; 4 | import { init } from '../App/actions'; 5 | 6 | export const BackendSelection = { 7 | setBackend: address => { 8 | API.setBackendURL(address); 9 | return { type: 'BACKEND_SET_ADDRESS', address} 10 | }, 11 | 12 | saveBackend: address => dispatch => { 13 | dispatch(Actions.BackendSelection.setBackend(address)); 14 | dispatch(init()); 15 | AsyncStorage.setItem('backend_address', address); 16 | }, 17 | 18 | loadAddress: () => dispatch => { 19 | Actions.BackendSelection.getAddress(dispatch); 20 | }, 21 | 22 | getAddress: async dispatch => { 23 | const address = await AsyncStorage.getItem('backend_address'); 24 | if(address !== null) { 25 | dispatch(Actions.BackendSelection.setBackend(address)); 26 | } 27 | return address; 28 | }, 29 | } -------------------------------------------------------------------------------- /frontend/src/BackendSelection/reducer.js: -------------------------------------------------------------------------------- 1 | import { API } from "../middleware/api"; 2 | 3 | const defaultState = { 4 | address: null, 5 | }; 6 | 7 | export const backendSelection = (state = defaultState, action) => { 8 | switch(action.type) { 9 | case 'BACKEND_SET_ADDRESS': 10 | API.setBackendURL(action.address); 11 | return {...state, address: action.address}; 12 | default: 13 | return state; 14 | } 15 | } -------------------------------------------------------------------------------- /frontend/src/BackendSelection/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const backendSelectionSelector = createSelector([ 4 | state => state.backendSelection.address, 5 | ], (address) => ({ 6 | address, 7 | })); -------------------------------------------------------------------------------- /frontend/src/Camera/AutoExposureContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import React from 'react'; 3 | import { shoot } from './actions'; 4 | import { autoExposureSelector } from './selectors'; 5 | 6 | 7 | class AutoExposure extends React.Component { 8 | render = () => null; 9 | 10 | componentDidUpdate = (prevProps) => { 11 | const {shouldAutostart, shotParameters, shoot} = this.props; 12 | 13 | if(shouldAutostart && ! prevProps.shouldAutostart) { 14 | shoot(shotParameters, this.props.section); 15 | } 16 | } 17 | } 18 | 19 | export const AutoExposureContainer = connect(autoExposureSelector, { shoot })(AutoExposure); 20 | -------------------------------------------------------------------------------- /frontend/src/Camera/Camera.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container, Form } from 'semantic-ui-react'; 3 | import CurrentImageViewerContainer from './CurrentImageViewerContainer'; 4 | import { AutoExposureContainer } from './AutoExposureContainer'; 5 | import { NotFoundPage } from '../components/NotFoundPage'; 6 | import { CameraShootingSectionMenuEntriesContaner, CameraImageOptionsSectionMenuEntriesContainer } from './CameraSectionMenuEntriesContainer'; 7 | 8 | 9 | export const CameraSectionMenu = ({section}) => ( 10 |
11 | 12 | 13 | 14 | ) 15 | 16 | 17 | export const Camera = ({options, cameras, section}) => { 18 | if(cameras.length === 0) 19 | return 25 | return ( 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/src/Camera/CameraBinning.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { NumericInput } from '../components/NumericInput'; 3 | import { Form } from 'semantic-ui-react'; 4 | 5 | 6 | export class CameraBinning extends React.Component { 7 | setBinning = binning => this.props.setOption({binning}, this.props.section); 8 | render = () => { 9 | const { selectedBinning, binning } = this.props; 10 | if(!binning) { 11 | return null; 12 | } 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/Camera/CameraBinningContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { CameraBinning } from './CameraBinning'; 3 | import { cameraBinningSelector } from './selectors'; 4 | import { setOption } from './actions'; 5 | 6 | export const CameraBinningContainer = connect(cameraBinningSelector, { setOption })(CameraBinning); 7 | -------------------------------------------------------------------------------- /frontend/src/Camera/CameraContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { Camera } from './Camera'; 3 | import { cameraContainerSelector } from './selectors'; 4 | 5 | export const CameraContainer = connect( 6 | cameraContainerSelector, 7 | null, 8 | )(Camera) 9 | -------------------------------------------------------------------------------- /frontend/src/Camera/CameraSectionMenuEntriesContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { cameraImageOptionsSectionMenuEntriesSelector, cameraShootingSectionMenuEntriesSelector } from './selectors'; 3 | import { setOption, setCamera, setFilterWheel, startCrop, resetCrop } from './actions'; 4 | import { CameraImageOptionsSectionMenuEntries, CameraShootingSectionMenuEntries } from './CameraSectionMenuEntries'; 5 | 6 | export const CameraShootingSectionMenuEntriesContaner = connect( 7 | cameraShootingSectionMenuEntriesSelector, 8 | { 9 | setOption, 10 | setCamera, 11 | setFilterWheel, 12 | }, 13 | )(CameraShootingSectionMenuEntries); 14 | 15 | export const CameraImageOptionsSectionMenuEntriesContainer = connect( 16 | cameraImageOptionsSectionMenuEntriesSelector, { 17 | startCrop, 18 | resetCrop, 19 | })(CameraImageOptionsSectionMenuEntries); 20 | -------------------------------------------------------------------------------- /frontend/src/Camera/CurrentImageViewerContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { ImageViewer } from './ImageViewer'; 3 | import { currentImageSelector } from './selectors'; 4 | import { imageLoading, imageLoaded, setCrop } from './actions'; 5 | 6 | 7 | 8 | const mapDispatchToProps = { 9 | onImageLoading: imageLoading, 10 | onImageLoaded: imageLoaded, 11 | setCrop, 12 | }; 13 | 14 | const CurrentImageViewerContainer = connect( 15 | currentImageSelector, 16 | mapDispatchToProps 17 | )(ImageViewer) 18 | 19 | 20 | export default CurrentImageViewerContainer; 21 | -------------------------------------------------------------------------------- /frontend/src/Camera/ExposureInputContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { setOption, shoot } from './actions'; 3 | import { ExposureInput } from './ExposureInput'; 4 | import { exposureInputSelector } from './selectors'; 5 | 6 | const onExposureChanged = (exposure, section) => setOption({exposure}, section); 7 | 8 | const mapDispatchToProps = { 9 | onExposureChanged, 10 | shoot, 11 | }; 12 | 13 | 14 | const ExposureInputContainer = connect( 15 | exposureInputSelector, 16 | mapDispatchToProps 17 | )(ExposureInput); 18 | 19 | export default ExposureInputContainer; 20 | -------------------------------------------------------------------------------- /frontend/src/Camera/FilterSelection.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dropdown } from 'semantic-ui-react'; 3 | 4 | export const FilterSelection = ({filterName, filterNumber, onClick}) => ; -------------------------------------------------------------------------------- /frontend/src/Camera/FilterSelectionContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { FilterSelection } from './FilterSelection'; 3 | import { filterSelectionSelector } from './selectors'; 4 | 5 | export const FilterSelectionContainer = connect(filterSelectionSelector)(FilterSelection); -------------------------------------------------------------------------------- /frontend/src/Camera/SelectFilter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dropdown } from 'semantic-ui-react'; 3 | import { FilterSelectionContainer } from './FilterSelectionContainer'; 4 | 5 | 6 | export class SelectFilter extends React.Component { 7 | renderFilter = (id, index) => ; 8 | 9 | onChange = (e,{value}) => this.props.onFilterSelected(value, this.props.section); 10 | 11 | render = () => { 12 | const {currentFilter, currentFilterName, availableFilters, isPending} = this.props; 13 | return ( 14 | 24 | 25 | {availableFilters.map(this.renderFilter)} 26 | 27 | 28 | 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/Camera/SelectFilterContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { SelectFilter } from './SelectFilter'; 3 | import { changeFilter } from './actions'; 4 | import { selectFilterSelector } from './selectors'; 5 | 6 | export const SelectFilterContainer = connect( 7 | selectFilterSelector, 8 | { onFilterSelected: changeFilter } 9 | )(SelectFilter) 10 | 11 | -------------------------------------------------------------------------------- /frontend/src/Camera/sections.js: -------------------------------------------------------------------------------- 1 | export const DARV_PAGE='darv'; 2 | export const CAMERA_PAGE='camera'; 3 | export const PLATESOLVING_PAGE='plateSolving'; 4 | export const POLAR_DRIFT='polarDrift'; 5 | 6 | -------------------------------------------------------------------------------- /frontend/src/Catalogs/reducer.js: -------------------------------------------------------------------------------- 1 | const defaultState = { 2 | availableCatalogs: {}, 3 | catalogs: {} 4 | }; 5 | 6 | 7 | const catalogFetch = (state, {sectionKey}) => ({...state, [sectionKey]: { fetching: true }}); 8 | const catalogFetchSuccess = (state, {sectionKey, results }) => ({...state, [sectionKey]: { results}}); 9 | const catalogLookupFailed = (state, {sectionKey, error}) => ({...state, [sectionKey]: { fetching: false, error }}); 10 | 11 | export const catalogs = (state = defaultState, action) => { 12 | switch(action.type) { 13 | case 'CATALOG_AVAILABLE_RETRIEVED': 14 | return {...state, availableCatalogs: action.catalogs }; 15 | case 'CATALOG_RETRIEVED': 16 | return {...state, catalogs: action.catalogs }; 17 | case 'CATALOG_LOOKUP_FETCH': 18 | return catalogFetch(state, action); 19 | case 'CATALOG_LOOKUP_SUCCESS': 20 | return catalogFetchSuccess(state, action); 21 | case 'CATALOG_LOOKUP_FAILED': 22 | return catalogLookupFailed(state, action); 23 | case 'CATALOG_IMPORT_FETCH': 24 | return {...state, importing: true }; 25 | case 'CATALOG_IMPORTED': 26 | return {...state, importing: false }; 27 | case 'CATALOG_CLEAR_RESULTS': 28 | return {...state, [action.sectionKey]: {}}; 29 | default: 30 | return state; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/Commands/CommandsContainer.js: -------------------------------------------------------------------------------- 1 | import { Commands } from './Commands'; 2 | import { connect } from 'react-redux'; 3 | import Actions from '../actions'; 4 | import { commandsSelector } from './selectors'; 5 | 6 | const addErrorNotification = (title, message) => Actions.Notifications.add(title, message, 'error'); 7 | 8 | const mapDispatchToProps = { 9 | onError: addErrorNotification, 10 | refresh: Actions.Commands.get, 11 | }; 12 | 13 | export const CommandsContainer = connect(commandsSelector, mapDispatchToProps)(Commands); 14 | -------------------------------------------------------------------------------- /frontend/src/Commands/actions.js: -------------------------------------------------------------------------------- 1 | import { fetchCommandsAPI } from '../middleware/api' 2 | 3 | export const Commands = { 4 | get: () => dispatch => { 5 | dispatch({ type: 'GET_COMMANDS' }); 6 | return fetchCommandsAPI(dispatch, data => { 7 | dispatch(Commands.gotCommands(data.entities.commands, data.result)); 8 | }); 9 | }, 10 | 11 | gotCommands: (commands, ids) => ({ type: 'GOT_COMMANDS', commands, ids }), 12 | } 13 | 14 | export default Commands; 15 | -------------------------------------------------------------------------------- /frontend/src/Commands/reducer.js: -------------------------------------------------------------------------------- 1 | const commands = (state = { commands: {}, ids: []}, action) => { 2 | switch(action.type) { 3 | case 'GOT_COMMANDS': 4 | return {...state, commands: action.commands, ids: action.ids, fetching: false }; 5 | case 'GET_COMMANDS': 6 | return {...state, fetching: true}; 7 | default: 8 | return state; 9 | } 10 | } 11 | 12 | export default commands; 13 | 14 | -------------------------------------------------------------------------------- /frontend/src/Commands/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | 4 | export const commandsSelector = createSelector([ 5 | state => state.commands.fetching, 6 | state => state.commands.ids, 7 | state => state.commands.commands, 8 | ], (fetching, commandIds, commandEntities) => { 9 | const commands = commandIds.map(id => commandEntities[id]); 10 | if(!commands || commands.length === 0) { 11 | return { fetching }; 12 | } 13 | 14 | const categories = commands.reduce( (acc, cur) => { 15 | let category = cur.category in acc ? acc[cur.category] : { commands: [] }; 16 | category.commands.push(cur); 17 | return {...acc, [cur.category]: category}; 18 | }, {}); 19 | 20 | return { 21 | categories, 22 | fetching, 23 | }; 24 | }); 25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/src/Errors/ErrorPageContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import ErrorPage from './ErrorPage' 3 | import React from 'react' 4 | import { errorPageSelector } from './selectors'; 5 | 6 | const ErrorPageContainer = ({isError, errorSource, errorPayload, children}) => { 7 | if(isError) { 8 | return 9 | } 10 | return children; 11 | } 12 | 13 | 14 | 15 | export default connect(errorPageSelector)(ErrorPageContainer) 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/Errors/reducer.js: -------------------------------------------------------------------------------- 1 | 2 | const defaultState = { 3 | isError: false, 4 | lastErrorSource: '', 5 | lastErrorPayloadType: '', 6 | lastErrorPayload: {}, 7 | } 8 | 9 | const errors = (state = defaultState, action) => { 10 | switch(action.type) { 11 | case 'SERVER_ERROR': 12 | return {...state, isError: true, lastErrorSource: action.source, lastErrorPayloadType: action.payloadType, lastErrorPayload: action.payload, lastResponseBody: action.responseBody }; 13 | case 'BACKEND_VERSION_FETCHED': 14 | return defaultState; 15 | default: 16 | return state; 17 | } 18 | } 19 | 20 | export default errors; 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/Errors/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | 3 | export const isError = state => state.errors.isError 4 | 5 | export const errorPageSelector = createSelector( 6 | [ 7 | state => state.errors.lastErrorPayload, 8 | state => state.errors.lastErrorPayloadType, 9 | state => state.errors.lastResponseBody, 10 | state => state.errors.lastErrorSource, 11 | isError, 12 | ], 13 | (payload, lastErrorPayloadType, lastResponseBody, errorSource, isError) => { 14 | let payloadAsString = String(payload); 15 | switch(lastErrorPayloadType) { 16 | case 'exception': 17 | payloadAsString = `exception: ${payload.name} ${payload.message}\n${payload.stack}` 18 | break; 19 | case 'event': 20 | payloadAsString = String(payload.target); 21 | break; 22 | case 'response': 23 | payloadAsString = `status: ${payload.status} - ${payload.statusText}\nURL: ${payload.url}\nBody:\n${lastResponseBody}`; 24 | break; 25 | default: 26 | break; 27 | }; 28 | return { 29 | errorSource, 30 | errorPayload: payloadAsString, 31 | isError, 32 | } 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /frontend/src/Gear/actions.js: -------------------------------------------------------------------------------- 1 | import { telescopeGOTOAPI } from '../middleware/api'; 2 | 3 | 4 | export const telescopeGOTO = (telescope, ra, dec, equinox, onGotoCompleted) => dispatch => { 5 | dispatch({ type: 'TELESCOPE_GOTO', ra, dec, telescope, equinox }); 6 | telescopeGOTOAPI(dispatch, telescope, {ra, dec, equinox, sync: true, onGotoCompleted }); 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/Home/HomeContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { homeSelector } from './selectors'; 3 | import { Home } from './Home'; 4 | 5 | export const HomeContainer = connect(homeSelector)(Home); -------------------------------------------------------------------------------- /frontend/src/Home/selectors.js: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect'; 2 | import { getServerName, getAutoguiderEngine } from '../Settings/selectors'; 3 | 4 | export const homeSelector = createSelector([getServerName, getAutoguiderEngine], 5 | (serverName, autoguiderEngine) => ({ 6 | serverName, 7 | autoguiderEngine, 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /frontend/src/INDI-Server/INDIDeviceContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { INDIDevicePage } from './INDIDevicePage' 3 | import { indiDeviceContainerSelector } from './selectors'; 4 | 5 | export const INDIDeviceContainer = connect( 6 | indiDeviceContainerSelector, 7 | null, 8 | )(INDIDevicePage) 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/INDI-Server/INDIDeviceGroup.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Grid } from 'semantic-ui-react' 3 | import { INDIPropertyRowContainer } from './INDIPropertyRowContainer'; 4 | 5 | export class INDIDeviceGroup extends React.PureComponent { 6 | renderPropertyRow = (property) => ; 7 | 8 | render = () => this.props.group ? ( 9 | 10 | { this.props.group.properties.map(this.renderPropertyRow)} 11 | 12 | ) : null; 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/INDI-Server/INDIDeviceGroupContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { INDIDeviceGroup } from './INDIDeviceGroup' 3 | import { indiDeviceGroupSelector } from './selectors'; 4 | 5 | 6 | export const INDIDeviceGroupContainer = connect( 7 | indiDeviceGroupSelector, 8 | )(INDIDeviceGroup) 9 | 10 | -------------------------------------------------------------------------------- /frontend/src/INDI-Server/INDILight.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Label } from 'semantic-ui-react' 3 | 4 | const states = { 5 | IDLE: { color: 'grey', text: 'idle', icon: 'dot circle outline'}, 6 | OK: { color: 'green', text: 'ok', icon: 'check circle outline' }, 7 | BUSY: { color: 'orange', text: 'busy', icon: 'hourglass' }, 8 | CHANGED_BUSY: { color: 'brown', text: 'busy', icon: 'hourglass' }, 9 | ALERT: { color: 'red', text: 'alert', icon: 'exclamation' }, 10 | } 11 | 12 | 13 | export const INDILight = ({state, text}) => ( 14 |