Skip to content

Commit

Permalink
Merge pull request #46 from miksrv/develop
Browse files Browse the repository at this point in the history
- [Added feature - photo download, fixes minor styles on photo page](a70fd20)
- [Refactoring, added objects table toolbar, search, popup and many more](2a52d35)
- [Fixed bug in photos list filter](a5d5dbc)
- [Optimize and bug fixing](521e279)
- [Update GitHub actions, SonarCloud integration](49949f6)
- [Updated documentation](610ee17)
  • Loading branch information
miksrv authored May 14, 2022
2 parents fc9f6a0 + 610ee17 commit f90a4a8
Show file tree
Hide file tree
Showing 45 changed files with 710 additions and 355 deletions.
41 changes: 41 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Build & Test

on:
pull_request:
branches:
- master
push:
branches:
- master

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install
run: npm ci
working-directory: ./frontend
- name: Build
run: npm run build --if-present
working-directory: ./frontend
- name: Test
run: npm run test:coverage
working-directory: ./frontend
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
35 changes: 0 additions & 35 deletions .github/workflows/nodejs-pr-master.yml

This file was deleted.

13 changes: 6 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
Observatory control panel
Amateur astronomical observatory portal
===============
[![Release](https://github.com/miksrv/observatory/actions/workflows/nodejs-pr-master.yml/badge.svg?branch=master)](https://github.com/miksrv/observatory/actions/workflows/nodejs-pr-master.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=miksrv_observatory&metric=alert_status)](https://sonarcloud.io/dashboard?id=miksrv_observatory)
[![Build & Test](https://github.com/miksrv/observatory/actions/workflows/build.yml/badge.svg)](https://github.com/miksrv/observatory/actions/workflows/build.yml)

An interface project for the management of an amateur astronomical observatory. The WEB-interface provides the ability to control the power supply of devices through a relay system, to receive data on temperatures at various points, humidity and voltage of various devices. The interface displays images from cameras, builds graphs for viewing archive statistics. The main function is to display statistics of the observatory's work: a calendar of filming with statistics of a filming night, display of captured objects with the number of frames in various filters, a photo gallery of the final results.

[ [DEMO](https://observatory.miksoft.pro/) ]

![General view of the interface](./documentation/ui-screen-1.png)
![General view of the interface](./docs/ui-screen-1.png)

----------------------
### Observatory

This is an amateur and completely homemade astronomical observatory project. The goal of the project is to teach the skills of building objects offline, writing drivers in C++, scripts in Python to automate the process of equipment operation. In addition, obtaining good astrophotography of deep-sky objects, observing comets, asteroids and searching for supernovae and variable stars.

![General view of the observatory](./documentation/photo-1.jpg)
![General view of the observatory](./docs/photo-1.jpg)

The observatory controller is based on Ardunio (AVR) and connects to the observatory network. The controller is controlled by means of HTTP requests, which send commands to switch the state of the relay and other elements of the power load. The controller's WEB client sends statistics to a remote server ([API](https://github.com/miksrv/api-backend)) at a specified time interval. The web interface in this repository displays statistics from the backend server and sends commands to the observatory controller through it.

![General view of the observatory](./documentation/photo-2.jpg)
![General view of the observatory](./docs/photo-2.jpg)

##### Controller components
- Arduino Mega 2560
Expand Down Expand Up @@ -233,7 +232,7 @@ The response format is JSON, the response structure is always the same, only pay
This project consists of 3 main sections:

1. [ **firmware** ] Firmware for Arduino microcontroller (AVR), observatory controller control unit.
2. [ **backend** ] Backend server (repository [here](https://github.com/miksrv/api-backend)).
2. [ **backend** ] Backend server.
3. [ **frontend** ] Observatory control interface. Written in ReactJS + Redux (use Node and NPM). To debug an application on a local server, you must first install the required dependencies:
* `npm install` Installing dependencies.
* `npm update` Update all dependencies.
Expand Down
24 changes: 22 additions & 2 deletions backend/app/Controllers/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,32 @@ function file($what = null)
function photo($what = null)
{
$Photos = new Photos();
$object = $this->request->getGet('object', FILTER_SANITIZE_STRING);
$name = $this->request->getGet('object', FILTER_SANITIZE_STRING);
$date = $this->request->getGet('date', FILTER_SANITIZE_STRING);

switch ($what)
{
case 'list': // Список всех фото
$this->_response(! $object ? $Photos->list() : $Photos->list_by_object($object));
$this->_response(! $name ? $Photos->list() : $Photos->list_by_object($name));
break;

case 'download': // Скачать фото по названию объекта и дате
if (!$name || !$date)
{
throw PageNotFoundException::forPageNotFound();
}

$filePath = $Photos->get_photo_path($name, $date);

if (!$filePath)
{
throw PageNotFoundException::forPageNotFound();
}

header('Content-Type: application/octet-stream');
header('Content-Transfer-Encoding: Binary');
header('Content-disposition: attachment; filename="' . basename($filePath) . '"');
readfile($filePath);
break;

default: throw PageNotFoundException::forPageNotFound();
Expand Down
19 changes: 19 additions & 0 deletions backend/app/Libraries/Photos.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,25 @@ function list_by_object(string $object): ?array
return $photos;
}

function get_photo_path(string $name, string $date): ?string
{
$photo = $this->_model->get_by_object($name, $date);

if (empty($photo) || !$photo[0] || !$photo[0]->photo_file)
{
return null;
}

$path = $_SERVER['DOCUMENT_ROOT'] . '/api/public/photo/' . $photo[0]->photo_file . '.' . $photo[0]->photo_file_ext;

if (!file_exists($path))
{
return null;
}

return $path;
}

protected function _make_list($data): array
{
$photos = [];
Expand Down
12 changes: 10 additions & 2 deletions backend/app/Models/Photos.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,23 @@ function get_list()
/**
* Return photos array by astronomy object name
* @param string $name
* @param string $date
* @return mixed
*/
function get_by_object(string $name)
function get_by_object(string $name, string $date = '')
{
$where = [$this->key_object => $name];

if ($date)
{
$where[$this->key_date] = $date;
}

return $this->db
->table($this->table)
->join($this->author, "$this->author.author_id = $this->table.$this->key_author", 'left')
->orderBy($this->key_date, 'DESC')
->getWhere([$this->key_object => $name])
->getWhere($where)
->getResult();
}

Expand Down
Binary file added docs/photo-1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file added docs/ui-screen-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed documentation/photo-1.jpg
Binary file not shown.
Binary file removed documentation/ui-screen-1.png
Binary file not shown.
5 changes: 3 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "observatory",
"update": "10.05.2022",
"version": "2.1.0",
"update": "14.05.2022",
"version": "2.1.1",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.6.2",
Expand Down Expand Up @@ -34,6 +34,7 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test src/__tests__",
"test:coverage": "react-scripts test --coverage src/__tests__",
"eject": "react-scripts eject",
"mswinit": "npx msw init public"
},
Expand Down
46 changes: 24 additions & 22 deletions frontend/src/components/camera/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,29 +32,31 @@ const Camera: React.FC<TCameraProps> = (props) => {
}
})

return <div className='box camera'>
{cameraURL && lightbox && (
<Lightbox
mainSrc={cameraSrc}
onCloseRequest={() => setLightbox(false)}
/>
)}
{cameraURL ?
<>
<img onClick={() => setLightbox(true)} src={cameraSrc} alt='' />
<Progress percent={Math.round((seconds / CAMERA_INTERVAL) * 100)} success size='tiny' />
</>
:
<Dimmer active>
<Message
error
icon='photo'
header='Камера не доступна'
content='Изображение камеры не доступно'
return (
<div className='box camera'>
{cameraURL && lightbox && (
<Lightbox
mainSrc={cameraSrc}
onCloseRequest={() => setLightbox(false)}
/>
</Dimmer>
}
</div>
)}
{cameraURL ?
<>
<img onClick={() => setLightbox(true)} src={cameraSrc} alt='' />
<Progress percent={Math.round((seconds / CAMERA_INTERVAL) * 100)} success size='tiny' />
</>
:
<Dimmer active>
<Message
error
icon='photo'
header='Камера не доступна'
content='Изображение камеры не доступно'
/>
</Dimmer>
}
</div>
)
}

export default Camera
1 change: 0 additions & 1 deletion frontend/src/components/chart/chart_coordinates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ const chart_coordinates = {
series: [{
name: 'Координаты объекта',
color: colors[3],
data: []
}]
}

Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/chart/chart_coordlines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ const chart_coordlines = {
yAxis: 0,
name: 'RA',
color: colors[6],
data: [],
tooltip: {
valueSuffix: ' °'
}
}, {
yAxis: 1,
name: 'DEC',
color: colors[9],
data: [],
tooltip: {
valueSuffix: ' °'
}
Expand Down
32 changes: 22 additions & 10 deletions frontend/src/components/filterList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useState, useEffect, useCallback } from 'react'
import { declOfNum } from '../../functions/helpers'
import { TObjectFilters } from '../../app/types'
import translate from '../../functions/translate'
Expand All @@ -20,23 +20,35 @@ const FilterListItem: (props: { filter: TFilterListItem }) => JSX.Element = (pro
const minutes = Math.round(exposure / 60)
const lang = translate().general.declining

return <li>
<span className={`filter-${name}`}>{name}</span>
{minutes} {declOfNum(minutes, lang.minutes)} ({frames} {declOfNum(frames, lang.frames)})
</li>
return (
<li>
<span className={`filter-${name}`}>{name}</span>
{minutes} {declOfNum(minutes, lang.minutes)} ({frames} {declOfNum(frames, lang.frames)})
</li>
)
}

const FilterList: React.FC<TFilterListProps> = (props) => {
const { filters } = props
const filterList: TFilterListItem[] = []
const [ filtersList, setFiltersList ] = useState<TFilterListItem[]>([])

const doListFilter = useCallback(() => {
const tempFiltersList: TFilterListItem[] = []

Object.entries(filters).forEach(([key, value]) =>
value.exposure && tempFiltersList.push({name: key, exposure: value.exposure, frames: value.frames}))

setFiltersList(tempFiltersList)
}, [filters])

Object.entries(filters).forEach(([key, value]) =>
value.exposure && filterList.push({name: key, exposure: value.exposure, frames: value.frames}))
useEffect(() => {
doListFilter()
}, [filters, doListFilter])

return (
<ul className='filter-list'>
{filterList.map((item, key) =>
<FilterListItem filter={item} key={key} />
{filtersList.map((item) =>
<FilterListItem filter={item} key={item.name} />
)}
</ul>
)
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/components/newsList/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ type TNewsListProps = {
news: TNews[] | undefined
}

const NewsLoader = () => <div className='box loader'>
<Dimmer active>
<Loader>Загрузка</Loader>
</Dimmer>
</div>
const NewsLoader = () => (
<div className='box loader'>
<Dimmer active>
<Loader>Загрузка</Loader>
</Dimmer>
</div>
)

const NewsList: React.FC<TNewsListProps> = (props) => {
const { loader, news } = props
Expand Down
Loading

0 comments on commit f90a4a8

Please sign in to comment.