Skip to content

Commit

Permalink
Merge pull request #212 from vsimakhin/responsive-ui
Browse files Browse the repository at this point in the history
new responsive ui
  • Loading branch information
vsimakhin authored May 18, 2024
2 parents 1bd5bd0 + 23b9d74 commit 350da0e
Show file tree
Hide file tree
Showing 101 changed files with 3,729 additions and 5,347 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [Unreleased]

- New: Implemented a new responsive user interface that is friendly for desktop, mobile, and tablet devices. The design is based on the adminkit.io template, and all pages are refactored to ensure a seamless user experience across different platforms. Probably some new bugs are introduced ¯\\_(ツ)_*
- Update: rename cmd/web directory to app, just internal change.
- Update: Code optimization. No UI change. Includes:
- Migrated all javascript code to a js files instead of keeping them in gohtml templates, plus some html code optimization. This significantly reduces amouunt of typo and errors.
Expand Down
113 changes: 58 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Web-logbook

This is a simple free EASA-style logbook application written in golang.
This is a simple, free and opensource EASA-style logbook application written in golang.

You can clone the repo and compile the binaries yourself, or just download the latest ones for your operating system from the [releases](https://github.com/vsimakhin/web-logbook/releases).

Expand All @@ -12,6 +12,7 @@ You also can easily export all flight records into EASA style pdf format, print

## [Unreleased]

- New: Implemented a new responsive user interface that is friendly for desktop, mobile, and tablet devices. The design is based on the adminkit.io template, and all pages are refactored to ensure a seamless user experience across different platforms. Probably some new bugs are introduced ¯\\_(ツ)_*
- Update: rename cmd/web directory to app, just internal change.
- Update: Code optimization. No UI change. Includes:
- Migrated all javascript code to a js files instead of keeping them in gohtml templates, plus some html code optimization. This significantly reduces amouunt of typo and errors.
Expand Down Expand Up @@ -50,11 +51,9 @@ The full changelog is [here](https://github.com/vsimakhin/web-logbook/blob/main/
1. Run:
* Windows:
* Double-click on the `web-logbook.exe` file. It will show you some warning about how unsafe it can be (need to solve it later), but just run it.
* Linux:
* Linux/MacOS:
* Open a terminal and navigate to the directory
* Run `./web-logbook`
* MacOS:
* *I still didn't test it for MacOS, so in theory, should be as same as for Linux, but... who knows ¯\\_(ツ)_*
4. Open your browser, type http://localhost:4000 and the application is ready to use
* *(first run)* Go to the [Settings](http://localhost:4000/settings) page, `Airports` tab and click on the `Update Airport DB` button
6. To close the application, use `Ctrl+C` in the terminal window or just close it
Expand Down Expand Up @@ -86,87 +85,90 @@ $ ./web-logbook -h
# Supported operating systems
Since it's written in golang it can run on any system if you compile the sources. For now, on the [Release](https://github.com/vsimakhin/web-logbook/releases/latest) page, there are 3 binaries for Linux, MacOS and Windows, all of them are amd64.
Since it's written in Golang, it can run on any system after compiling the sources. Currently, on the [Release](https://github.com/vsimakhin/web-logbook/releases/latest) page, there are binaries available for Linux, MacOS, and Windows.
There is an application [Web Logbook Mobile Ionic](https://github.com/vsimakhin/web-logbook-mobile-ionic) for Android (and later I hope will be for IOS), which can sync with the main application.
# Interface
Currently, there are implemented several modules in the logbook app:
* Logbook itself
* Flight records
* Logbook
* Flight records with date filter and global search through all data
* Quick export to PDF (A4, A5) and CSV/XLS
* Flight records
* Flight data
* Attachments for the flight records
* Automatic night-time calculation
* Map drawing and distance calculation for the flight record
* Settings
* Signature and owner name
* Signature pad to automatically include signatures to the PDF exports
* Enable/Disable authentication (in case you need to expose the app to the public internet)
* Aircraft groups/classes
* List global airport database
* Your own custom airfields or heliports
* Some interface settings
* Export
* Export to EASA PDF format (A4 and A5)
* Additional export formats (XLSX, CSV)
* Adjustable settings for each export format
* Import
* CSV support
* Automatic WebLogbook profile load
* Map
* Filters for routes and airports
* Filters for the aircraft
* Licensing & Certification
* List of licenses, certificates and endorsements
* Document attachments and preview
* Expiration time tracking
* Map
* Map of the flights
* Date filters
* Routes and airports filters
* Aircraft filters
* Statistics
* Totals
* By Year
* By Aircraft
* By Aircraft group/class, defined in settings
* By Aircraft group/class
* Limits (EASA flight time limitations)
* Export
* Export to EASA PDF format (A4 and A5)
* PDF export formats with custom title pages (for example, include your CV automatically)
* Additional export formats (XLSX, CSV)
* Adjustable settings for each export format
* Import
* CSV support
* Automatic WebLogbook profile load
* Settings
* Owner name, license and address, signature for the PDF exports
* Signature pad to automatically include signatures to the PDF exports
* Enable/Disable authentication (in case you need to expose the app to the public internet)
* Aircraft groups/classes
* Global airport database
* Your own custom airfields or heliports
* Some interface settings
## Logbook
![EASA Logbook](./readme-assets/logbook.png)
![Main logbook page](https://github.com/vsimakhin/web-logbook-assets/raw/main/logbook-main.png)
## Flight record
![Flight record](./readme-assets/flight-record.png)
## Export
## Licensing & Certification
![Licensing & Certification](./readme-assets/licensing-record.png)
![Export](https://github.com/vsimakhin/web-logbook-assets/raw/main/export.png)
## Map
![Map of the flights](./readme-assets/map-example.png)
## Stats example
![Flight stats example](./readme-assets/stats-example.png)
## Export
![Export](./readme-assets/export-page.png)
### A4
![Export to PDF](https://github.com/vsimakhin/web-logbook-assets/raw/main/logbook-export.png)
![Export to PDF](./readme-assets/logbook-export.png)
### A5
![Export to PDF](https://github.com/vsimakhin/web-logbook-assets/raw/main/export-a5-a.png)
![Export to PDF](https://github.com/vsimakhin/web-logbook-assets/raw/main/export-a5-b.png)
![Export to PDF](./readme-assets/export-a5-a.png)
![Export to PDF](./readme-assets/export-a5-b.png)
So in real life the logbook could look like
![Pilot logbook](https://github.com/vsimakhin/web-logbook-assets/raw/main/logbook_irl.jpg)
## Flight record
![Pilot logbook](./readme-assets/logbook_irl.jpg)
![Flight record](https://github.com/vsimakhin/web-logbook-assets/raw/main/flight-record-example.png)
### Attachments
![Flight record attachments](https://github.com/vsimakhin/web-logbook-assets/raw/main/flight-record-example-attachments.png)
## Import
![Import](./readme-assets/import.png)
## Settings
![Settings](./readme-assets/settings-general.png)
![Settings](https://github.com/vsimakhin/web-logbook-assets/raw/main/settings.png)
![Settings-Airports](https://github.com/vsimakhin/web-logbook-assets/raw/main/settings-airports.png)
## Stats
![Flight stats](https://github.com/vsimakhin/web-logbook-assets/raw/main/stats.png)
![Map](https://github.com/vsimakhin/web-logbook-assets/raw/main/stats-map.png)
## Licensing & Certifications
![Licensing](https://github.com/vsimakhin/web-logbook-assets/raw/main/licensing.png)
## Dark mode
![Dark mode](./readme-assets/dark-mode.png)
![Licensing record](https://github.com/vsimakhin/web-logbook-assets/raw/main/licensing-record.png)
## Mobile friendly
![Mobile friendly](./readme-assets/mobile-friendly.png)
# Airports Databases
Expand Down Expand Up @@ -219,6 +221,7 @@ In case you'd like to add some other features to the logbook or you found a bug,
# Used libraries
* Adminkit.io https://adminkit.io
* Bootstrap https://getbootstrap.com/
* Datatables https://datatables.net/
* Openlayers https://openlayers.org/
Expand Down
2 changes: 1 addition & 1 deletion app/handlers_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (app *application) HandlerLoginPost(w http.ResponseWriter, r *http.Request)
time.Now().Unix() + int64(loginAttempts[ip].failedAttempts*10),
}

app.errorLog.Println(err)
app.warningLog.Println(err)
response.OK = false
response.Message = err.Error()

Expand Down
10 changes: 0 additions & 10 deletions app/handlers_auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -24,18 +23,13 @@ func TestAuth(t *testing.T) {
// auth enabled
app.isAuthEnabled = true
resp, _ := http.Get(fmt.Sprintf("%s/", srv.URL))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, string(responseBody), `<input class="form-control" id="login" type="login" placeholder="Login"`)

// auth disabled
app.isAuthEnabled = false
resp, _ = http.Get(fmt.Sprintf("%s/", srv.URL))
responseBody, _ = io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, string(responseBody), `<link rel="stylesheet" type="text/css" href="/static/css/datatables.min.css"/>`)

}

Expand All @@ -49,10 +43,8 @@ func TestHandlerLogin(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, APILogin))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, string(responseBody), `<input class="form-control" id="login" type="login" placeholder="Login"`)

}

Expand All @@ -66,9 +58,7 @@ func TestHandlerLogout(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, APILogout))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Contains(t, string(responseBody), `<input class="form-control" id="login" type="login" placeholder="Login"`)

}
3 changes: 0 additions & 3 deletions app/handlers_aux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -20,8 +19,6 @@ func TestHandlerNotFound(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s/notfound", srv.URL))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusNotFound, resp.StatusCode)
assert.Contains(t, string(responseBody), `<p class="lead">This requested URL was not found on this server</p>`)
}
23 changes: 17 additions & 6 deletions app/handlers_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,23 @@ const exportA5 = "A5"
const exportCSV = "csv"
const exportXLS = "xls"

// HandlerExport is a handler for /export page
func (app *application) HandlerExport(w http.ResponseWriter, r *http.Request) {
// HandlerExportPDFA4Page is a handler for /export-pdf-a4 page
func (app *application) HandlerExportPDFA4Page(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "export-pdf-a4", &templateData{}); err != nil {
app.errorLog.Println(err)
}
}

// HandlerExportPDFA5Page is a handler for /export-pdf-a5 page
func (app *application) HandlerExportPDFA5Page(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "export-pdf-a5", &templateData{}); err != nil {
app.errorLog.Println(err)
}
}

partials := []string{"export-a4", "export-a5", "export-xls", "export-csv"}
if err := app.renderTemplate(w, r, "export", &templateData{}, partials...); err != nil {
// HandlerExportPDFA5Page is a handler for /export-pdf-a5 page
func (app *application) HandlerExportCSVXLSPage(w http.ResponseWriter, r *http.Request) {
if err := app.renderTemplate(w, r, "export-csv-xls", &templateData{}); err != nil {
app.errorLog.Println(err)
}
}
Expand Down Expand Up @@ -181,8 +193,7 @@ func (app *application) HandlerExportRestoreDefaults(w http.ResponseWriter, r *h
response.Message = err.Error()
} else {
response.OK = true
response.Message = "Export settings have been updated"
response.RedirectURL = fmt.Sprintf("%s?param=%s", APIExport, param)
response.Message = "Export settings have been restored. Refresh the page to see the changes."
}

app.writeJSON(w, http.StatusOK, response)
Expand Down
21 changes: 0 additions & 21 deletions app/handlers_export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,6 @@ import (
"github.com/vsimakhin/web-logbook/internal/models"
)

func TestHandlerExport(t *testing.T) {

app, mock := initTestApplication()

models.InitMock(mock, "GetSettings")

srv := httptest.NewServer(app.routes())
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, APIExport))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)

assert.Contains(t, string(responseBody), `PDF A4`)
assert.Contains(t, string(responseBody), `PDF A5`)
assert.Contains(t, string(responseBody), `XLS`)
assert.Contains(t, string(responseBody), `CSV`)

}

func TestHandlerExportLogbook(t *testing.T) {

app, mock := initTestApplication()
Expand Down
9 changes: 0 additions & 9 deletions app/handlers_flightrecord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
Expand All @@ -24,13 +23,9 @@ func TestHandlerFlightRecordNew(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, APILogbookNew))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)

assert.Contains(t, string(responseBody), `<link rel="stylesheet" href="/static/css/ol.css">`)
assert.Contains(t, string(responseBody), `<script src="/static/js/ol.js"></script>`)

}

func TestHandlerFlightRecordByID(t *testing.T) {
Expand All @@ -46,11 +41,7 @@ func TestHandlerFlightRecordByID(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, strings.ReplaceAll(APILogbookUUID, "{uuid}", "uuid")))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)

assert.Contains(t, string(responseBody), `<link rel="stylesheet" href="/static/css/ol.css">`)
assert.Contains(t, string(responseBody), `<script src="/static/js/ol.js"></script>`)

}
1 change: 0 additions & 1 deletion app/handlers_licensing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ func TestHandlerLicensing(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)

// need to have datatables css and js
assert.Contains(t, string(responseBody), `<link rel="stylesheet" type="text/css" href="/static/css/datatables.min.css"/>`)
assert.Contains(t, string(responseBody), `<script type="text/javascript" src="/static/js/datatables.min.js"></script>`)

}
Expand Down
1 change: 0 additions & 1 deletion app/handlers_logbook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ func TestHandlerLogbook(t *testing.T) {
assert.Equal(t, http.StatusOK, resp.StatusCode)

// need to have datatables css and js
assert.Contains(t, string(responseBody), `<link rel="stylesheet" type="text/css" href="/static/css/datatables.min.css"/>`)
assert.Contains(t, string(responseBody), `<script type="text/javascript" src="/static/js/datatables.min.js"></script>`)

}
Expand Down
5 changes: 0 additions & 5 deletions app/handlers_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
Expand All @@ -24,13 +23,9 @@ func TestHandlerMap(t *testing.T) {
defer srv.Close()

resp, _ := http.Get(fmt.Sprintf("%s%s", srv.URL, APIMap))
responseBody, _ := io.ReadAll(resp.Body)

assert.Equal(t, http.StatusOK, resp.StatusCode)

assert.Contains(t, string(responseBody), `<script src="/static/js/ol.js"></script>`)
assert.Contains(t, string(responseBody), `<link rel="stylesheet" href="/static/css/ol.css">`)

}

func TestHandlerMapGetData(t *testing.T) {
Expand Down
Loading

0 comments on commit 350da0e

Please sign in to comment.