├── ember ├── vendor │ └── .gitkeep ├── app │ ├── helpers │ │ ├── .gitkeep │ │ ├── lowercase.js │ │ ├── to-iso-string.js │ │ ├── sum.js │ │ ├── includes.js │ │ ├── multiply.js │ │ ├── percent.js │ │ ├── truncate.js │ │ ├── html-safe.js │ │ ├── format-lift.js │ │ ├── format-speed.js │ │ ├── format-altitude.js │ │ ├── format-distance.js │ │ ├── format-hours.js │ │ ├── initials.js │ │ ├── markdown.js │ │ └── format-seconds.js │ ├── models │ │ └── .gitkeep │ ├── routes │ │ ├── .gitkeep │ │ ├── flights │ │ │ ├── all.js │ │ │ ├── latest.js │ │ │ ├── date.js │ │ │ ├── index.js │ │ │ ├── list.js │ │ │ ├── club.js │ │ │ ├── unassigned.js │ │ │ ├── pilot.js │ │ │ ├── airport.js │ │ │ └── pinned.js │ │ ├── statistics │ │ │ ├── index.js │ │ │ ├── club.js │ │ │ ├── pilot.js │ │ │ └── airport.js │ │ ├── ranking │ │ │ └── index.js │ │ ├── settings │ │ │ ├── profile.js │ │ │ ├── index.js │ │ │ ├── tracking.js │ │ │ └── club.js │ │ ├── flight │ │ │ ├── map-redirect.js │ │ │ └── change-aircraft.js │ │ ├── ranking.js │ │ ├── tracking │ │ │ ├── map-redirect.js │ │ │ ├── index.js │ │ │ ├── details.js │ │ │ └── info.js │ │ ├── flight.js │ │ ├── about │ │ │ ├── team.js │ │ │ ├── imprint.js │ │ │ └── license.js │ │ ├── club │ │ │ ├── index.js │ │ │ ├── edit.js │ │ │ └── pilots.js │ │ ├── clubs.js │ │ ├── users │ │ │ └── index.js │ │ ├── club.js │ │ ├── user.js │ │ ├── settings.js │ │ ├── user │ │ │ ├── followers.js │ │ │ └── following.js │ │ └── login.js │ ├── components │ │ ├── .gitkeep │ │ ├── cesium-scene.hbs │ │ ├── flight-page.module.scss │ │ ├── phase-icons-layer.hbs │ │ ├── timeline-event.hbs │ │ ├── cesium-plane-model.hbs │ │ ├── fix-table.module.scss │ │ ├── tracking-page.module.scss │ │ ├── x-flag.hbs │ │ ├── progress-bar.hbs │ │ ├── timeline.module.scss │ │ ├── nav-bar-menu-right.module.scss │ │ ├── pin-star.module.scss │ │ ├── plane-label-overlay.module.scss │ │ ├── comments-list.module.scss │ │ ├── flight-path-layer.hbs │ │ ├── takeoffs-layer.hbs │ │ ├── base-map.hbs │ │ ├── nav-bar.js │ │ ├── search-result-row.module.scss │ │ ├── wingman-row.module.scss │ │ ├── loading-page.hbs │ │ ├── sidebar │ │ │ ├── tabs.hbs │ │ │ ├── tabs.module.scss │ │ │ ├── panel.hbs │ │ │ ├── tab.hbs │ │ │ ├── tab.js │ │ │ └── tab.module.scss │ │ ├── timeline.hbs │ │ ├── x-pager.module.scss │ │ ├── flight-list-row.module.scss │ │ ├── fullscreen-button.hbs │ │ ├── notifications-badge.hbs │ │ ├── progress-bar.js │ │ ├── validated-input.js │ │ ├── plane-label-overlays.hbs │ │ ├── cesium-plane-models.hbs │ │ ├── flight-phase-table.module.scss │ │ ├── settings-page.js │ │ ├── flight-phase-table.js │ │ ├── follower-panel.module.scss │ │ ├── flight-details-table.module.scss │ │ ├── follower-list.hbs │ │ ├── flight-performance-panel.hbs │ │ ├── plane-label-overlay.hbs │ │ ├── season-dropdown.js │ │ ├── nav-bar-search-form.js │ │ ├── upload-result-form.module.scss │ │ ├── base-barogram.hbs │ │ ├── notifications-badge.js │ │ ├── cesium-button.hbs │ │ ├── pin-button.hbs │ │ ├── takeoffs-map.hbs │ │ ├── flight-leg-panel.hbs │ │ ├── playback-button.hbs │ │ ├── sidebar.hbs │ │ ├── timeline-event.js │ │ ├── follower-page.hbs │ │ ├── following-page.hbs │ │ ├── pin-star.hbs │ │ ├── stats-flights-table.js │ │ ├── login-form.module.scss │ │ ├── stats-distance-table.js │ │ ├── stats-duration-table.js │ │ ├── form-page.hbs │ │ ├── stats-pilots-table.js │ │ ├── aircraft-model-select.hbs │ │ ├── contest-layer.hbs │ │ ├── tracking-table.module.scss │ │ ├── nav-bar-search-form.module.scss │ │ ├── tracking-table-row.js │ │ ├── cesium-button.js │ │ ├── nav-bar-link.hbs │ │ ├── pilot-select.hbs │ │ ├── timeline-events │ │ │ ├── new-user.js │ │ │ ├── club-join.js │ │ │ ├── new-user.hbs │ │ │ ├── flight-upload.js │ │ │ ├── club-join.hbs │ │ │ ├── follower.hbs │ │ │ ├── flight-upload.hbs │ │ │ ├── -base.js │ │ │ └── flight-comment.hbs │ │ ├── tracking-tables.hbs │ │ ├── base-page.module.scss │ │ ├── layer-switcher-element.js │ │ ├── validated-block.hbs │ │ ├── circling-performance-row.hbs │ │ ├── validated-input.hbs │ │ ├── fix-table-row.module.scss │ │ ├── nav-bar-user-menu.js │ │ ├── fix-table.hbs │ │ ├── base-page.hbs │ │ ├── cesium-button.module.scss │ │ ├── flight-list-row.js │ │ ├── playback-button.module.scss │ │ ├── flight-list-nav.module.scss │ │ ├── progress-bar.module.scss │ │ ├── wingman-table.hbs │ │ ├── layer-switcher.js │ │ ├── search-result-table.hbs │ │ ├── tracking-table.hbs │ │ ├── nav-bar-search-form.hbs │ │ ├── fix-table-row.js │ │ ├── pin-button.js │ │ ├── settings-panels │ │ │ └── tracking-key.module.scss │ │ ├── pin-star.js │ │ ├── fullscreen-button.module.scss │ │ ├── layer-switcher-element.hbs │ │ ├── user-quick-stats.js │ │ ├── background-layers.hbs │ │ ├── user-distance-flight.hbs │ │ ├── sortable-table-header.hbs │ │ ├── background-layers │ │ │ └── empty.js │ │ ├── contest-layer-feature.js │ │ ├── circling-performance-table.hbs │ │ ├── circling-performance-table.js │ │ ├── login-form.js │ │ ├── aircraft-model-select.js │ │ └── fullscreen-button.js │ ├── controllers │ │ ├── .gitkeep │ │ ├── flights.js │ │ ├── login.js │ │ ├── ranking │ │ │ ├── clubs.js │ │ │ ├── pilots.js │ │ │ └── airports.js │ │ ├── users │ │ │ ├── recover.js │ │ │ └── new.js │ │ ├── ranking.js │ │ ├── settings │ │ │ └── club.js │ │ ├── statistics.js │ │ ├── flight │ │ │ ├── index.js │ │ │ ├── change-pilot.js │ │ │ └── change-aircraft.js │ │ ├── flights │ │ │ ├── all.js │ │ │ ├── club.js │ │ │ ├── date.js │ │ │ ├── list.js │ │ │ ├── airport.js │ │ │ ├── latest.js │ │ │ ├── pilot.js │ │ │ ├── pinned.js │ │ │ └── unassigned.js │ │ ├── flight-upload.js │ │ ├── application.js │ │ ├── index.js │ │ ├── stats │ │ │ └── wildcard.js │ │ ├── search.js │ │ └── about │ │ │ └── team.js │ ├── templates │ │ ├── flight │ │ │ ├── index_loading.hbs │ │ │ ├── change-pilot.hbs │ │ │ └── change-aircraft.hbs │ │ ├── tracking │ │ │ ├── details_loading.hbs │ │ │ ├── index_loading.hbs │ │ │ ├── index.hbs │ │ │ └── details.hbs │ │ ├── user │ │ │ ├── index.hbs │ │ │ ├── followers.hbs │ │ │ └── following.hbs │ │ ├── club │ │ │ └── edit.hbs │ │ ├── settings │ │ │ ├── password.hbs │ │ │ ├── club.hbs │ │ │ ├── tracking.hbs │ │ │ └── profile.hbs │ │ ├── users │ │ │ ├── new.hbs │ │ │ └── recover.hbs │ │ ├── login.hbs │ │ ├── application.hbs │ │ ├── error.hbs │ │ ├── statistics │ │ │ ├── pilot.hbs │ │ │ ├── club.hbs │ │ │ ├── airport.hbs │ │ │ └── index.hbs │ │ ├── about │ │ │ ├── team.hbs │ │ │ ├── imprint.hbs │ │ │ └── license.hbs │ │ ├── flights.hbs │ │ ├── page-not-found.hbs │ │ ├── flights │ │ │ ├── all.hbs │ │ │ ├── list.hbs │ │ │ ├── pinned.hbs │ │ │ ├── unassigned.hbs │ │ │ ├── club.hbs │ │ │ ├── date.hbs │ │ │ ├── pilot.hbs │ │ │ ├── airport.hbs │ │ │ └── latest.hbs │ │ └── flight-upload.hbs │ ├── styles │ │ ├── bootstrap │ │ │ ├── _modals.scss │ │ │ ├── _page-header.scss │ │ │ ├── _tables.scss │ │ │ └── _bootstrap-badges.scss │ │ ├── timeline.module.scss │ │ ├── notifications.module.scss │ │ ├── application.module.scss │ │ ├── app.scss │ │ ├── _font-awesome.scss │ │ ├── ranking.module.scss │ │ └── _spinner.scss │ ├── resolver.js │ ├── utils │ │ ├── pad.js │ │ ├── iso-date.js │ │ ├── add-days.js │ │ ├── raf.js │ │ ├── locales.js │ │ ├── parse-query-string.js │ │ └── geo-distance.js │ ├── services │ │ └── search-text.js │ ├── modifiers │ │ └── set-map-target.js │ ├── computed │ │ ├── is-none.js │ │ └── computed-point.js │ ├── app.js │ ├── formats.js │ └── validators │ │ └── current-password.js ├── translations │ ├── da.yaml │ ├── it.yaml │ ├── ja.yaml │ ├── no.yaml │ ├── pt.yaml │ ├── hu.yaml │ ├── sv.yaml │ ├── fi.yaml │ ├── cs.yaml │ └── ru.yaml ├── .watchmanconfig ├── public │ ├── robots.txt │ ├── _redirects │ ├── flags.png │ ├── 3d │ │ └── AS21.glb │ ├── favicon.ico │ ├── images │ │ ├── 2d.png │ │ ├── 3d.png │ │ ├── play.png │ │ ├── stop.png │ │ ├── layers.png │ │ ├── marker.png │ │ ├── about-1.jpeg │ │ ├── about-2.jpeg │ │ ├── about-3.jpeg │ │ ├── about-4.jpeg │ │ ├── layers │ │ │ ├── Empty.png │ │ │ ├── Flight.png │ │ │ ├── Terrain.png │ │ │ ├── Airspace.png │ │ │ ├── Bing Road.png │ │ │ ├── Bing Satellite.png │ │ │ ├── Google Physical.png │ │ │ ├── OpenStreetMap.png │ │ │ ├── Shaded Relief.png │ │ │ ├── Google Satellite.png │ │ │ ├── Takeoff Locations.png │ │ │ └── Mountain Wave Project.png │ │ ├── marker-gold.png │ │ ├── marker-green.png │ │ ├── glider_symbol.png │ │ ├── hangglider_symbol.png │ │ ├── paraglider_symbol.png │ │ ├── glider_symbol_msie.png │ │ └── motorglider_symbol.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── svg │ │ └── icons │ │ ├── play.svg │ │ ├── minus.svg │ │ ├── caret-down.svg │ │ ├── bar-chart.svg │ │ ├── long-arrow-right.svg │ │ ├── chevron-down.svg │ │ ├── chevron-up.svg │ │ ├── check.svg │ │ ├── step-forward.svg │ │ ├── plus.svg │ │ ├── map-marker.svg │ │ ├── arrows-h.svg │ │ ├── bolt.svg │ │ ├── arrow-left.svg │ │ ├── cloud.svg │ │ ├── arrow-right.svg │ │ ├── star.svg │ │ ├── circle-o.svg │ │ ├── pencil.svg │ │ ├── remove.svg │ │ ├── facebook-square.svg │ │ ├── info.svg │ │ ├── reorder.svg │ │ ├── user.svg │ │ ├── star-o.svg │ │ ├── plane.svg │ │ ├── ban.svg │ │ ├── search.svg │ │ ├── clock-o.svg │ │ ├── warning.svg │ │ ├── twitter.svg │ │ ├── info-circle.svg │ │ ├── wrench.svg │ │ ├── asterisk.svg │ │ ├── download.svg │ │ ├── rotate-right.svg │ │ ├── sign-in.svg │ │ ├── sign-out.svg │ │ ├── upload.svg │ │ ├── rotate-left.svg │ │ └── question.svg ├── .bowerrc ├── config │ ├── dependency-lint.js │ ├── coverage.js │ ├── optional-features.json │ └── targets.js ├── lib │ ├── cesium │ │ ├── package.json │ │ └── index.js │ ├── freestyle │ │ ├── package.json │ │ ├── index.js │ │ └── app │ │ │ ├── templates │ │ │ ├── components │ │ │ │ └── usage │ │ │ │ │ └── x-flag.hbs │ │ │ └── freestyle.hbs │ │ │ └── routes │ │ │ └── freestyle.js │ └── .eslintrc.js ├── mirage │ ├── route-handlers │ │ ├── clubs.js │ │ └── notifications.js │ ├── models │ │ ├── user.js │ │ ├── club.js │ │ └── mirage-session.js │ ├── factories │ │ ├── club.js │ │ ├── mirage-session.js │ │ └── user.js │ ├── scenarios │ │ └── default.js │ ├── utils │ │ └── session.js │ ├── serializers │ │ ├── application.js │ │ └── club.js │ └── config.js ├── bower.json ├── tests │ ├── .eslintrc.js │ ├── test-helpers │ │ ├── match-json.js │ │ └── auth.js │ ├── test-helper.js │ ├── helpers │ │ └── initials-test.js │ └── acceptance │ │ └── index-test.js ├── .eslintignore ├── .gitignore ├── .ember-cli └── .template-lintrc.js ├── tests ├── lib │ ├── __init__.py │ ├── test_basic_auth.py │ └── test_files.py ├── model │ └── __init__.py ├── api │ ├── views │ │ ├── __init__.py │ │ ├── clubs │ │ │ └── __init__.py │ │ ├── users │ │ │ └── __init__.py │ │ ├── account │ │ │ └── __init__.py │ │ ├── airports │ │ │ └── __init__.py │ │ ├── flights │ │ │ └── __init__.py │ │ ├── aircraft_models │ │ │ └── __init__.py │ │ ├── notifications │ │ │ └── __init__.py │ │ ├── conftest.py │ │ └── user_agent_test.py │ └── __init__.py ├── tracking │ ├── __init__.py │ └── test_datetime.py ├── data │ ├── igcs.zip │ ├── __init__.py │ ├── clubs.py │ ├── flight_comments.py │ ├── live_fix.py │ └── airspace.py └── schemas │ ├── fields │ └── test_location.py │ └── schemas │ └── test_flight.py ├── migrations ├── versions │ ├── .gitkeep │ ├── 3e82b37a4989_non_null_tracking_ke.py │ ├── 5efafe47090_removed_eye_candy.py │ ├── ffa5706b1fb_added_callsign_colum.py │ ├── 2dade673f10e_add_qnh_column_to_flight.py │ ├── 66650ad3d70_unregistered_pilot_n.py │ ├── 58325345d375_add_weglide_fields_to_igcfile.py │ ├── 1f33435e1753_added_valid_until_co.py │ ├── 8a8e921cb88_added_scoring_window_columns.py │ └── 16f9f28f0ea3_added_privacy_level_column.py └── script.py.mako ├── skylines ├── api │ ├── __init__.py │ ├── cache.py │ ├── views │ │ ├── airports.py │ │ └── aircraft_models.py │ ├── json.py │ └── args.py ├── lib │ ├── __init__.py │ ├── helpers │ │ └── __init__.py │ ├── waypoints │ │ └── __init__.py │ ├── formatter │ │ └── __init__.py │ ├── xcsoar_ │ │ └── __init__.py │ ├── md5.py │ ├── basic_auth.py │ ├── compat.py │ └── util.py ├── worker │ ├── __init__.py │ └── celery.py ├── frontend │ ├── __init__.py │ ├── templates │ │ ├── widgets │ │ │ └── wrapper.jinja │ │ ├── __init__.py │ │ └── error.jinja │ └── views │ │ ├── files.py │ │ ├── __init__.py │ │ └── assets.py ├── tracking │ ├── __init__.py │ ├── datetime.py │ └── crc.py ├── schemas │ ├── __init__.py │ └── validate.py ├── commands │ ├── notifications │ │ ├── __init__.py │ │ └── mark_all_unread.py │ ├── users │ │ └── __init__.py │ ├── shell.py │ ├── celery.py │ ├── import_ │ │ └── __init__.py │ ├── server.py │ └── tracking │ │ ├── server.py │ │ ├── clear.py │ │ └── fill_missing_keys.py ├── __about__.py └── __init__.py ├── pytest.ini ├── srtm ├── .gitignore ├── extract.sh ├── download.sh └── import.sh ├── mapserver ├── airports │ ├── airport.png │ └── airport_gray.png └── fonts │ └── truetype.txt ├── setup.cfg ├── manage.py ├── .dockerignore ├── wsgi_skylines.py ├── airspace └── airspace_blacklist.txt ├── wsgi_mapproxy.py ├── config └── testing.py ├── docker └── docker-entrypoint-initdb.d │ └── create-databases.sql ├── systemd ├── celery.service ├── tracking.service ├── mapproxy.service └── skylines.service ├── .github ├── FUNDING.yml └── renovate.json ├── .editorconfig ├── INSTALL.mapserver.md └── Dockerfile /ember/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /migrations/versions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/worker/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/tracking/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/lib/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/tracking/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/clubs/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /skylines/lib/waypoints/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/account/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/airports/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/flights/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/translations/da.yaml: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /ember/translations/it.yaml: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /ember/translations/ja.yaml: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /ember/translations/no.yaml: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /ember/translations/pt.yaml: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /tests/api/views/aircraft_models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/api/views/notifications/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = tests 3 | -------------------------------------------------------------------------------- /srtm/.gitignore: -------------------------------------------------------------------------------- 1 | /downloads 2 | /unzipped 3 | -------------------------------------------------------------------------------- /ember/app/templates/flight/index_loading.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/templates/tracking/details_loading.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/templates/user/index.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /ember/app/components/cesium-scene.hbs: -------------------------------------------------------------------------------- 1 | {{yield (hash ol3d=ol3d scene=scene)}} -------------------------------------------------------------------------------- /ember/app/styles/bootstrap/_modals.scss: -------------------------------------------------------------------------------- 1 | .modal-dialog { 2 | top: 40px; 3 | } 4 | -------------------------------------------------------------------------------- /skylines/api/cache.py: -------------------------------------------------------------------------------- 1 | from flask_caching import Cache 2 | 3 | cache = Cache() 4 | -------------------------------------------------------------------------------- /skylines/lib/formatter/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from .units import * 4 | -------------------------------------------------------------------------------- /ember/app/components/flight-page.module.scss: -------------------------------------------------------------------------------- 1 | .barogram { 2 | height: 133px; 3 | } 4 | -------------------------------------------------------------------------------- /ember/app/components/phase-icons-layer.hbs: -------------------------------------------------------------------------------- 1 | {{did-update this.updateSource @coordinates}} -------------------------------------------------------------------------------- /ember/app/components/timeline-event.hbs: -------------------------------------------------------------------------------- 1 | {{component this.eventComponent event=@event}} -------------------------------------------------------------------------------- /ember/app/styles/timeline.module.scss: -------------------------------------------------------------------------------- 1 | .timeline { 2 | margin: 18px -10px; 3 | } 4 | -------------------------------------------------------------------------------- /ember/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /skylines/frontend/templates/widgets/wrapper.jinja: -------------------------------------------------------------------------------- 1 | {{ callback }}('{{ content }}'); 2 | -------------------------------------------------------------------------------- /srtm/extract.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | unzip -j -d unzipped "downloads/*.zip" 4 | -------------------------------------------------------------------------------- /ember/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /ember/app/components/cesium-plane-model.hbs: -------------------------------------------------------------------------------- 1 | {{did-update this.update @coordinate @heading}} -------------------------------------------------------------------------------- /ember/app/components/fix-table.module.scss: -------------------------------------------------------------------------------- 1 | .fix-table { 2 | margin-bottom: 0; 3 | } 4 | -------------------------------------------------------------------------------- /ember/app/components/tracking-page.module.scss: -------------------------------------------------------------------------------- 1 | .barogram { 2 | height: 133px; 3 | } 4 | -------------------------------------------------------------------------------- /ember/app/styles/notifications.module.scss: -------------------------------------------------------------------------------- 1 | .timeline { 2 | margin: 18px -10px; 3 | } 4 | -------------------------------------------------------------------------------- /ember/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /ember/app/templates/user/followers.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/templates/user/following.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/x-flag.hbs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/data/igcs.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/tests/data/igcs.zip -------------------------------------------------------------------------------- /ember/app/components/progress-bar.hbs: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /ember/public/_redirects: -------------------------------------------------------------------------------- 1 | /api/* https://skylines.aero/api/:splat 200 2 | /* /index.html 200 3 | -------------------------------------------------------------------------------- /ember/public/flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/flags.png -------------------------------------------------------------------------------- /srtm/download.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | wget -i tiles.txt -P downloads -c --quiet --show-progress 4 | -------------------------------------------------------------------------------- /ember/config/dependency-lint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | generateTests: false, 5 | }; 6 | -------------------------------------------------------------------------------- /ember/public/3d/AS21.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/3d/AS21.glb -------------------------------------------------------------------------------- /ember/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/favicon.ico -------------------------------------------------------------------------------- /ember/public/images/2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/2d.png -------------------------------------------------------------------------------- /ember/public/images/3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/3d.png -------------------------------------------------------------------------------- /ember/app/components/timeline.module.scss: -------------------------------------------------------------------------------- 1 | .list { 2 | list-style: none; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | -------------------------------------------------------------------------------- /ember/lib/cesium/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cesium", 3 | "keywords": [ 4 | "ember-addon" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /ember/mirage/route-handlers/clubs.js: -------------------------------------------------------------------------------- 1 | export function register(server) { 2 | server.get('/api/clubs/:id'); 3 | } 4 | -------------------------------------------------------------------------------- /ember/public/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/play.png -------------------------------------------------------------------------------- /ember/public/images/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/stop.png -------------------------------------------------------------------------------- /skylines/frontend/templates/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Templates package for the application.""" 3 | -------------------------------------------------------------------------------- /srtm/import.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | raster2pgsql -a -e -s 4326 -t 100x100 unzipped/*.hgt elevations | psql 4 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-menu-right.module.scss: -------------------------------------------------------------------------------- 1 | .badge { 2 | vertical-align: top; 3 | padding: 4px 7px; 4 | } 5 | -------------------------------------------------------------------------------- /ember/app/utils/pad.js: -------------------------------------------------------------------------------- 1 | export default function pad(number) { 2 | return number < 10 ? `0${number}` : number; 3 | } 4 | -------------------------------------------------------------------------------- /ember/lib/freestyle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "freestyle", 3 | "keywords": [ 4 | "ember-addon" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /ember/public/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers.png -------------------------------------------------------------------------------- /ember/public/images/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/marker.png -------------------------------------------------------------------------------- /mapserver/airports/airport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/mapserver/airports/airport.png -------------------------------------------------------------------------------- /ember/app/templates/club/edit.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ember/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/apple-touch-icon.png -------------------------------------------------------------------------------- /ember/public/images/about-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/about-1.jpeg -------------------------------------------------------------------------------- /ember/public/images/about-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/about-2.jpeg -------------------------------------------------------------------------------- /ember/public/images/about-3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/about-3.jpeg -------------------------------------------------------------------------------- /ember/public/images/about-4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/about-4.jpeg -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203,E501,W503,E701,E711,E712,E722,F901 3 | 4 | [pytest] 5 | usefixtures = seeded_random 6 | -------------------------------------------------------------------------------- /ember/app/components/pin-star.module.scss: -------------------------------------------------------------------------------- 1 | .pin { 2 | cursor: pointer; 3 | } 4 | 5 | .pinned { 6 | visibility: visible; 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/plane-label-overlay.module.scss: -------------------------------------------------------------------------------- 1 | .label { 2 | composes: badge from global; 3 | min-height: 1em; 4 | } 5 | -------------------------------------------------------------------------------- /ember/public/images/layers/Empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Empty.png -------------------------------------------------------------------------------- /ember/public/images/marker-gold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/marker-gold.png -------------------------------------------------------------------------------- /ember/public/images/marker-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/marker-green.png -------------------------------------------------------------------------------- /mapserver/airports/airport_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/mapserver/airports/airport_gray.png -------------------------------------------------------------------------------- /tests/data/__init__.py: -------------------------------------------------------------------------------- 1 | def add_fixtures(db_session, *fixtures): 2 | db_session.add_all(fixtures) 3 | db_session.commit() 4 | -------------------------------------------------------------------------------- /ember/public/images/glider_symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/glider_symbol.png -------------------------------------------------------------------------------- /ember/public/images/layers/Flight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Flight.png -------------------------------------------------------------------------------- /ember/public/images/layers/Terrain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Terrain.png -------------------------------------------------------------------------------- /ember/app/components/comments-list.module.scss: -------------------------------------------------------------------------------- 1 | .spinner { 2 | composes: spinner spinner-white from global; 3 | margin-left: 7px; 4 | } 5 | -------------------------------------------------------------------------------- /ember/app/templates/settings/password.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /ember/config/coverage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | excludes: [], 5 | reporters: ['text-summary', 'html'], 6 | }; 7 | -------------------------------------------------------------------------------- /ember/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /ember/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /ember/public/images/hangglider_symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/hangglider_symbol.png -------------------------------------------------------------------------------- /ember/public/images/layers/Airspace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Airspace.png -------------------------------------------------------------------------------- /ember/public/images/layers/Bing Road.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Bing Road.png -------------------------------------------------------------------------------- /ember/public/images/paraglider_symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/paraglider_symbol.png -------------------------------------------------------------------------------- /ember/translations/hu.yaml: -------------------------------------------------------------------------------- 1 | avg-speed-abbr: Átl. sebesség 2 | avg-vario-abbr: Átl. vario 3 | duration: Időtartam 4 | glide-rate: Siklószám 5 | -------------------------------------------------------------------------------- /ember/translations/sv.yaml: -------------------------------------------------------------------------------- 1 | avg-speed-abbr: Snitthastighet 2 | avg-vario-abbr: Snittvario 3 | duration: Varaktighet 4 | glide-rate: Glidtal 5 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from skylines.commands import manager 4 | 5 | if __name__ == "__main__": 6 | manager.run() 7 | -------------------------------------------------------------------------------- /ember/app/templates/users/new.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ember/public/images/glider_symbol_msie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/glider_symbol_msie.png -------------------------------------------------------------------------------- /ember/public/images/motorglider_symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/motorglider_symbol.png -------------------------------------------------------------------------------- /ember/app/controllers/flights.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class FlightsController extends Controller {} 4 | -------------------------------------------------------------------------------- /ember/app/helpers/lowercase.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export default helper(([value]) => value?.toLowerCase()); 4 | -------------------------------------------------------------------------------- /ember/public/images/layers/Bing Satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Bing Satellite.png -------------------------------------------------------------------------------- /ember/public/images/layers/Google Physical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Google Physical.png -------------------------------------------------------------------------------- /ember/public/images/layers/OpenStreetMap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/OpenStreetMap.png -------------------------------------------------------------------------------- /ember/public/images/layers/Shaded Relief.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Shaded Relief.png -------------------------------------------------------------------------------- /mapserver/fonts/truetype.txt: -------------------------------------------------------------------------------- 1 | sans /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf 2 | serif /usr/share/fonts/truetype/dejavu/DejaVuSans-Oblique.ttf 3 | -------------------------------------------------------------------------------- /skylines/lib/xcsoar_/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from .analysis import analyse_flight 4 | from .flightpath import flight_path, FlightPathFix 5 | -------------------------------------------------------------------------------- /skylines/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | from marshmallow import ValidationError 2 | 3 | from .schemas import * # NOQA 4 | 5 | __all__ = ("ValidationError",) 6 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | .coverage 3 | .envrc 4 | .git/ 5 | .github/ 6 | .idea/ 7 | .local/ 8 | .vagrant/ 9 | ember/node_modules/ 10 | htdocs/ 11 | -------------------------------------------------------------------------------- /ember/mirage/models/user.js: -------------------------------------------------------------------------------- 1 | import { Model, belongsTo } from 'ember-cli-mirage'; 2 | 3 | export default Model.extend({ 4 | club: belongsTo(), 5 | }); 6 | -------------------------------------------------------------------------------- /ember/public/images/layers/Google Satellite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Google Satellite.png -------------------------------------------------------------------------------- /ember/public/images/layers/Takeoff Locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Takeoff Locations.png -------------------------------------------------------------------------------- /wsgi_skylines.py: -------------------------------------------------------------------------------- 1 | import config 2 | 3 | config.to_envvar() 4 | 5 | from skylines import create_combined_app 6 | 7 | application = create_combined_app() 8 | -------------------------------------------------------------------------------- /ember/app/components/flight-path-layer.hbs: -------------------------------------------------------------------------------- 1 | {{#each @flights as |flight|}} 2 | 3 | {{/each}} -------------------------------------------------------------------------------- /ember/app/components/takeoffs-layer.hbs: -------------------------------------------------------------------------------- 1 | {{#each locations as |location|}} 2 | 3 | {{/each}} -------------------------------------------------------------------------------- /ember/app/helpers/to-iso-string.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export default helper(([date]) => new Date(date).toISOString()); 4 | -------------------------------------------------------------------------------- /ember/app/templates/login.hbs: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | -------------------------------------------------------------------------------- /ember/mirage/models/club.js: -------------------------------------------------------------------------------- 1 | import { Model, belongsTo } from 'ember-cli-mirage'; 2 | 3 | export default Model.extend({ 4 | owner: belongsTo('user'), 5 | }); 6 | -------------------------------------------------------------------------------- /tests/api/views/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(scope="function", autouse=True) 5 | def autouse_db_session(db_session): 6 | pass 7 | -------------------------------------------------------------------------------- /ember/app/components/base-map.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /ember/app/components/nav-bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | export default class NavBar extends Component { 4 | collapsed = true; 5 | } 6 | -------------------------------------------------------------------------------- /ember/app/components/search-result-row.module.scss: -------------------------------------------------------------------------------- 1 | .column-icon { 2 | width: 1px; 3 | text-align: center; 4 | } 5 | 6 | .column-type { 7 | width: 70px; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/services/search-text.js: -------------------------------------------------------------------------------- 1 | import Service from '@ember/service'; 2 | 3 | export default class SearchTextService extends Service { 4 | text = null; 5 | } 6 | -------------------------------------------------------------------------------- /ember/public/images/layers/Mountain Wave Project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skylines-project/skylines/HEAD/ember/public/images/layers/Mountain Wave Project.png -------------------------------------------------------------------------------- /ember/app/components/wingman-row.module.scss: -------------------------------------------------------------------------------- 1 | .wingman-row:hover { 2 | background-color: #eeeeee; 3 | } 4 | 5 | .color-stripe { 6 | width: 3px; 7 | padding: 0; 8 | } 9 | -------------------------------------------------------------------------------- /ember/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "jquery-integration": false, 4 | "template-only-glimmer-components": true 5 | } 6 | -------------------------------------------------------------------------------- /ember/translations/fi.yaml: -------------------------------------------------------------------------------- 1 | avg-speed-abbr: Keskinopeus 2 | avg-vario-abbr: Keskinosto 3 | duration: Kesto 4 | glide-rate: Liitosuhde 5 | glide-rate-abbr: GR 6 | start: Lähtö 7 | -------------------------------------------------------------------------------- /ember/mirage/route-handlers/notifications.js: -------------------------------------------------------------------------------- 1 | export function register(server) { 2 | // TODO implement this properly 3 | server.get('/api/notifications', { events: [] }); 4 | } 5 | -------------------------------------------------------------------------------- /ember/translations/cs.yaml: -------------------------------------------------------------------------------- 1 | avg-speed-abbr: Pr. rychlost 2 | avg-vario-abbr: Vário průměr 3 | duration: Doba 4 | glide-rate: Klouzavost 5 | glide-rate-abbr: Klouz. 6 | start: Začátek 7 | -------------------------------------------------------------------------------- /ember/app/routes/flights/all.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class AllRoute extends BaseRoute { 4 | getURL() { 5 | return '/api/flights/all'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/styles/application.module.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --ecn-container-position: 60px; 3 | } 4 | 5 | :global(.c-notification) { 6 | box-shadow: 0 5px 15px hsla(0, 0%, 0%, 0.2); 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/styles/bootstrap/_page-header.scss: -------------------------------------------------------------------------------- 1 | .page-header { 2 | position: relative; 3 | padding: 0; 4 | margin: 10px 0; 5 | 6 | h1 { 7 | margin-top: 0; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/components/loading-page.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
-------------------------------------------------------------------------------- /ember/app/components/sidebar/tabs.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{yield (hash Tab=(component "sidebar/tab" selectedTab=@selectedTab onSelect=@onSelect))}} 3 |
-------------------------------------------------------------------------------- /ember/app/components/timeline.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each @events as |event|}} 3 |
  • 4 | 5 |
  • 6 | {{/each}} 7 |
-------------------------------------------------------------------------------- /ember/app/components/x-pager.module.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | composes: pagination from global; 3 | 4 | margin: 0; 5 | 6 | > li > a { 7 | line-height: 22px; 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /ember/app/controllers/login.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class LoginController extends Controller { 4 | queryParams = ['next']; 5 | next = null; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/routes/flights/latest.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class LatestRoute extends BaseRoute { 4 | getURL() { 5 | return '/api/flights/latest'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/statistics/index.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class IndexRoute extends BaseRoute { 4 | getURL() { 5 | return '/api/statistics/'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/utils/iso-date.js: -------------------------------------------------------------------------------- 1 | import pad from './pad'; 2 | 3 | export default function isoDate(date) { 4 | return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`; 5 | } 6 | -------------------------------------------------------------------------------- /ember/app/controllers/ranking/clubs.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class ClubsController extends Controller { 4 | queryParams = ['page']; 5 | page = 1; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/controllers/ranking/pilots.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class PilotsController extends Controller { 4 | queryParams = ['page']; 5 | page = 1; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/controllers/users/recover.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class RecoverController extends Controller { 4 | queryParams = ['key']; 5 | key = null; 6 | } 7 | -------------------------------------------------------------------------------- /ember/lib/freestyle/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = { 5 | name: 'freestyle', 6 | 7 | isDevelopingAddon() { 8 | return true; 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /ember/app/components/flight-list-row.module.scss: -------------------------------------------------------------------------------- 1 | .row { 2 | &:hover { 3 | .pin { 4 | visibility: visible; 5 | } 6 | } 7 | } 8 | 9 | .pin { 10 | visibility: hidden; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /ember/app/controllers/ranking.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default Controller.extend({ 4 | queryParams: ['year'], 5 | year: new Date().getFullYear().toString(), 6 | }); 7 | -------------------------------------------------------------------------------- /ember/app/controllers/ranking/airports.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class AirportsController extends Controller { 4 | queryParams = ['page']; 5 | page = 1; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/helpers/sum.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function sum(numbers) { 4 | return numbers.reduce((a, b) => a + b, 0); 5 | } 6 | 7 | export default helper(sum); 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/date.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class DateRoute extends BaseRoute { 4 | getURL({ date }) { 5 | return `/api/flights/date/${date}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class IndexRoute extends Route { 4 | redirect() { 5 | this.replaceWith('flights.latest'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/list.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class ListRoute extends BaseRoute { 4 | getURL({ list }) { 5 | return `/api/flights/list/${list}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/ranking/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class IndexRoute extends Route { 4 | redirect() { 5 | this.replaceWith('ranking.clubs'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/settings/profile.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class ProfileRoute extends Route { 4 | model() { 5 | return this.modelFor('settings'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* eslint-env node */ 4 | 5 | module.exports = { 6 | browsers: ['ie 9', 'last 1 Chrome versions', 'last 1 Firefox versions', 'last 1 Safari versions'], 7 | }; 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/club.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class ClubRoute extends BaseRoute { 4 | getURL({ club_id }) { 5 | return `/api/flights/club/${club_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/unassigned.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class UnassignedRoute extends BaseRoute { 4 | getURL() { 5 | return '/api/flights/unassigned'; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/settings/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class IndexRoute extends Route { 4 | redirect() { 5 | this.replaceWith('settings.profile'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/settings/tracking.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class TrackingRoute extends Route { 4 | model() { 5 | return this.modelFor('settings'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/fullscreen-button.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/notifications-badge.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{this.notificationCounter.counterText}} 3 | -------------------------------------------------------------------------------- /ember/app/components/progress-bar.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class extends Component { 5 | @service progress; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/components/validated-input.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default class ValidatedInput extends Component { 4 | tagName = ''; 5 | 6 | type = 'text'; 7 | value = null; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/helpers/includes.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function includes([array, value]) { 4 | return array.includes(value); 5 | } 6 | 7 | export default helper(includes); 8 | -------------------------------------------------------------------------------- /ember/app/routes/flight/map-redirect.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class MapRedirectRoute extends Route { 4 | redirect() { 5 | this.replaceWith('flight.index'); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/flights/pilot.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class PilotRoute extends BaseRoute { 4 | getURL({ pilot_id }) { 5 | return `/api/flights/pilot/${pilot_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/templates/users/recover.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#if key}} 3 | 4 | {{else}} 5 | 6 | {{/if}} 7 | -------------------------------------------------------------------------------- /ember/app/components/plane-label-overlays.hbs: -------------------------------------------------------------------------------- 1 | {{#each @fixes as |fix|}} 2 | {{#if fix.coordinate}} 3 | 4 | {{/if}} 5 | {{/each}} 6 | -------------------------------------------------------------------------------- /ember/app/helpers/multiply.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function multiply(numbers) { 4 | return numbers.reduce((a, b) => a * b, 1); 5 | } 6 | 7 | export default helper(multiply); 8 | -------------------------------------------------------------------------------- /ember/app/helpers/percent.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function percent([value, max]) { 4 | return Math.round((value * 100) / max); 5 | } 6 | 7 | export default helper(percent); 8 | -------------------------------------------------------------------------------- /ember/app/routes/statistics/club.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class ClubRoute extends BaseRoute { 4 | getURL({ club_id }) { 5 | return `/api/statistics/club/${club_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "skylines", 3 | "dependencies": { 4 | "Flot": "flot#0.8.3", 5 | "flot-marks": "https://github.com/TobiasLohner/flot-marks.git#f09ded70f5a229a38ba0b9cfa92dbb448ca4daaf" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/lib/freestyle/app/templates/components/usage/x-flag.hbs: -------------------------------------------------------------------------------- 1 | {{#freestyle-usage "x-flag"}} 2 | 3 | 4 | 5 | 6 | {{/freestyle-usage}} 7 | -------------------------------------------------------------------------------- /ember/app/components/cesium-plane-models.hbs: -------------------------------------------------------------------------------- 1 | {{#each @fixes as |fix|}} 2 | {{#if fix.coordinate}} 3 | 4 | {{/if}} 5 | {{/each}} -------------------------------------------------------------------------------- /ember/app/routes/flights/airport.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class AirportRoute extends BaseRoute { 4 | getURL({ airport_id }) { 5 | return `/api/flights/airport/${airport_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/statistics/pilot.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class PilotRoute extends BaseRoute { 4 | getURL({ pilot_id }) { 5 | return `/api/statistics/pilot/${pilot_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/flight-phase-table.module.scss: -------------------------------------------------------------------------------- 1 | .table { 2 | composes: table table-condensed table-striped from global; 3 | 4 | font-size: 85%; 5 | 6 | thead tr th { 7 | vertical-align: top; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/components/settings-page.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class SettingsPage extends Component { 5 | @service account; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/controllers/settings/club.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ClubController extends Controller { 5 | @service account; 6 | } 7 | -------------------------------------------------------------------------------- /ember/mirage/factories/club.js: -------------------------------------------------------------------------------- 1 | import { Factory } from 'ember-cli-mirage'; 2 | 3 | export default Factory.extend({ 4 | name: 'AeroClub Aachen', 5 | timeCreated: '2020-05-24T21:41:03Z', 6 | website: 'https://acac.aero/', 7 | }); 8 | -------------------------------------------------------------------------------- /ember/app/components/flight-phase-table.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class FlightPhaseTable extends Component { 5 | @service units; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/components/follower-panel.module.scss: -------------------------------------------------------------------------------- 1 | .follower-panel a { 2 | color: #333; 3 | } 4 | 5 | .follower-panel, .follower-panel h4 { 6 | overflow: hidden; 7 | white-space: nowrap; 8 | text-overflow: ellipsis; 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/controllers/statistics.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class StatisticsController extends Controller { 5 | @service account; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/routes/ranking.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class RankingRoute extends Route { 4 | queryParams = { 5 | year: { refreshModel: true }, 6 | }; 7 | 8 | model() {} 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/routes/statistics/airport.js: -------------------------------------------------------------------------------- 1 | import BaseRoute from './-base'; 2 | 3 | export default class AirportRoute extends BaseRoute { 4 | getURL({ airport_id }) { 5 | return `/api/statistics/airport/${airport_id}`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/templates/settings/club.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ember/app/utils/add-days.js: -------------------------------------------------------------------------------- 1 | import isoDate from './iso-date'; 2 | 3 | export default function addDays(date, days = 0) { 4 | let result = new Date(date); 5 | result.setDate(result.getDate() + days); 6 | return isoDate(result); 7 | } 8 | -------------------------------------------------------------------------------- /ember/translations/ru.yaml: -------------------------------------------------------------------------------- 1 | avg-speed-abbr: Ср. скорость 2 | avg-vario-abbr: Ср. подъем 3 | duration: Длительность 4 | glide-rate: Аэродинамическое качество (относительно земли) 5 | glide-rate-abbr: AK 6 | speed: Скорость 7 | start: Старт 8 | -------------------------------------------------------------------------------- /airspace/airspace_blacklist.txt: -------------------------------------------------------------------------------- 1 | at FIR LOVV 2 | cz CZ Border (126.1 W, 136.175 E Praha Information) 3 | nl FIR EHAA 4 | no Old Comp Area with Swedish border 5 | pl FIS Gdansk 6 | pl FIS Krakow 7 | pl FIS Olsztyn 8 | pl FIS Poznan 9 | pl FIS Warszawa -------------------------------------------------------------------------------- /ember/app/modifiers/set-map-target.js: -------------------------------------------------------------------------------- 1 | import { modifier } from 'ember-modifier'; 2 | 3 | export default modifier((element, [map]) => { 4 | if (map) { 5 | map.setTarget(element); 6 | return () => map.setTarget(null); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /wsgi_mapproxy.py: -------------------------------------------------------------------------------- 1 | import os 2 | from mapproxy.wsgiapp import make_wsgi_app 3 | 4 | config_file = os.path.join( 5 | os.path.dirname(os.path.abspath(__file__)), "mapproxy", "mapproxy.yaml" 6 | ) 7 | application = make_wsgi_app(config_file) 8 | -------------------------------------------------------------------------------- /ember/app/components/flight-details-table.module.scss: -------------------------------------------------------------------------------- 1 | .weglide-button { 2 | composes: btn btn-default from global; 3 | 4 | .logo { 5 | height: 1em; 6 | transform: translate(0, -2px); 7 | margin-right: 3px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/components/follower-list.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#each @followers as |follower|}} 3 |
  • 4 | 5 |
  • 6 | {{/each}} 7 |
8 | -------------------------------------------------------------------------------- /ember/app/controllers/flight/index.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class IndexController extends Controller { 4 | queryParams = ['baselayer', 'overlays']; 5 | baselayer = null; 6 | overlays = null; 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/routes/tracking/map-redirect.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class MapRedirectRoute extends Route { 4 | redirect({ user_ids }) { 5 | this.transitionTo('tracking.details', user_ids); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/flight-performance-panel.hbs: -------------------------------------------------------------------------------- 1 |

{{t "circling-performance"}}

2 | 3 | 4 |

{{t "cruise-performance"}}

5 | -------------------------------------------------------------------------------- /ember/mirage/scenarios/default.js: -------------------------------------------------------------------------------- 1 | export default function (/* server */) { 2 | /* 3 | Seed your development database using your factories. 4 | This data will not be loaded in your tests. 5 | */ 6 | // server.createList('post', 10); 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/plane-label-overlay.hbs: -------------------------------------------------------------------------------- 1 | 7 | {{or @flight.competition_id @flight.registration}} 8 | -------------------------------------------------------------------------------- /ember/app/components/season-dropdown.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | const YEAR = new Date().getFullYear(); 4 | 5 | export default class SeasonDropdown extends Component { 6 | recentYears = [0, 1, 2, 3, 4].map(i => YEAR - i); 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/helpers/truncate.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function truncate([text, length]) { 4 | return text.length <= length ? text : `${text.slice(0, length - 3)}...`; 5 | } 6 | 7 | export default helper(truncate); 8 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-search-form.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class NavBarSearchForm extends Component { 5 | @service('searchText') searchTextService; 6 | } 7 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/all.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class AllController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/club.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class ClubController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/date.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class DateController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'score'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/list.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class ListController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/helpers/html-safe.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { htmlSafe as _htmlSafe } from '@ember/string'; 3 | 4 | export function htmlSafe([text]) { 5 | return _htmlSafe(text); 6 | } 7 | 8 | export default helper(htmlSafe); 9 | -------------------------------------------------------------------------------- /ember/app/routes/flight.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class FlightRoute extends Route { 4 | model(params) { 5 | let ids = params.flight_ids.split(',').map(it => parseInt(it, 10)); 6 | return { ids }; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /skylines/frontend/templates/error.jinja: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ code }} {{ name }} 5 | 6 | 7 |

{{ name }}

8 | {% if description %}

{{ description }}

{% endif %} 9 | 10 | 11 | -------------------------------------------------------------------------------- /config/testing.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from tempfile import mkdtemp 4 | 5 | TESTING = True 6 | 7 | SQLALCHEMY_DATABASE_URI = "postgresql:///skylines_test" 8 | SQLALCHEMY_ECHO = False 9 | SKYLINES_FILES_PATH = mkdtemp(suffix="skylines-uploads") 10 | -------------------------------------------------------------------------------- /ember/app/components/upload-result-form.module.scss: -------------------------------------------------------------------------------- 1 | .upload-result-form { 2 | :global(.ember-flatpickr-input.form-control[readonly]) { 3 | background: white; 4 | } 5 | } 6 | 7 | .barogram { 8 | height: 160px; 9 | margin: 10px 0 15px 15px; 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/computed/is-none.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import { isNone as _isNone } from '@ember/utils'; 3 | 4 | export default function isNone(key) { 5 | return computed(key, function () { 6 | return _isNone(this.get(key)); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/airport.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class AirportController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/latest.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class LatestController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'score'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/pilot.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class PilotController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/pinned.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class PinnedController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /skylines/commands/notifications/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_script import Manager 2 | 3 | from .mark_all_unread import MarkAllUnread 4 | 5 | manager = Manager(help="Perform operations related to notifications") 6 | manager.add_command("mark-all-unread", MarkAllUnread()) 7 | -------------------------------------------------------------------------------- /ember/app/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import '_power-select'; 2 | 3 | @import 'bootstrap'; 4 | @import 'flatpickr'; 5 | @import 'font-awesome'; 6 | 7 | @import 'tables'; 8 | @import 'map'; 9 | @import 'spinner'; 10 | 11 | // ember-css-modules 12 | @import 'css-modules'; 13 | -------------------------------------------------------------------------------- /ember/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title "SkyLines"}} 2 | 3 | 4 | 5 | 6 | {{#if onFreestyleRoute}} 7 | {{outlet}} 8 | {{else}} 9 | 10 | {{outlet}} 11 | {{/if}} -------------------------------------------------------------------------------- /ember/app/templates/settings/tracking.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ember/app/components/base-barogram.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{did-update this.setCrosshair this.crosshair}} 3 | {{did-update this.setGridMarkings this.gridMarkings}} 4 | {{did-update this.setXAxis this.xaxis}} -------------------------------------------------------------------------------- /ember/app/components/notifications-badge.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class NotificationsBadge extends Component { 5 | @service notificationCounter; 6 | @service intl; 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/components/sidebar/tabs.module.scss: -------------------------------------------------------------------------------- 1 | .tabs { 2 | position: absolute; 3 | top: 0; 4 | bottom: 0; 5 | width: 40px; 6 | height: 100%; 7 | margin: 0; 8 | padding: 0; 9 | background-color: rgba(255, 255, 255, 0.5); 10 | overflow: hidden; 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/controllers/flights/unassigned.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | 3 | export default class UnassignedController extends Controller { 4 | queryParams = ['page', 'column', 'order']; 5 | page = 1; 6 | column = 'date'; 7 | order = 'desc'; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/templates/error.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
7 |
8 |

{{t "error-text"}}

9 |
10 |
11 |
-------------------------------------------------------------------------------- /ember/app/templates/flight/change-pilot.hbs: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /ember/mirage/factories/mirage-session.js: -------------------------------------------------------------------------------- 1 | import { Factory } from 'ember-cli-mirage'; 2 | 3 | export default Factory.extend({ 4 | afterCreate(session) { 5 | if (!session.user) { 6 | throw new Error('Missing `user` relationship'); 7 | } 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /ember/app/components/cesium-button.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/pin-button.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{#if this.pinned}} 3 | {{svg-jar "star" class="fa-svg large"}} {{t "flight-pinned"}} 4 | {{else}} 5 | {{svg-jar "star-o" class="fa-svg large"}} {{t "flight-unpinned"}} 6 | {{/if}} 7 | -------------------------------------------------------------------------------- /ember/app/templates/statistics/pilot.hbs: -------------------------------------------------------------------------------- 1 |

{{t "flights"}}

2 | 3 | 4 |

{{t "distance"}}

5 | 6 | 7 |

{{t "flight-time"}}

8 | -------------------------------------------------------------------------------- /ember/app/components/takeoffs-map.hbs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 |
-------------------------------------------------------------------------------- /ember/app/components/flight-leg-panel.hbs: -------------------------------------------------------------------------------- 1 |

{{t "distance"}}

2 | 3 | 4 |

{{t "triangle"}}

5 | -------------------------------------------------------------------------------- /ember/app/templates/about/team.hbs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 | {{markdown text}} 11 |
12 |
13 |
-------------------------------------------------------------------------------- /ember/tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 2017, 4 | }, 5 | extends: ['simplabs/configs/ember-qunit', 'prettier'], 6 | env: { 7 | embertest: null, 8 | }, 9 | rules: { 10 | 'prettier/prettier': 'error', 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /skylines/commands/users/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_script import Manager 2 | 3 | from .merge import Merge 4 | from .email import Email 5 | 6 | manager = Manager(help="Perform operations related to user accounts") 7 | manager.add_command("email", Email()) 8 | manager.add_command("merge", Merge()) 9 | -------------------------------------------------------------------------------- /ember/app/components/playback-button.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/controllers/users/new.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class NewController extends Controller { 5 | @action 6 | transitionTo(...args) { 7 | this.transitionToRoute(...args); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/routes/about/team.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class TeamRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/team'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/routes/club/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class IndexRoute extends Route { 4 | setupController(controller) { 5 | super.setupController(...arguments); 6 | controller.set('club', this.controllerFor('club').get('model')); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/routes/clubs.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ClubsRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/clubs/'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/templates/about/imprint.hbs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 | {{markdown model.content}} 11 |
12 |
13 |
-------------------------------------------------------------------------------- /ember/app/templates/about/license.hbs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 |
{{model.content}}
11 |
12 |
13 |
-------------------------------------------------------------------------------- /ember/app/routes/settings/club.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ClubRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/clubs'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/routes/tracking/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class IndexRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/live'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/routes/users/index.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class IndexRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/users'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/components/sidebar.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{yield (hash 3 | Tabs=(component "sidebar/tabs" selectedTab=this.selectedTab onSelect=this.onSelect) 4 | Panel=(component "sidebar/panel" visibleTab=this.visibleTab) 5 | )}} 6 |
-------------------------------------------------------------------------------- /ember/app/components/sidebar/panel.hbs: -------------------------------------------------------------------------------- 1 | {{#if (eq @visibleTab @id)}} 2 |
3 |
4 |

{{@title}}

5 |
6 | {{yield}} 7 |
8 |
9 |
10 | {{/if}} -------------------------------------------------------------------------------- /ember/app/components/sidebar/tab.hbs: -------------------------------------------------------------------------------- 1 |
  • 2 | 5 |
  • -------------------------------------------------------------------------------- /ember/app/components/timeline-event.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import computedComponent from '../computed/computed-component'; 4 | 5 | export default class TimelineEvent extends Component { 6 | @computedComponent('args.event.type', 'timeline-events/') eventComponent; 7 | } 8 | -------------------------------------------------------------------------------- /ember/app/controllers/flight-upload.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class FlightUploadController extends Controller { 5 | @action 6 | transitionTo(...args) { 7 | this.transitionToRoute(...args); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/routes/about/imprint.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ImprintRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/imprint'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/routes/about/license.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class LicenseRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | return this.ajax.request('/api/license'); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docker/docker-entrypoint-initdb.d/create-databases.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE skylines; 2 | \connect skylines; 3 | CREATE EXTENSION postgis; 4 | CREATE EXTENSION fuzzystrmatch; 5 | 6 | CREATE DATABASE skylines_test; 7 | \connect skylines_test; 8 | CREATE EXTENSION postgis; 9 | CREATE EXTENSION fuzzystrmatch; 10 | -------------------------------------------------------------------------------- /ember/app/components/follower-page.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /ember/app/components/following-page.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 | 9 |
    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /ember/app/routes/club.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class ClubRoute extends Route { 5 | @service ajax; 6 | 7 | model({ club_id }) { 8 | return this.ajax.request(`/api/clubs/${club_id}`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/mirage/utils/session.js: -------------------------------------------------------------------------------- 1 | export function getSession(schema) { 2 | let session = schema.mirageSessions.first(); 3 | if (!session || Date.parse(session.expires) < Date.now()) { 4 | return {}; 5 | } 6 | 7 | let user = schema.users.find(session.userId); 8 | return { session, user }; 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/components/pin-star.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{svg-jar (if this.pinned "star" "star-o") class="fa-svg"}} 3 | Activate this to show the flight on top of other flights on the map 4 | -------------------------------------------------------------------------------- /ember/app/components/stats-flights-table.js: -------------------------------------------------------------------------------- 1 | import { mapBy, max, sum } from '@ember/object/computed'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class StatsFlightsTable extends Component { 5 | @mapBy('args.years', 'flights') flights; 6 | @max('flights') max; 7 | @sum('flights') sum; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/routes/user.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class UserRoute extends Route { 5 | @service ajax; 6 | 7 | model({ user_id }) { 8 | return this.ajax.request(`/api/users/${user_id}?extended`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /skylines/lib/md5.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import hashlib 4 | 5 | 6 | def file_md5(f): 7 | """Return the hex MD5 digest of a file-like object.""" 8 | 9 | md5 = hashlib.md5() 10 | for chunk in iter(lambda: f.read(8192), b""): 11 | md5.update(chunk) 12 | return md5.hexdigest() 13 | -------------------------------------------------------------------------------- /ember/app/components/login-form.module.scss: -------------------------------------------------------------------------------- 1 | .form-login { 2 | min-width: 300px; 3 | padding: 0 20px; 4 | margin-bottom: inherit; 5 | 6 | 7 | > div, > button, > a { 8 | margin: 15px 0; 9 | } 10 | 11 | > a { 12 | display: block; 13 | } 14 | 15 | hr { 16 | margin: 8px 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ember/public/svg/icons/play.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/stats-distance-table.js: -------------------------------------------------------------------------------- 1 | import { mapBy, max, sum } from '@ember/object/computed'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class StatsDistanceTable extends Component { 5 | @mapBy('args.years', 'distance') distances; 6 | @max('distances') max; 7 | @sum('distances') sum; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/components/stats-duration-table.js: -------------------------------------------------------------------------------- 1 | import { mapBy, max, sum } from '@ember/object/computed'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class StatsDurationTable extends Component { 5 | @mapBy('args.years', 'duration') durations; 6 | @max('durations') max; 7 | @sum('durations') sum; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/routes/tracking/details.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class DetailsRoute extends Route { 5 | @service ajax; 6 | 7 | model({ user_ids }) { 8 | return this.ajax.request(`/api/live/${user_ids}`); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ember/lib/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true, 4 | browser: false, 5 | }, 6 | rules: { 7 | // unfortunate workaround for: 8 | // ENOENT: no such file or directory, scandir 'skylines/ember/lib/freestyle/app/app/controllers' 9 | 'prettier/prettier': 'off', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /ember/public/svg/icons/minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /systemd/celery.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Celery background queue 3 | After=network.target 4 | 5 | [Service] 6 | SyslogIdentifier=celery 7 | WorkingDirectory=/home/skylines/src 8 | ExecStart=/usr/local/bin/pipenv run python manage.py celery runworker 9 | Restart=always 10 | 11 | [Install] 12 | WantedBy=default.target 13 | -------------------------------------------------------------------------------- /ember/app/components/form-page.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 |
    9 | {{yield}} 10 |
    11 |
    12 |
    13 |
    14 | -------------------------------------------------------------------------------- /ember/app/components/stats-pilots-table.js: -------------------------------------------------------------------------------- 1 | import { mapBy, max, readOnly } from '@ember/object/computed'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class StatsPilotsTable extends Component { 5 | @mapBy('args.years', 'pilots') pilots; 6 | @max('pilots') max; 7 | @readOnly('args.sumPilots') sum; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/controllers/flight/change-pilot.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class ChangePilotController extends Controller { 5 | @action 6 | transitionToFlight() { 7 | this.transitionToRoute('flight', this.get('model.id')); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/utils/raf.js: -------------------------------------------------------------------------------- 1 | export function nextAnimationFrame() { 2 | let animationFrameId; 3 | let promise = new Promise(resolve => { 4 | animationFrameId = requestAnimationFrame(resolve); 5 | }); 6 | promise.__ec_cancel__ = () => { 7 | cancelAnimationFrame(animationFrameId); 8 | }; 9 | return promise; 10 | } 11 | -------------------------------------------------------------------------------- /skylines/__about__.py: -------------------------------------------------------------------------------- 1 | __title__ = "SkyLines" 2 | __summary__ = "Live tracking, flight database and competition framework" 3 | __uri__ = "https://github.com/skylines-project/skylines/" 4 | 5 | __version__ = "0.1.0" 6 | 7 | __author__ = "Tobias Bieniek" 8 | __email__ = "tobias.bieniek@gmx.de" 9 | 10 | __license__ = "AGPLv3" 11 | -------------------------------------------------------------------------------- /ember/app/controllers/flight/change-aircraft.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class ChangeAircraftController extends Controller { 5 | @action 6 | transitionToFlight() { 7 | this.transitionToRoute('flight', this.get('model.id')); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/helpers/format-lift.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseHelper from 'ember-intl/helpers/-format-base'; 4 | 5 | export default class extends BaseHelper { 6 | @service units; 7 | 8 | format(value, options) { 9 | return this.units.formatLift(value, options); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/helpers/format-speed.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseHelper from 'ember-intl/helpers/-format-base'; 4 | 5 | export default class extends BaseHelper { 6 | @service units; 7 | 8 | format(value, options) { 9 | return this.units.formatSpeed(value, options); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/public/svg/icons/caret-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/.eslintignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /bower_components/ 3 | /blueprints/**/files/ 4 | /common-tmp/ 5 | /coverage/ 6 | /public/cesium/ 7 | /tests/fixtures/ 8 | /tmp/ 9 | /vendor/ 10 | 11 | /lib/broccoli/app-*.js 12 | /lib/broccoli/test-support-*.js 13 | /lib/broccoli/tests-*.js 14 | /lib/broccoli/vendor-*.js 15 | 16 | !.* 17 | /node_modules/ 18 | -------------------------------------------------------------------------------- /ember/app/components/aircraft-model-select.hbs: -------------------------------------------------------------------------------- 1 | 7 | {{#if (eq model.id null)}} 8 | [{{t "unspecified"}}] 9 | {{else}} 10 | {{model.name}} 11 | {{/if}} 12 | 13 | -------------------------------------------------------------------------------- /ember/app/components/contest-layer.hbs: -------------------------------------------------------------------------------- 1 | {{did-insert this.setVisible @visible}} 2 | {{did-update this.setVisible @visible}} 3 | {{#each @flights as |flight|}} 4 | {{#each flight.contests as |contest|}} 5 | {{#if contest}} 6 | 7 | {{/if}} 8 | {{/each}} 9 | {{/each}} -------------------------------------------------------------------------------- /ember/app/components/tracking-table.module.scss: -------------------------------------------------------------------------------- 1 | /* make sure the columns have the same width in all tracking tables */ 2 | 3 | .tracking-table { 4 | composes: table table-striped from global; 5 | 6 | th { 7 | width: 22.222%; 8 | } 9 | 10 | th:nth-child(4), 11 | th:nth-child(5) { 12 | width: 11.111%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ember/app/helpers/format-altitude.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseHelper from 'ember-intl/helpers/-format-base'; 4 | 5 | export default class extends BaseHelper { 6 | @service units; 7 | 8 | format(value, options) { 9 | return this.units.formatAltitude(value, options); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/helpers/format-distance.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseHelper from 'ember-intl/helpers/-format-base'; 4 | 5 | export default class extends BaseHelper { 6 | @service units; 7 | 8 | format(value, options) { 9 | return this.units.formatDistance(value, options); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /systemd/tracking.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SkyLines Tracking server 3 | After=network.target 4 | 5 | [Service] 6 | SyslogIdentifier=tracking 7 | WorkingDirectory=/home/skylines/src 8 | ExecStart=/usr/local/bin/pipenv run python manage.py tracking runserver 9 | Restart=on-failure 10 | 11 | [Install] 12 | WantedBy=default.target 13 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-search-form.module.scss: -------------------------------------------------------------------------------- 1 | .form { 2 | margin: 5px 0; 3 | } 4 | 5 | .input { 6 | composes: form-control from global; 7 | height: 30px; 8 | color: #bbb; 9 | background-color: #626262; 10 | border: 1px solid #333; 11 | 12 | &:focus { 13 | color: #666; 14 | background-color: white; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/app/components/tracking-table-row.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import safeComputed from '../computed/safe-computed'; 4 | 5 | export default class TrackingTableRow extends Component { 6 | @safeComputed('track.altitude', 'track.elevation', (altitude, elevation) => Math.max(altitude - elevation, 0)) 7 | altitudeAGL; 8 | } 9 | -------------------------------------------------------------------------------- /ember/app/helpers/format-hours.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | import pad from '../utils/pad'; 4 | 5 | export function formatHours([value]) { 6 | let h = Math.floor(value / 3600); 7 | let m = Math.floor((value % 3600) / 60); 8 | 9 | return `${h}:${pad(m)}`; 10 | } 11 | 12 | export default helper(formatHours); 13 | -------------------------------------------------------------------------------- /ember/app/helpers/initials.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function initials([name]) { 4 | let parts = name.split(/\s/); 5 | let initials = parts.filter(it => it.length > 2 && it.indexOf('.') === -1).map(it => it[0].toUpperCase()); 6 | return initials.join(''); 7 | } 8 | 9 | export default helper(initials); 10 | -------------------------------------------------------------------------------- /skylines/frontend/views/files.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint, current_app, send_from_directory 2 | 3 | files_blueprint = Blueprint("files", "skylines") 4 | 5 | 6 | @files_blueprint.route("/files/") 7 | def index(filename): 8 | path = current_app.config.get("SKYLINES_FILES_PATH") 9 | return send_from_directory(path, filename) 10 | -------------------------------------------------------------------------------- /ember/app/components/cesium-button.js: -------------------------------------------------------------------------------- 1 | import { action } from '@ember/object'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class CesiumButton extends Component { 5 | @action toggle() { 6 | if (this.args.enabled) { 7 | this.args.onDisable(); 8 | } else { 9 | this.args.onEnable(); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-link.hbs: -------------------------------------------------------------------------------- 1 | <@nav.item ...attributes> 2 | {{#@nav.link-to @target}} 3 | {{svg-jar @icon class="fa-svg large"}} 4 | {{svg-jar @icon class="fa-svg fixed-width visible-xs-inline-block"}} 5 | 6 | {{/@nav.link-to}} 7 | -------------------------------------------------------------------------------- /ember/app/components/pilot-select.hbs: -------------------------------------------------------------------------------- 1 | 8 | {{#if (eq pilot.id null)}} 9 | [{{t "unknown-or-other-person"}}] 10 | {{else}} 11 | {{pilot.name}} 12 | {{/if}} 13 | 14 | -------------------------------------------------------------------------------- /ember/app/helpers/markdown.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | import { htmlSafe } from '@ember/string'; 3 | 4 | import { Remarkable } from 'remarkable'; 5 | 6 | let remarkable = new Remarkable(); 7 | 8 | export function markdown([text]) { 9 | return htmlSafe(remarkable.render(text)); 10 | } 11 | 12 | export default helper(markdown); 13 | -------------------------------------------------------------------------------- /ember/app/templates/flights.hbs: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 |
    13 |
    14 | {{outlet}} 15 |
    16 |
    17 |
    -------------------------------------------------------------------------------- /ember/app/templates/page-not-found.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 |

    {{t "page-not-found.text"}}

    9 |

    URL: /{{model.wildcard}}

    10 |
    11 |
    12 |
    13 | -------------------------------------------------------------------------------- /ember/tests/test-helpers/match-json.js: -------------------------------------------------------------------------------- 1 | /* globals QUnit */ 2 | 3 | import match from 'match-json'; 4 | 5 | export default function registerMatchJsonAssertion() { 6 | QUnit.assert.matchJson = function (value, expected, message) { 7 | let result = match(value, expected); 8 | this.pushResult({ result, actual: value, expected, message }); 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/new-user.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | 3 | import Base from './-base'; 4 | 5 | export default class NewUser extends Base { 6 | @computed('accountUserIsActor') 7 | get translationKey() { 8 | let i = this.accountUserIsActor ? 2 : 1; 9 | return `timeline-events.new-user.message${i}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/club-join.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | 3 | import Base from './-base'; 4 | 5 | export default class ClubJoin extends Base { 6 | @computed('accountUserIsActor') 7 | get translationKey() { 8 | let i = this.accountUserIsActor ? 2 : 1; 9 | return `timeline-events.club-join.message${i}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/components/tracking-tables.hbs: -------------------------------------------------------------------------------- 1 | {{#if friendsTracks}} 2 | {{#if othersTracks}} 3 |

    {{t "friends"}}

    4 | {{/if}} 5 | 6 | {{/if}} 7 | 8 | {{#if othersTracks}} 9 | {{#if friendsTracks}} 10 |

    {{t "other-pilots"}}

    11 | {{/if}} 12 | 13 | {{/if}} 14 | -------------------------------------------------------------------------------- /ember/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | .eslintcache 17 | npm-debug.log* 18 | yarn-error.log 19 | testem.log 20 | -------------------------------------------------------------------------------- /ember/app/components/base-page.module.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | composes: container from global; 3 | padding-top: 15px; 4 | padding-bottom: 15px; 5 | } 6 | 7 | .footer { 8 | /* center align it with the container */ 9 | text-align: center; 10 | margin-top: 5px; 11 | font-size: 85%; 12 | 13 | a { 14 | color: #333; 15 | text-decoration: none; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ember/app/components/layer-switcher-element.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class LayerSwitcherElement extends Component { 5 | highlighted = false; 6 | 7 | @computed('args.visible', 'highlighted') 8 | get dimmed() { 9 | return !this.args.visible && !this.highlighted; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/new-user.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/routes/club/edit.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class EditRoute extends Route { 5 | @service ajax; 6 | 7 | setupController(controller) { 8 | super.setupController(...arguments); 9 | controller.set('club', this.controllerFor('club').get('model')); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/schemas/fields/test_location.py: -------------------------------------------------------------------------------- 1 | from skylines.schemas import fields, Schema 2 | from skylines.model.geo import Location 3 | 4 | 5 | def test_serialization(): 6 | class TestSchema(Schema): 7 | location = fields.Location() 8 | 9 | data = TestSchema().dump(dict(location=Location(longitude=7, latitude=51))).data 10 | assert data["location"] == [7, 51] 11 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/flight-upload.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | 3 | import Base from './-base'; 4 | 5 | export default class FlightUpload extends Base { 6 | @computed('accountUserIsActor') 7 | get translationKey() { 8 | let i = this.accountUserIsActor ? 2 : 1; 9 | return `timeline-events.flight-upload.message${i}`; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/components/validated-block.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{#if label}}{{/if}} 3 | {{yield}} 4 | 5 | {{#if showMessage}} 6 |

    {{validation.message}}

    7 | {{/if}} 8 |
    -------------------------------------------------------------------------------- /ember/public/svg/icons/bar-chart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/long-arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/commands/notifications/mark_all_unread.py: -------------------------------------------------------------------------------- 1 | from flask_script import Command 2 | from skylines.database import db 3 | from skylines.model import Notification 4 | 5 | 6 | class MarkAllUnread(Command): 7 | """ Mark all notifications as unread """ 8 | 9 | def run(self): 10 | Notification.query().update(dict(time_read=None)) 11 | db.session.commit() 12 | -------------------------------------------------------------------------------- /ember/app/components/circling-performance-row.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{t (concat "circling-direction." @perf.circlingDirection)}} 3 | 4 | {{@perf.count}}
    5 | {{format-lift @perf.vario}} 6 | 7 | 8 | {{format-seconds @perf.duration}}
    9 | {{format-altitude @perf.altDiff}} 10 | 11 | 12 | -------------------------------------------------------------------------------- /ember/app/styles/bootstrap/_tables.scss: -------------------------------------------------------------------------------- 1 | .table { 2 | > thead, 3 | > tbody, 4 | > tfoot { 5 | > tr { 6 | > th, 7 | > td { 8 | border-top: none; 9 | } 10 | } 11 | } 12 | > thead > tr > th { 13 | border-bottom: 1px solid $table-border-color; 14 | } 15 | > tbody + tbody { 16 | border-top: 1px solid $table-border-color; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /skylines/lib/basic_auth.py: -------------------------------------------------------------------------------- 1 | from base64 import b64encode 2 | 3 | from skylines.lib.types import is_unicode 4 | 5 | 6 | def encode(name, password): 7 | if is_unicode(name): 8 | name = name.encode("utf-8") 9 | 10 | if is_unicode(password): 11 | password = password.encode("utf-8") 12 | 13 | return u"Basic " + b64encode(name + b":" + password).decode("ascii") 14 | -------------------------------------------------------------------------------- /ember/app/components/validated-input.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /ember/app/templates/statistics/club.hbs: -------------------------------------------------------------------------------- 1 |

    {{t "flights"}}

    2 | 3 | 4 |

    {{t "distance"}}

    5 | 6 | 7 |

    {{t "flight-time"}}

    8 | 9 | 10 |

    {{t "pilots"}}

    11 | -------------------------------------------------------------------------------- /ember/app/utils/locales.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { code: 'de', name: 'Deutsch', countryCode: 'de' }, 3 | { code: 'en', name: 'English', countryCode: 'gb' }, 4 | { code: 'es', name: 'Español', countryCode: 'es' }, 5 | { code: 'fr', name: 'Français', countryCode: 'fr' }, 6 | { code: 'nl', name: 'Nederlands', countryCode: 'nl' }, 7 | { code: 'pl', name: 'Polski', countryCode: 'pl' }, 8 | ]; 9 | -------------------------------------------------------------------------------- /ember/public/svg/icons/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/chevron-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = ${repr(up_revision)} 3 | down_revision = ${repr(down_revision)} 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | ${imports if imports else ""} 8 | 9 | def upgrade(): 10 | ${upgrades if upgrades else "pass"} 11 | 12 | 13 | def downgrade(): 14 | ${downgrades if downgrades else "pass"} 15 | -------------------------------------------------------------------------------- /ember/app/styles/_font-awesome.scss: -------------------------------------------------------------------------------- 1 | .fa-svg { 2 | position: relative; 3 | height: 1em; 4 | width: auto; 5 | top: 0.125em; 6 | 7 | &.large { 8 | height: 1.33333em; 9 | vertical-align: -7.5%; 10 | } 11 | 12 | &.fixed-width { 13 | width: 1.28571429em; 14 | text-align: center; 15 | } 16 | 17 | &.rotate-270 { 18 | transform: rotateZ(270deg); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ember/app/templates/flights/all.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/app/templates/flights/list.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/app/templates/statistics/airport.hbs: -------------------------------------------------------------------------------- 1 |

    {{t "flights"}}

    2 | 3 | 4 |

    {{t "distance"}}

    5 | 6 | 7 |

    {{t "flight-time"}}

    8 | 9 | 10 |

    {{t "pilots"}}

    11 | -------------------------------------------------------------------------------- /ember/app/templates/statistics/index.hbs: -------------------------------------------------------------------------------- 1 |

    {{t "flights"}}

    2 | 3 | 4 |

    {{t "distance"}}

    5 | 6 | 7 |

    {{t "flight-time"}}

    8 | 9 | 10 |

    {{t "pilots"}}

    11 | -------------------------------------------------------------------------------- /ember/mirage/serializers/application.js: -------------------------------------------------------------------------------- 1 | import { Serializer } from 'ember-cli-mirage'; 2 | 3 | export default Serializer.extend({ 4 | embed: true, 5 | root: false, 6 | 7 | serialize() { 8 | let json = Serializer.prototype.serialize.apply(this, arguments); 9 | 10 | if ('id' in json) { 11 | json.id = Number(json.id); 12 | } 13 | 14 | return json; 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /ember/public/svg/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /tests/tracking/test_datetime.py: -------------------------------------------------------------------------------- 1 | from datetime import time 2 | 3 | from skylines.tracking.datetime import ms_to_time 4 | 5 | 6 | def test_ms_to_time(): 7 | assert ms_to_time(0) == time(0, 0, 0) 8 | 9 | assert ms_to_time(12 * 60 * 60 * 1000 + 34 * 60 * 1000 + 56 * 1000 + 789) == time( 10 | 12, 34, 56, 789 11 | ) 12 | 13 | assert ms_to_time(123.789) == time(0, 0, 0, 123) 14 | -------------------------------------------------------------------------------- /ember/app/computed/computed-point.js: -------------------------------------------------------------------------------- 1 | import Point from 'ol/geom/Point'; 2 | 3 | import safeComputed from './safe-computed'; 4 | 5 | export default function computedPoint(key, layout = 'XYZM') { 6 | return safeComputed(key, coordinate => { 7 | if (layout === 'XY') { 8 | coordinate = coordinate.slice(0, 2); 9 | } 10 | 11 | return new Point(coordinate, layout); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /ember/app/templates/flights/pinned.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/app/templates/flights/unassigned.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import { setApplication } from '@ember/test-helpers'; 2 | import { start } from 'ember-qunit'; 3 | 4 | import Application from '../app'; 5 | import config from '../config/environment'; 6 | import registerMatchJsonAssertion from './test-helpers/match-json'; 7 | 8 | registerMatchJsonAssertion(); 9 | 10 | setApplication(Application.create(config.APP)); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /ember/app/components/sidebar/tab.js: -------------------------------------------------------------------------------- 1 | import { action } from '@ember/object'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class SidebarTab extends Component { 5 | @action onClick(event) { 6 | event.preventDefault(); 7 | event.stopPropagation(); 8 | 9 | let { id, selectedTab } = this.args; 10 | this.args.onSelect(selectedTab === id ? null : id); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ember/app/routes/tracking/info.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class InfoRoute extends Route { 5 | @service account; 6 | @service ajax; 7 | 8 | model() { 9 | let userId = this.get('account.user.id'); 10 | if (userId) { 11 | return this.ajax.request('/api/settings'); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /ember/app/utils/parse-query-string.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function parseQueryString(qs) { 4 | if (!qs) { 5 | return {}; 6 | } 7 | 8 | if (qs[0] === '?') { 9 | qs = qs.slice(1); 10 | } 11 | 12 | let RR = Ember.__loader.require('route-recognizer').default; 13 | let parseQueryString = RR.prototype.parseQueryString; 14 | return parseQueryString(qs); 15 | } 16 | -------------------------------------------------------------------------------- /ember/public/svg/icons/step-forward.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/fix-table-row.module.scss: -------------------------------------------------------------------------------- 1 | .fix-table-row td { 2 | width: 18%; 3 | text-align: right; 4 | 5 | &:first-child { 6 | width: 10%; 7 | text-align: center; 8 | vertical-align: middle; 9 | } 10 | } 11 | 12 | .remove-button { 13 | padding: 0; 14 | border: none; 15 | font: inherit; 16 | color: inherit; 17 | background-color: transparent; 18 | cursor: pointer; 19 | } 20 | -------------------------------------------------------------------------------- /skylines/commands/shell.py: -------------------------------------------------------------------------------- 1 | from flask_script import Shell as BaseShell 2 | 3 | from flask import current_app 4 | from skylines import model, database 5 | 6 | 7 | def make_context(): 8 | return dict(app=current_app, model=model, db=database.db) 9 | 10 | 11 | class Shell(BaseShell): 12 | def __init__(self, *args, **kw): 13 | super(Shell, self).__init__(make_context=make_context, *args, **kw) 14 | -------------------------------------------------------------------------------- /skylines/lib/compat.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa F821 2 | 3 | import sys 4 | 5 | 6 | def _xrange(*args, **kwargs): 7 | if sys.version_info[0] == 2: 8 | return xrange(*args, **kwargs) 9 | else: 10 | return range(*args, **kwargs) 11 | 12 | 13 | if sys.version_info[0] == 2: 14 | binary_type = str 15 | text_type = unicode 16 | else: 17 | binary_type = bytes 18 | text_type = str 19 | -------------------------------------------------------------------------------- /ember/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default class ApplicationController extends Controller { 6 | @service router; 7 | 8 | @action search(text) { 9 | this.router.transitionTo('search', { 10 | queryParams: { text }, 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ember/app/templates/flight/change-aircraft.hbs: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /ember/app/templates/flights/club.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/app/templates/flights/date.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/lib/freestyle/app/routes/freestyle.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default Route.extend({ 4 | activate() { 5 | this._super(...arguments); 6 | this.controllerFor('application').set('onFreestyleRoute', true); 7 | }, 8 | 9 | deactivate() { 10 | this._super(...arguments); 11 | this.controllerFor('application').set('onFreestyleRoute', false); 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /skylines/tracking/datetime.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from datetime import time 4 | 5 | 6 | def ms_to_time(ms): 7 | ms = ms % (24 * 3600 * 1000) 8 | 9 | hour = int((ms / (1000 * 60 * 60)) % 24) 10 | minute = int((ms / (1000 * 60)) % 60) 11 | second = int((ms / 1000) % 60) 12 | millisecond = int(ms % 1000) 13 | 14 | return time(hour, minute, second, millisecond) 15 | -------------------------------------------------------------------------------- /tests/api/__init__.py: -------------------------------------------------------------------------------- 1 | from werkzeug.datastructures import Headers 2 | 3 | from skylines.lib.basic_auth import encode as basic_auth_encode 4 | 5 | 6 | def auth_for(user): 7 | return basic_auth(user.email_address, user.original_password) 8 | 9 | 10 | def basic_auth(email, password): 11 | headers = Headers() 12 | headers.add("Authorization", basic_auth_encode(email, password)) 13 | return headers 14 | -------------------------------------------------------------------------------- /ember/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | "outputPath": "../skylines/frontend/static", 10 | "proxy": "https://skylines.aero" 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/controllers/index.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { computed } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default class IndexController extends Controller { 6 | @service account; 7 | 8 | @computed('account.user') 9 | get notificationsTarget() { 10 | return this.account.user ? 'notifications' : 'timeline'; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ember/app/controllers/stats/wildcard.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { readOnly } from '@ember/object/computed'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default class WildcardController extends Controller { 6 | @service account; 7 | 8 | @readOnly('model.name') name; 9 | @readOnly('model.years') years; 10 | @readOnly('model.sumPilots') sumPilots; 11 | } 12 | -------------------------------------------------------------------------------- /ember/app/templates/flights/pilot.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/tests/test-helpers/auth.js: -------------------------------------------------------------------------------- 1 | import { getContext } from '@ember/test-helpers'; 2 | 3 | import { authenticateSession } from 'ember-simple-auth/test-support'; 4 | 5 | export async function authenticateAs(user) { 6 | let { owner, server } = getContext(); 7 | 8 | server.create('mirage-session', { user }); 9 | 10 | await authenticateSession(); 11 | await owner.lookup('service:account').loadSettings(); 12 | } 13 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-user-menu.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | 4 | import { task } from 'ember-concurrency'; 5 | 6 | export default class NavBarUserMenu extends Component { 7 | @service account; 8 | @service session; 9 | 10 | @(task(function* () { 11 | yield this.session.invalidate(); 12 | }).drop()) 13 | logoutTask; 14 | } 15 | -------------------------------------------------------------------------------- /ember/public/svg/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from skylines.app import ( 4 | create_app, 5 | create_http_app, 6 | create_frontend_app, 7 | create_api_app, 8 | create_combined_app, 9 | create_celery_app, 10 | ) 11 | 12 | from skylines.__about__ import ( 13 | __title__, 14 | __summary__, 15 | __uri__, 16 | __version__, 17 | __author__, 18 | __email__, 19 | __license__, 20 | ) 21 | -------------------------------------------------------------------------------- /ember/app/routes/settings.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class SettingsRoute extends Route { 5 | @service ajax; 6 | @service session; 7 | 8 | beforeModel(transition) { 9 | this.session.requireAuthentication(transition, 'login'); 10 | } 11 | 12 | model() { 13 | return this.ajax.request('/api/settings/'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ember/app/templates/flights/airport.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/app/templates/tracking/index_loading.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 |

    {{t "loading"}}

    9 | 10 | {{svg-jar "question-circle" class="fa-svg"}} {{t "how-to-use-live-tracking"}} 11 |
    12 |
    13 |
    -------------------------------------------------------------------------------- /ember/public/svg/icons/map-marker.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/templates/flights/latest.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | 5 |
    6 | 7 | 8 | 9 |
    10 | 11 |
    -------------------------------------------------------------------------------- /ember/public/svg/icons/arrows-h.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/bolt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/fix-table.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#each this.data as |row|}} 4 | 10 | {{/each}} 11 | 12 |
    -------------------------------------------------------------------------------- /skylines/commands/celery.py: -------------------------------------------------------------------------------- 1 | from flask_script import Manager 2 | from skylines import create_celery_app 3 | from skylines.worker.celery import celery 4 | 5 | manager = Manager(help="Perform operations related to the Celery task queue") 6 | 7 | 8 | @manager.command 9 | def runworker(): 10 | """ Runs the Celery background worker process """ 11 | create_celery_app() 12 | celery.worker_main(["skylines.worker", "--loglevel=INFO"]) 13 | -------------------------------------------------------------------------------- /ember/app/components/base-page.hbs: -------------------------------------------------------------------------------- 1 |
    2 | {{yield}} 3 | 4 |

    5 | © {{t "the-skylines-team"}} 6 | - 7 | {{t "imprint"}} 8 | - 9 | {{t "privacy-policy"}} 10 |

    11 |
    -------------------------------------------------------------------------------- /ember/app/components/timeline-events/club-join.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/cesium-button.module.scss: -------------------------------------------------------------------------------- 1 | .cesium-button { 2 | z-index: 1005 !important; 3 | 4 | position: absolute; 5 | top: 126px; 6 | right: 12px; 7 | padding: 2px; 8 | 9 | background: white; 10 | background: rgba(255,255,255,0.7); 11 | border: 1px solid #bbb; 12 | border-radius: 4px; 13 | box-shadow: 0 0 8px rgba(50,50,50,0.3); 14 | 15 | cursor: pointer; 16 | 17 | &:hover { 18 | background: white; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ember/app/components/flight-list-row.js: -------------------------------------------------------------------------------- 1 | import { or, equal, not } from '@ember/object/computed'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class FlightListRow extends Component { 5 | @or('args.flight.pilot.name', 'args.flight.pilotName') pilotName; 6 | @or('args.flight.copilot.name', 'args.flight.copilotName') copilotName; 7 | 8 | @equal('args.flight.privacyLevel', 0) isPublic; 9 | @not('isPublic') isPrivate; 10 | } 11 | -------------------------------------------------------------------------------- /ember/app/components/playback-button.module.scss: -------------------------------------------------------------------------------- 1 | .play-button { 2 | z-index: 1005 !important; 3 | 4 | position: absolute; 5 | top: 50px; 6 | right: 12px; 7 | padding: 2px; 8 | 9 | background: white; 10 | background: rgba(255,255,255,0.7); 11 | border: 1px solid #bbb; 12 | border-radius: 4px; 13 | box-shadow: 0 0 8px rgba(50,50,50,0.3); 14 | 15 | cursor: pointer; 16 | 17 | &:hover { 18 | background: white; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ember/public/svg/icons/arrow-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/cloud.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/arrow-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /systemd/mapproxy.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=MapProxy uWSGI Server 3 | After=network.target 4 | 5 | [Service] 6 | SyslogIdentifier=mapproxy 7 | WorkingDirectory=/home/skylines/src 8 | ExecStart=/usr/local/bin/pipenv run gunicorn -b 127.0.0.1:9109 -w 10 --no-sendfile wsgi_mapproxy 9 | ExecReload=/bin/kill -s HUP $MAINPID 10 | ExecStop=/bin/kill -s TERM $MAINPID 11 | PrivateTmp=true 12 | Restart=on-failure 13 | 14 | [Install] 15 | WantedBy=default.target 16 | -------------------------------------------------------------------------------- /systemd/skylines.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=SkyLines uWSGI server 3 | After=network.target 4 | 5 | [Service] 6 | SyslogIdentifier=skylines 7 | WorkingDirectory=/home/skylines/src 8 | ExecStart=/usr/local/bin/pipenv run gunicorn -b 127.0.0.1:9115 -w 10 --no-sendfile wsgi_skylines 9 | ExecReload=/bin/kill -s HUP $MAINPID 10 | ExecStop=/bin/kill -s TERM $MAINPID 11 | PrivateTmp=true 12 | Restart=on-failure 13 | 14 | [Install] 15 | WantedBy=default.target 16 | -------------------------------------------------------------------------------- /tests/lib/test_basic_auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from skylines.lib.basic_auth import encode 4 | from skylines.lib.types import is_unicode 5 | 6 | 7 | def test_unicode(): 8 | result = encode(u"foo", u"bar") 9 | assert result == u"Basic Zm9vOmJhcg==" 10 | assert is_unicode(result) 11 | 12 | 13 | def test_bytes(): 14 | result = encode("foo", "bar") 15 | assert result == u"Basic Zm9vOmJhcg==" 16 | assert is_unicode(result) 17 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: skylines 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: https://paypal.me/tobiasbieniek # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /ember/app/components/flight-list-nav.module.scss: -------------------------------------------------------------------------------- 1 | .datepicker { 2 | display: none; 3 | } 4 | 5 | .datepicker-button { 6 | padding: 0; 7 | border: none; 8 | outline: none; 9 | font: inherit; 10 | color: inherit; 11 | background-color: transparent; 12 | cursor: pointer; 13 | } 14 | 15 | .flight-list-nav :global(.flatpickr-calendar.static) { 16 | // this causes the calendar arrow to align with the button 17 | left: -12px; 18 | top: 12px; 19 | } 20 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/follower.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/public/svg/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /migrations/versions/3e82b37a4989_non_null_tracking_ke.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "3e82b37a4989" 3 | down_revision = "5efafe47090" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | op.alter_column("users", "tracking_key", existing_type=sa.BIGINT(), nullable=False) 11 | 12 | 13 | def downgrade(): 14 | op.alter_column("users", "tracking_key", existing_type=sa.BIGINT(), nullable=True) 15 | -------------------------------------------------------------------------------- /ember/app/styles/ranking.module.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | composes: page-header from global; 3 | padding-bottom: 50px; 4 | 5 | @media (min-width: 768px) { 6 | padding-bottom: 0; 7 | } 8 | } 9 | 10 | .tabs { 11 | position: absolute; 12 | bottom: 0; 13 | left: 5px; 14 | 15 | @media (min-width: 768px) { 16 | left: auto; 17 | right: 5px; 18 | } 19 | 20 | :global(.nav-tabs) { 21 | margin-bottom: 0; 22 | border-bottom: 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ember/app/templates/settings/profile.hbs: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ember/app/templates/tracking/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
    7 |
    8 | 9 | 10 | {{svg-jar "question-circle" class="fa-svg"}} {{t "how-to-use-live-tracking"}} 11 |
    12 |
    13 |
    -------------------------------------------------------------------------------- /ember/app/components/progress-bar.module.scss: -------------------------------------------------------------------------------- 1 | .progress-bar { 2 | position: fixed; 3 | left: 0; 4 | top: 0; 5 | height: 2px; 6 | background: lighten($skylines-blue, 0%); 7 | border-bottom: solid 1px lighten($skylines-blue, 10%); 8 | box-shadow: 0 0 5px 0 lighten($skylines-blue, 20%); 9 | z-index: 3000; 10 | 11 | :global(.ember-application) & { 12 | top: 40px; 13 | 14 | @media (max-width: 767px) { 15 | top: 50px; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ember/app/components/wingman-table.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{#each this.nearFlightsWithColors as |nearFlight|}} 11 | 12 | {{/each}} 13 | 14 |
    {{t "aircraft"}}{{t "pilot"}}{{t "first-last"}}
    -------------------------------------------------------------------------------- /ember/public/svg/icons/circle-o.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /migrations/versions/5efafe47090_removed_eye_candy.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "5efafe47090" 3 | down_revision = "4b3af9d93c97" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | op.drop_column("users", u"eye_candy") 11 | 12 | 13 | def downgrade(): 14 | op.add_column( 15 | "users", 16 | sa.Column(u"eye_candy", sa.BOOLEAN(), server_default="false", nullable=False), 17 | ) 18 | -------------------------------------------------------------------------------- /skylines/lib/util.py: -------------------------------------------------------------------------------- 1 | def str_to_bool(str): 2 | return str.lower() in ["1", "true", "t", "yes", "y"] 3 | 4 | 5 | def pressure_alt_to_qnh_alt(altitude, qnh): 6 | if qnh is None: 7 | qnh = 1013.25 8 | 9 | k1 = 0.190263 10 | inv_k1 = 1.0 / 0.190263 11 | k2 = 8.417286e-5 12 | inv_k2 = 1.0 / 8.417286e-5 13 | 14 | static_pressure = ((1013.25 ** k1) - k2 * altitude) ** inv_k1 15 | 16 | return (qnh ** k1 - static_pressure ** k1) * inv_k2 17 | -------------------------------------------------------------------------------- /ember/app/components/layer-switcher.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | import Component from '@glimmer/component'; 3 | import { tracked } from '@glimmer/tracking'; 4 | 5 | import { BASE_LAYERS, OVERLAY_LAYERS } from '../services/map-settings'; 6 | 7 | export default class LayerSwitcher extends Component { 8 | @service mapSettings; 9 | 10 | @tracked open = false; 11 | 12 | BASE_LAYERS = BASE_LAYERS; 13 | OVERLAY_LAYERS = OVERLAY_LAYERS; 14 | } 15 | -------------------------------------------------------------------------------- /ember/app/components/search-result-table.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#each @results as |result|}} 4 | 5 | {{else}} 6 | 7 | 14 | 15 | {{/each}} 16 | 17 |
    8 | {{#if @searchText}} 9 | {{t "search.no-results"}} 10 | {{else}} 11 | {{t "search.no-search-text"}} 12 | {{/if}} 13 |
    -------------------------------------------------------------------------------- /ember/app/components/timeline-events/flight-upload.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/app/components/tracking-table.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {{#each @tracks as |track|}} 14 | 15 | {{/each}} 16 | 17 |
    {{t "pilot"}}{{t "nearest-airport"}}
    -------------------------------------------------------------------------------- /ember/app/helpers/format-seconds.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | import pad from '../utils/pad'; 4 | 5 | export function formatSeconds([value]) { 6 | value %= 86400; 7 | let h = Math.floor(value / 3600); 8 | let m = Math.floor((value % 3600) / 60); 9 | let s = Math.floor((value % 3600) % 60); 10 | 11 | // Format the result into time strings 12 | return `${h}:${pad(m)}:${pad(s)}`; 13 | } 14 | 15 | export default helper(formatSeconds); 16 | -------------------------------------------------------------------------------- /skylines/commands/import_/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_script import Manager 2 | 3 | from .airspace import AirspaceCommand 4 | from .dmst_index import DMStIndex 5 | from .mwp import MWP 6 | from .welt2000 import Welt2000 7 | 8 | manager = Manager(help="Import external data into the database") 9 | manager.add_command("airspace", AirspaceCommand()) 10 | manager.add_command("dmst-index", DMStIndex()) 11 | manager.add_command("mwp", MWP()) 12 | manager.add_command("welt2000", Welt2000()) 13 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/-base.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | import safeComputed from '../../computed/safe-computed'; 5 | 6 | export default class Base extends Component { 7 | tagName = ''; 8 | 9 | @service account; 10 | 11 | event = null; 12 | 13 | @safeComputed('account.user', 'event.actor', (accountUser, actor) => accountUser.id === actor.id) 14 | accountUserIsActor; 15 | } 16 | -------------------------------------------------------------------------------- /ember/app/components/timeline-events/flight-comment.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/mirage/serializers/club.js: -------------------------------------------------------------------------------- 1 | import BaseSerializer from './application'; 2 | 3 | export default BaseSerializer.extend({ 4 | serialize(object) { 5 | let json = BaseSerializer.prototype.serialize.apply(this, arguments); 6 | 7 | if (object.owner) { 8 | json.owner = { 9 | id: Number(object.owner.id), 10 | name: object.owner.name, 11 | }; 12 | } else { 13 | json.owner = null; 14 | } 15 | 16 | return json; 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:js-app", 4 | ":dependencyDashboard", 5 | ":maintainLockFilesWeekly", 6 | ":semanticCommitsDisabled", 7 | ":automergeLinters", 8 | ":automergeTesters" 9 | ], 10 | "python": { 11 | "enabled": false 12 | }, 13 | "postUpdateOptions": ["yarnDedupeFewer"], 14 | "packageRules": [{ 15 | "packageNames": ["ember-cli", "ember-data", "ember-source"], 16 | "separateMinorPatch": true 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /ember/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'recommended', 3 | 4 | rules: { 5 | 'block-indentation': false, 6 | 'link-href-attributes': false, 7 | 'link-rel-noopener': false, 8 | 'no-inline-styles': false, 9 | 'no-invalid-interactive': false, 10 | 'no-negated-condition': false, 11 | 'require-button-type': false, 12 | 'require-valid-alt-text': false, 13 | 'style-concatenation': false, 14 | 'table-groups': false, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /ember/public/svg/icons/remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/facebook-square.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /tests/data/clubs.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from skylines.model import Club 4 | 5 | 6 | def lva(**kwargs): 7 | return Club( 8 | name=u"LV Aachen", 9 | website=u"http://www.lv-aachen.de", 10 | time_created=datetime(2015, 12, 24, 12, 34, 56), 11 | ).apply_kwargs(kwargs) 12 | 13 | 14 | def sfn(**kwargs): 15 | return Club( 16 | name=u"Sportflug Niederberg", time_created=datetime(2017, 1, 1, 12, 34, 56) 17 | ).apply_kwargs(kwargs) 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.py] 17 | indent_size = 4 18 | 19 | [*.hbs] 20 | insert_final_newline = false 21 | 22 | [*.{diff,md}] 23 | trim_trailing_whitespace = false 24 | -------------------------------------------------------------------------------- /skylines/commands/server.py: -------------------------------------------------------------------------------- 1 | from flask_script import Server as BaseServer 2 | from skylines.app import create_combined_app, create_api_app 3 | 4 | 5 | class Server(BaseServer): 6 | def handle(self, app, *args, **kw): 7 | app = create_combined_app() 8 | super(Server, self).handle(app, *args, **kw) 9 | 10 | 11 | class APIServer(BaseServer): 12 | def handle(self, app, *args, **kw): 13 | app = create_api_app() 14 | super(APIServer, self).handle(app, *args, **kw) 15 | -------------------------------------------------------------------------------- /INSTALL.mapserver.md: -------------------------------------------------------------------------------- 1 | ## Running a local mapserver 2 | 3 | If you want to run the map server locally you will need to install the 4 | `mapserver` CLI. On Debian you can do: 5 | 6 | $ apt-get install mapserver-cli 7 | 8 | To import airspaces into the database, install the `python-gdal` package (using 9 | gdal extension directly from pypi is not recommended) and import the required 10 | airspace files: 11 | 12 | $ ./manage.py import airspace airspace/airspace_list.txt airspace/airspace_blacklist.txt 13 | -------------------------------------------------------------------------------- /migrations/versions/ffa5706b1fb_added_callsign_colum.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "ffa5706b1fb" 3 | down_revision = "438d391a92b4" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | from sqlalchemy.dialects import postgresql 8 | 9 | 10 | def upgrade(): 11 | op.add_column( 12 | "users", sa.Column("tracking_callsign", sa.Unicode(length=5), nullable=True) 13 | ) 14 | 15 | 16 | def downgrade(): 17 | op.drop_column("users", "tracking_callsign") 18 | -------------------------------------------------------------------------------- /ember/app/styles/_spinner.scss: -------------------------------------------------------------------------------- 1 | @keyframes spinner { 2 | to { 3 | transform: rotate(360deg); 4 | } 5 | } 6 | 7 | .spinner { 8 | display: inline-block; 9 | width: 1.5rem; 10 | height: 1.5rem; 11 | vertical-align: text-bottom; 12 | border: .2em solid rgba(0, 0, 0, 0.35); 13 | border-right-color: black; 14 | border-radius: 50%; 15 | animation: spinner 1s linear infinite; 16 | } 17 | 18 | .spinner-white { 19 | border-color: rgba(255, 255, 255, 0.35); 20 | border-right-color: white; 21 | } 22 | -------------------------------------------------------------------------------- /ember/app/components/nav-bar-search-form.hbs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember/public/svg/icons/reorder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /tests/data/flight_comments.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from datetime import datetime 4 | 5 | from skylines.model import FlightComment 6 | 7 | 8 | def yeah(flight, **kwargs): 9 | return FlightComment( 10 | time_created=datetime(2017, 1, 19, 12, 34, 56), flight=flight, text=u"Yeah!" 11 | ).apply_kwargs(kwargs) 12 | 13 | 14 | def emoji(flight, **kwargs): 15 | return FlightComment( 16 | time_created=datetime(2020, 12, 19, 12, 34, 56), flight=flight, text=u"👍" 17 | ).apply_kwargs(kwargs) 18 | -------------------------------------------------------------------------------- /tests/data/live_fix.py: -------------------------------------------------------------------------------- 1 | from skylines.model import TrackingFix 2 | 3 | 4 | def create(pilot, time, latitude, longitude): 5 | """Creates a test fix for the passed pilot""" 6 | fix = TrackingFix( 7 | pilot=pilot, 8 | time=time, 9 | time_visible=time, 10 | track=0, 11 | ground_speed=10, 12 | airspeed=10, 13 | altitude=100, 14 | elevation=0, 15 | vario=0, 16 | ) 17 | 18 | fix.set_location(latitude, longitude) 19 | 20 | return fix 21 | -------------------------------------------------------------------------------- /ember/app/components/fix-table-row.js: -------------------------------------------------------------------------------- 1 | import { action, computed } from '@ember/object'; 2 | import { htmlSafe } from '@ember/template'; 3 | import Component from '@glimmer/component'; 4 | 5 | export default class extends Component { 6 | @computed('args.row.color') 7 | get badgeStyle() { 8 | return htmlSafe(`background-color: ${this.args.row.color}`); 9 | } 10 | 11 | @action 12 | handleClick() { 13 | if (this.args.selectable) { 14 | this.args.onSelect(this.args.row.id); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ember/app/controllers/search.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | import { oneWay, readOnly } from '@ember/object/computed'; 4 | 5 | export default class SearchController extends Controller { 6 | queryParams = ['text']; 7 | 8 | @readOnly('text') searchText; 9 | @oneWay('searchText') searchTextInput; 10 | @readOnly('model.results') results; 11 | 12 | @action 13 | search(text) { 14 | this.transitionToRoute({ queryParams: { text } }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/public/svg/icons/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/routes/club/pilots.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class PilotsRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | let { club_id } = this.paramsFor('club'); 9 | return this.ajax.request(`/api/users?club=${club_id}`); 10 | } 11 | 12 | setupController(controller) { 13 | super.setupController(...arguments); 14 | controller.set('club', this.controllerFor('club').get('model')); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/public/svg/icons/star-o.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/api/views/airports.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | from skylines.api.json import jsonify 4 | from skylines.lib.dbutil import get_requested_record 5 | from skylines.model import Airport 6 | from skylines.schemas import AirportSchema 7 | 8 | airports_blueprint = Blueprint("airports", "skylines") 9 | 10 | 11 | @airports_blueprint.route("/airports/") 12 | def index(airport_id): 13 | airport = get_requested_record(Airport, airport_id) 14 | return jsonify(AirportSchema().dump(airport).data) 15 | -------------------------------------------------------------------------------- /skylines/frontend/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import register as register_error_handlers 2 | 3 | from .assets import assets_blueprint 4 | from .files import files_blueprint 5 | from .livetrack24 import lt24_blueprint 6 | from .widgets import widgets_blueprint 7 | 8 | 9 | def register(app): 10 | register_error_handlers(app) 11 | 12 | app.register_blueprint(assets_blueprint) 13 | app.register_blueprint(files_blueprint) 14 | app.register_blueprint(lt24_blueprint) 15 | app.register_blueprint(widgets_blueprint) 16 | -------------------------------------------------------------------------------- /tests/data/airspace.py: -------------------------------------------------------------------------------- 1 | from geoalchemy2.shape import from_shape 2 | from shapely.geometry import Polygon 3 | 4 | from skylines.model import Airspace 5 | 6 | 7 | def test_airspace(**kwargs): 8 | shape = Polygon(((30, 10), (40, 40), (20, 40), (10, 20), (30, 10))) 9 | 10 | return Airspace( 11 | name="TestAirspace", 12 | airspace_class="WAVE", 13 | top="FL100", 14 | base="4500ft", 15 | country_code="de", 16 | the_geom=from_shape(shape, srid=4326), 17 | ).apply_kwargs(kwargs) 18 | -------------------------------------------------------------------------------- /ember/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | 3 | import loadInitializers from 'ember-load-initializers'; 4 | 5 | import config from './config/environment'; 6 | import Resolver from './resolver'; 7 | import { startSentry } from './sentry'; 8 | 9 | startSentry(); 10 | 11 | class App extends Application { 12 | modulePrefix = config.modulePrefix; 13 | podModulePrefix = config.podModulePrefix; 14 | Resolver = Resolver; 15 | } 16 | 17 | loadInitializers(App, config.modulePrefix); 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /ember/app/components/pin-button.js: -------------------------------------------------------------------------------- 1 | import { action, computed } from '@ember/object'; 2 | import { inject as service } from '@ember/service'; 3 | import Component from '@glimmer/component'; 4 | 5 | export default class PinButton extends Component { 6 | @service pinnedFlights; 7 | 8 | @computed('pinnedFlights.pinned.[]', 'args.flightId') 9 | get pinned() { 10 | return this.pinnedFlights.pinned.includes(this.args.flightId); 11 | } 12 | 13 | @action toggle() { 14 | this.pinnedFlights.toggle(this.args.flightId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/app/components/settings-panels/tracking-key.module.scss: -------------------------------------------------------------------------------- 1 | .panel-heading { 2 | composes: panel-heading from global; 3 | position: relative; 4 | } 5 | 6 | .help-icon { 7 | position: absolute; 8 | right: 0; 9 | top: 0; 10 | font-size: 20pt; 11 | color: #aaa; 12 | text-decoration: none; 13 | 14 | &:hover { 15 | color: #666; 16 | } 17 | } 18 | 19 | .tracking-key { 20 | composes: label from global; 21 | 22 | font-size: 100%; 23 | font-family: monospace; 24 | margin: 0 6px; 25 | background: #008eda; 26 | } 27 | -------------------------------------------------------------------------------- /ember/lib/cesium/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const Funnel = require('broccoli-funnel'); 6 | 7 | module.exports = { 8 | name: 'cesium', 9 | 10 | isDevelopingAddon() { 11 | return true; 12 | }, 13 | 14 | treeForPublic() { 15 | let cesiumPkgPath = require.resolve('cesium'); 16 | let cesiumPath = path.dirname(cesiumPkgPath); 17 | let cesiumBuildPath = path.join(cesiumPath, 'Build', 'Cesium'); 18 | return new Funnel(cesiumBuildPath, { destDir: 'cesium' }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /ember/mirage/models/mirage-session.js: -------------------------------------------------------------------------------- 1 | import { Model, belongsTo } from 'ember-cli-mirage'; 2 | 3 | /** 4 | * This is a mirage-only model, that is used to keep track of the current 5 | * session and the associated `user` model, because in route handlers we don't 6 | * have access to the auth data that the actual auth service is using for 7 | * these things. 8 | * 9 | * This mock implementation means that there can only ever exist one 10 | * session at a time. 11 | */ 12 | export default Model.extend({ 13 | user: belongsTo(), 14 | }); 15 | -------------------------------------------------------------------------------- /ember/public/svg/icons/plane.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/pin-star.js: -------------------------------------------------------------------------------- 1 | import { action, computed } from '@ember/object'; 2 | import { inject as service } from '@ember/service'; 3 | import Component from '@glimmer/component'; 4 | 5 | export default class PinStar extends Component { 6 | @service pinnedFlights; 7 | 8 | @computed('pinnedFlights.pinned.[]', 'args.flightId') 9 | get pinned() { 10 | return this.pinnedFlights.pinned.includes(this.args.flightId); 11 | } 12 | 13 | @action handleClick() { 14 | this.pinnedFlights.toggle(this.args.flightId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/app/controllers/about/team.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { computed } from '@ember/object'; 3 | import { inject as service } from '@ember/service'; 4 | 5 | export default class TeamController extends Controller { 6 | @service intl; 7 | 8 | @computed('model.content', 'intl.locale') 9 | get text() { 10 | let intl = this.intl; 11 | 12 | return this.get('model.content') 13 | .replace('Developers', intl.t('developers')) 14 | .replace('Translators', intl.t('translators')); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/app/templates/flight-upload.hbs: -------------------------------------------------------------------------------- 1 | {{#if result}} 2 | 3 | 6 | 7 | 8 | 9 | {{else}} 10 | 11 | 12 | 13 | {{/if}} 14 | -------------------------------------------------------------------------------- /ember/public/svg/icons/ban.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/commands/tracking/server.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | import sys 4 | from flask_script import Command 5 | 6 | from skylines.app import create_app 7 | from skylines.tracking.server import TrackingServer 8 | 9 | 10 | class Server(Command): 11 | """ Runs the live tracking UDP server """ 12 | 13 | def run(self): 14 | print("Receiving datagrams on :5597") 15 | sys.stdout.flush() 16 | server = TrackingServer(":5597") 17 | server.init_app(create_app()) 18 | server.serve_forever() 19 | -------------------------------------------------------------------------------- /ember/app/styles/bootstrap/_bootstrap-badges.scss: -------------------------------------------------------------------------------- 1 | // Colored "badges" 2 | 3 | .badge-default { 4 | @include label-variant($label-default-bg); 5 | } 6 | 7 | .badge-primary { 8 | @include label-variant($label-primary-bg); 9 | } 10 | 11 | .badge-success { 12 | @include label-variant($label-success-bg); 13 | } 14 | 15 | .badge-info { 16 | @include label-variant($label-info-bg); 17 | } 18 | 19 | .badge-warning { 20 | @include label-variant($label-warning-bg); 21 | } 22 | 23 | .badge-danger { 24 | @include label-variant($label-danger-bg); 25 | } 26 | -------------------------------------------------------------------------------- /ember/tests/helpers/initials-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | 3 | import { initials } from 'skylines/helpers/initials'; 4 | 5 | module('Unit | Helper | initials', function () { 6 | test('works', function (assert) { 7 | assert.equal(initials(['John Doe']), 'JD'); 8 | assert.equal(initials(['John Adam Bart Charly Doe']), 'JABCD'); 9 | assert.equal(initials(['John Fitz. Kennedy']), 'JK'); 10 | assert.equal(initials(['John F. Kennedy']), 'JK'); 11 | assert.equal(initials(['John F Kennedy']), 'JK'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /skylines/schemas/validate.py: -------------------------------------------------------------------------------- 1 | from marshmallow.validate import * # NOQA 2 | from marshmallow.validate import Validator, ValidationError 3 | 4 | 5 | class NotEmpty(Validator): 6 | def __call__(self, value): 7 | if len(value) == 0: 8 | raise ValidationError("Must not be empty.") 9 | 10 | 11 | class EmptyOr(Validator): 12 | def __init__(self, other_validator): 13 | self.other_validator = other_validator 14 | 15 | def __call__(self, value): 16 | if len(value) != 0: 17 | self.other_validator(value) 18 | -------------------------------------------------------------------------------- /tests/api/views/user_agent_test.py: -------------------------------------------------------------------------------- 1 | from werkzeug.datastructures import Headers 2 | 3 | 4 | def test_request_with_user_agent_works(client): 5 | res = client.get("/") 6 | assert res.status_code == 404 7 | 8 | 9 | def test_missing_user_agent_returns_403(client): 10 | headers = Headers() 11 | headers.set("User-Agent", "") 12 | 13 | res = client.get("/", headers=headers) 14 | assert res.status_code == 403 15 | 16 | json = res.json 17 | assert isinstance(json, dict) 18 | assert "User-Agent header" in json.get("message") 19 | -------------------------------------------------------------------------------- /ember/app/components/fullscreen-button.module.scss: -------------------------------------------------------------------------------- 1 | .fullscreen-button { 2 | z-index: 1006 !important; 3 | 4 | position: absolute; 5 | top: 88px; 6 | right: 12px; 7 | padding: 0; 8 | 9 | width: 32px; 10 | height: 32px; 11 | 12 | font-size: 26px; 13 | line-height: 30px; 14 | 15 | background: white; 16 | background: rgba(255,255,255,0.7); 17 | border: 1px solid #bbb; 18 | border-radius: 4px; 19 | box-shadow: 0 0 8px rgba(50,50,50,0.3); 20 | 21 | cursor: pointer; 22 | 23 | &:hover { 24 | background: white; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /skylines/commands/tracking/clear.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from flask_script import Command, Option 4 | from skylines.database import db 5 | from skylines.model import TrackingFix 6 | 7 | 8 | class Clear(Command): 9 | """ Clear all live tracks for a certain user """ 10 | 11 | option_list = (Option("user", type=int, help="a user ID"),) 12 | 13 | def run(self, user): 14 | result = TrackingFix.query(pilot_id=user).delete() 15 | db.session.commit() 16 | 17 | print("%d live tracks cleared." % result) 18 | -------------------------------------------------------------------------------- /ember/app/components/layer-switcher-element.hbs: -------------------------------------------------------------------------------- 1 | 9 | {{@name}} 10 | {{svg-jar "check" class="fa-svg"}} {{@name}} 11 | -------------------------------------------------------------------------------- /ember/mirage/config.js: -------------------------------------------------------------------------------- 1 | import * as Clubs from './route-handlers/clubs'; 2 | import * as Notifications from './route-handlers/notifications'; 3 | import * as Settings from './route-handlers/settings'; 4 | import * as Users from './route-handlers/users'; 5 | 6 | export default function () { 7 | this.passthrough('/translations/**'); 8 | this.passthrough('/write-coverage'); 9 | 10 | this.get('/api/locale', { locale: 'en' }); 11 | 12 | Clubs.register(this); 13 | Notifications.register(this); 14 | Settings.register(this); 15 | Users.register(this); 16 | } 17 | -------------------------------------------------------------------------------- /ember/tests/acceptance/index-test.js: -------------------------------------------------------------------------------- 1 | import { visit, currentURL } from '@ember/test-helpers'; 2 | import { module, test } from 'qunit'; 3 | 4 | import { setupApplicationTest } from '../test-helpers'; 5 | 6 | module('Acceptance | index', function (hooks) { 7 | setupApplicationTest(hooks); 8 | 9 | hooks.beforeEach(async function (assert) { 10 | await visit('/'); 11 | assert.equal(currentURL(), '/'); 12 | }); 13 | 14 | test('shows a welcome message', function (assert) { 15 | assert.dom('[data-test-welcome-message]').exists(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /skylines/api/json.py: -------------------------------------------------------------------------------- 1 | from flask import request, json, current_app 2 | 3 | 4 | def jsonify(_data=None, **kwargs): 5 | if _data is None: 6 | _data = kwargs 7 | 8 | if not isinstance(_data, (dict, list)): 9 | raise TypeError 10 | 11 | # Determine JSON indentation 12 | indent = None 13 | if current_app.config["JSONIFY_PRETTYPRINT_REGULAR"] and not request.is_xhr: 14 | indent = 2 15 | 16 | content = json.dumps(_data, indent=indent) 17 | 18 | return current_app.response_class(content, mimetype="application/json") 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Python 2.7 by default, but allow 2 | # e.g. `--build-arg PYTHON_VERSION=3.6` usage 3 | ARG PYTHON_VERSION=2.7 4 | FROM python:${PYTHON_VERSION} 5 | 6 | RUN pip install --upgrade pip 7 | RUN pip install --upgrade virtualenv 8 | RUN pip install pipenv==v2020.11.15 9 | 10 | WORKDIR /home/skylines/code/ 11 | 12 | # Install Python dependencies 13 | COPY Pipfile Pipfile.lock /home/skylines/code/ 14 | RUN pipenv install --dev --python=${PYTHON_VERSION} 15 | 16 | # Run the development server by default 17 | CMD ["pipenv", "run", "./manage.py", "runserver"] 18 | -------------------------------------------------------------------------------- /ember/app/components/user-quick-stats.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import safeComputed from '../computed/safe-computed'; 4 | 5 | export default class UserQuickStats extends Component { 6 | @safeComputed('args.stats.distance', 'args.stats.duration', (distance, duration) => distance / duration) speed; 7 | @safeComputed('args.stats.distance', 'args.stats.flights', (distance, flights) => distance / flights) avgDistance; 8 | @safeComputed('args.stats.duration', 'args.stats.flights', (duration, flights) => duration / flights) avgDuration; 9 | } 10 | -------------------------------------------------------------------------------- /ember/app/components/background-layers.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{#if this.bingApiKey}} 6 | 7 | 8 | {{/if}} 9 | {{#if this.mapBoxUrl}} 10 | 11 | {{/if}} 12 | -------------------------------------------------------------------------------- /ember/public/svg/icons/clock-o.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/user-distance-flight.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{@label}} 3 | 4 | {{#if @flight}} 5 | 11 |
    12 | {{t "show"}} 13 |
    14 | {{/if}} 15 | 16 | -------------------------------------------------------------------------------- /ember/app/routes/user/followers.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class FollowersRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | let { user_id } = this.paramsFor('user'); 9 | return this.ajax.request(`/api/users/${user_id}/followers`); 10 | } 11 | 12 | setupController(controller, model) { 13 | super.setupController(...arguments); 14 | controller.set('user', this.modelFor('user')); 15 | controller.set('followers', model.followers); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ember/app/routes/user/following.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class FollowingRoute extends Route { 5 | @service ajax; 6 | 7 | model() { 8 | let { user_id } = this.paramsFor('user'); 9 | return this.ajax.request(`/api/users/${user_id}/following`); 10 | } 11 | 12 | setupController(controller, model) { 13 | super.setupController(...arguments); 14 | controller.set('user', this.modelFor('user')); 15 | controller.set('following', model.following); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ember/app/utils/geo-distance.js: -------------------------------------------------------------------------------- 1 | const RADIUS = 6367009; 2 | const DEG_TO_RAD = Math.PI / 180; 3 | 4 | export default function (loc1_deg, loc2_deg) { 5 | let loc1 = [loc1_deg[0] * DEG_TO_RAD, loc1_deg[1] * DEG_TO_RAD]; 6 | let loc2 = [loc2_deg[0] * DEG_TO_RAD, loc2_deg[1] * DEG_TO_RAD]; 7 | 8 | let dlon = loc2[0] - loc1[0]; 9 | let dlat = loc2[1] - loc1[1]; 10 | 11 | let a = Math.pow(Math.sin(dlat / 2), 2) + Math.cos(loc1[1]) * Math.cos(loc2[1]) * Math.pow(Math.sin(dlon / 2), 2); 12 | 13 | let c = 2 * Math.asin(Math.sqrt(a)); 14 | 15 | return RADIUS * c; 16 | } 17 | -------------------------------------------------------------------------------- /ember/mirage/factories/user.js: -------------------------------------------------------------------------------- 1 | import { Factory } from 'ember-cli-mirage'; 2 | 3 | export default Factory.extend({ 4 | admin: false, 5 | altitudeUnit: 0, 6 | distanceUnit: 1, 7 | email: 'johnny.dee@gmail.com', 8 | firstName: 'John', 9 | followers: 107, 10 | following: 128, 11 | lastName: 'Doe', 12 | liftUnit: 0, 13 | name() { 14 | return `${this.firstName} ${this.lastName}`; 15 | }, 16 | password: 'secret123', 17 | recoveryKey: null, 18 | speedUnit: 1, 19 | trackingCallsign: 'JD', 20 | trackingDelay: 0, 21 | trackingKey: 'ABCDEF42', 22 | }); 23 | -------------------------------------------------------------------------------- /skylines/commands/tracking/fill_missing_keys.py: -------------------------------------------------------------------------------- 1 | from flask_script import Command 2 | from skylines.database import db 3 | from skylines.model import User 4 | 5 | 6 | class FillMissingKeys(Command): 7 | """ Generate tracking keys for user accounts that don't have one. """ 8 | 9 | def run(self): 10 | keyless_users = User.query(tracking_key=None) 11 | 12 | for i, user in enumerate(keyless_users): 13 | user.generate_tracking_key() 14 | 15 | if i % 25 == 0: 16 | db.session.flush() 17 | 18 | db.session.commit() 19 | -------------------------------------------------------------------------------- /ember/app/routes/flight/change-aircraft.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | import RSVP from 'rsvp'; 4 | 5 | export default class ChangeAircraftRoute extends Route { 6 | @service ajax; 7 | 8 | model() { 9 | let id = this.modelFor('flight').ids[0]; 10 | 11 | let flight = this.ajax.request(`/api/flights/${id}/`).then(it => it.flight); 12 | let aircraftModels = this.ajax.request('/api/aircraft-models').then(it => it.models); 13 | 14 | return RSVP.hash({ id, flight, aircraftModels }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ember/app/components/sortable-table-header.hbs: -------------------------------------------------------------------------------- 1 | {{#if (not-eq @column @sortColumn)}} 2 | {{yield}} 3 | {{else if (eq @sortOrder "asc")}} 4 | {{svg-jar "chevron-up" class="fa-svg"}} {{yield}} 5 | {{else}} 6 | {{svg-jar "chevron-down" class="fa-svg"}} {{yield}} 7 | {{/if}} 8 | -------------------------------------------------------------------------------- /ember/public/svg/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/background-layers/empty.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import TileLayer from 'ol/layer/Tile'; 4 | 5 | export default class extends Component { 6 | layer = new TileLayer({}); 7 | 8 | constructor() { 9 | super(...arguments); 10 | 11 | this.layer.setProperties({ 12 | name: 'Empty', 13 | id: 'Empty', 14 | base_layer: true, 15 | display_in_layer_switcher: true, 16 | }); 17 | 18 | this.args.map.addLayer(this.layer); 19 | } 20 | 21 | willDestroy() { 22 | this.args.map.removeLayer(this.layer); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ember/app/formats.js: -------------------------------------------------------------------------------- 1 | export default { 2 | time: { 3 | hhmm: { 4 | hour: '2-digit', 5 | minute: '2-digit', 6 | }, 7 | hhmmss: { 8 | hour: '2-digit', 9 | minute: '2-digit', 10 | second: '2-digit', 11 | }, 12 | full: { 13 | year: 'numeric', 14 | month: 'short', 15 | day: 'numeric', 16 | hour: '2-digit', 17 | minute: '2-digit', 18 | second: '2-digit', 19 | }, 20 | }, 21 | date: { 22 | ddmmyyyy: { 23 | year: 'numeric', 24 | month: '2-digit', 25 | day: '2-digit', 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /tests/schemas/schemas/test_flight.py: -------------------------------------------------------------------------------- 1 | from skylines.schemas import FlightSchema 2 | 3 | 4 | def test_deserialization_passes_for_valid_model_id(): 5 | data = FlightSchema(partial=True).load(dict(modelId=4)).data 6 | assert data["model_id"] == 4 7 | 8 | 9 | def test_deserialization_passes_for_missing_model_id(): 10 | data = FlightSchema(partial=True).load(dict()).data 11 | assert "model_id" not in data 12 | 13 | 14 | def test_deserialization_passes_for_null_model_id(): 15 | data = FlightSchema(partial=True).load(dict(modelId=None)).data 16 | assert data["model_id"] == None 17 | -------------------------------------------------------------------------------- /ember/app/validators/current-password.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseValidator from 'ember-cp-validations/validators/base'; 4 | 5 | export default class CurrentPassword extends BaseValidator { 6 | @service ajax; 7 | @service intl; 8 | 9 | async validate(password, options) { 10 | if (!password) { 11 | return; 12 | } 13 | 14 | let json = { password }; 15 | let { result } = await this.ajax.request('/api/settings/password/check', { method: 'POST', json }); 16 | return result ? true : this.intl.t(options.messageKey); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ember/public/svg/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/sidebar/tab.module.scss: -------------------------------------------------------------------------------- 1 | .list-item { 2 | width: 100%; 3 | height: 40px; 4 | color: #999; 5 | font-size: 12pt; 6 | overflow: hidden; 7 | transition: all 80ms; 8 | list-style-type: none; 9 | 10 | &:hover { 11 | color: #000; 12 | background-color: #ccc; 13 | } 14 | 15 | &.active { 16 | color: #fff; 17 | background-color: #428bca; 18 | } 19 | } 20 | 21 | .button { 22 | width: 100%; 23 | height: 100%; 24 | padding: 0; 25 | border: none; 26 | font: inherit; 27 | color: inherit; 28 | background-color: transparent; 29 | cursor: pointer; 30 | } 31 | -------------------------------------------------------------------------------- /ember/app/routes/login.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class LoginRoute extends Route { 5 | @service session; 6 | 7 | beforeModel() { 8 | this.session.prohibitAuthentication('index'); 9 | } 10 | 11 | setupController() { 12 | super.setupController(...arguments); 13 | this.controllerFor('application').set('inLoginRoute', true); 14 | } 15 | 16 | resetController() { 17 | super.resetController(...arguments); 18 | this.controllerFor('application').set('inLoginRoute', false); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/lib/test_files.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | from skylines.lib import files 6 | from skylines.lib.types import is_unicode 7 | 8 | 9 | @pytest.mark.parametrize( 10 | "input,expected", 11 | [ 12 | (b"foo/bar/baz.igc", u"baz.igc"), 13 | (u"HERR.müller@123.igc", u"herr.m_ller_123.igc"), 14 | (u"abc/...1234.igc", u"1234.igc"), 15 | (u"", u"empty"), 16 | ], 17 | ) 18 | def test_sanitise_filename(input, expected): 19 | output = files.sanitise_filename(input) 20 | 21 | assert is_unicode(output) 22 | assert output == expected 23 | -------------------------------------------------------------------------------- /ember/app/components/contest-layer-feature.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | 3 | import Feature from 'ol/Feature'; 4 | 5 | export default class ContestLayerFeature extends Component { 6 | constructor() { 7 | super(...arguments); 8 | 9 | const { contest, source } = this.args; 10 | 11 | let { geometry, isTriangle } = contest; 12 | this.feature = new Feature({ geometry, isTriangle }); 13 | 14 | source.addFeature(this.feature); 15 | } 16 | 17 | willDestroy() { 18 | super.willDestroy(...arguments); 19 | this.args.source.removeFeature(this.feature); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /migrations/versions/2dade673f10e_add_qnh_column_to_flight.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "2dade673f10e" 3 | down_revision = "1d8eda758ba6" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | ### commands auto generated by Alembic - please adjust! ### 11 | op.add_column("flights", sa.Column("qnh", sa.Float(), nullable=True)) 12 | ### end Alembic commands ### 13 | 14 | 15 | def downgrade(): 16 | ### commands auto generated by Alembic - please adjust! ### 17 | op.drop_column("flights", "qnh") 18 | ### end Alembic commands ### 19 | -------------------------------------------------------------------------------- /ember/app/templates/tracking/details.hbs: -------------------------------------------------------------------------------- 1 | {{#if (eq model.pilots.length 1)}} 2 | {{page-title (t "live-tracking-of-PILOT" pilot=model.pilots.0.name)}} 3 | {{else if (eq model.pilots.length 2)}} 4 | {{page-title (t "live-tracking-of-PILOT-and-PILOT2" pilot=model.pilots.0.name pilot2=model.pilots.1.name)}} 5 | {{else}} 6 | {{page-title (t "live-tracking-of-PILOT-and-NUM-other-pilots" pilot=model.pilots.0.name num=(sum model.pilots.length -1))}} 7 | {{/if}} 8 | 9 |
    10 | 11 |
    12 | -------------------------------------------------------------------------------- /ember/public/svg/icons/info-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/worker/celery.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | from celery import Celery 3 | 4 | 5 | def init_app(self, app): 6 | self.name = app.import_name 7 | self.config_from_object(app.config) 8 | 9 | TaskBase = self.Task 10 | 11 | class ContextTask(TaskBase): 12 | abstract = True 13 | 14 | def __call__(self, *args, **kwargs): 15 | with app.app_context(): 16 | return TaskBase.__call__(self, *args, **kwargs) 17 | 18 | self.Task = ContextTask 19 | 20 | 21 | celery = Celery(include=["skylines.worker.tasks"]) 22 | Celery.init_app = init_app 23 | -------------------------------------------------------------------------------- /ember/app/components/circling-performance-table.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{#if this.total}}{{/if}} 9 | {{#if this.left}}{{/if}} 10 | {{#if this.right}}{{/if}} 11 | {{#if this.mixed}}{{/if}} 12 | 13 |
    {{t "count"}}
    {{t "avg-vario-abbr"}}
    {{t "duration"}}
    ΔH
    -------------------------------------------------------------------------------- /migrations/versions/66650ad3d70_unregistered_pilot_n.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "66650ad3d70" 3 | down_revision = "3e82b37a4989" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | op.add_column( 11 | "flights", sa.Column("pilot_name", sa.Unicode(length=255), nullable=True) 12 | ) 13 | op.add_column( 14 | "flights", sa.Column("co_pilot_name", sa.Unicode(length=255), nullable=True) 15 | ) 16 | 17 | 18 | def downgrade(): 19 | op.drop_column("flights", "pilot_name") 20 | op.drop_column("flights", "co_pilot_name") 21 | -------------------------------------------------------------------------------- /skylines/frontend/views/assets.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | from flask import Blueprint, current_app, send_from_directory, send_file 4 | from werkzeug.exceptions import NotFound 5 | 6 | assets_blueprint = Blueprint("assets", "skylines") 7 | 8 | 9 | @assets_blueprint.route("/") 10 | @assets_blueprint.route("/") 11 | def static(**kwargs): 12 | path = kwargs.get("path", "") 13 | assets_folder = current_app.config.get("ASSETS_LOAD_DIR") 14 | try: 15 | return send_from_directory(assets_folder, path) 16 | except NotFound: 17 | return send_file(os.path.join(assets_folder, "index.html")) 18 | -------------------------------------------------------------------------------- /skylines/tracking/crc.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from crc16 import crc16xmodem 3 | 4 | 5 | def calc_crc(data): 6 | assert len(data) >= 16 7 | 8 | crc = crc16xmodem(data[:4]) 9 | crc = crc16xmodem(b"\0\0", crc) 10 | crc = crc16xmodem(data[6:], crc) 11 | return crc 12 | 13 | 14 | def check_crc(data): 15 | assert len(data) >= 16 16 | 17 | crc1 = calc_crc(data) 18 | crc2 = struct.unpack_from("!H", data, 4)[0] 19 | return crc1 == crc2 20 | 21 | 22 | def set_crc(data): 23 | assert len(data) >= 16 24 | 25 | crc = calc_crc(data) 26 | return data[:4] + struct.pack("!H", crc) + data[6:] 27 | -------------------------------------------------------------------------------- /ember/app/components/circling-performance-table.js: -------------------------------------------------------------------------------- 1 | import { computed } from '@ember/object'; 2 | import Component from '@glimmer/component'; 3 | 4 | export default class CirclingPerformanceTable extends Component { 5 | @findBy('perf', 'circlingDirection', 'left') left; 6 | @findBy('perf', 'circlingDirection', 'right') right; 7 | @findBy('perf', 'circlingDirection', 'mixed') mixed; 8 | @findBy('perf', 'circlingDirection', 'total') total; 9 | } 10 | 11 | function findBy(array, key, value) { 12 | return computed(`${array}.@each.${key}`, function () { 13 | return this.args[array]?.findBy(key, value); 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /migrations/versions/58325345d375_add_weglide_fields_to_igcfile.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "58325345d375" 3 | down_revision = "70a6f5e6f0e1" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | from sqlalchemy.dialects.postgresql import JSONB 8 | 9 | 10 | def upgrade(): 11 | op.add_column("igc_files", sa.Column("weglide_data", JSONB(), nullable=True)) 12 | op.add_column("igc_files", sa.Column("weglide_status", sa.Integer(), nullable=True)) 13 | 14 | 15 | def downgrade(): 16 | op.drop_column("igc_files", "weglide_status") 17 | op.drop_column("igc_files", "weglide_data") 18 | -------------------------------------------------------------------------------- /ember/app/components/login-form.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | import { task } from 'ember-concurrency'; 5 | 6 | export default class LoginForm extends Component { 7 | tagName = ''; 8 | 9 | @service session; 10 | 11 | inline = false; 12 | error = null; 13 | 14 | @(task(function* () { 15 | let { email, password } = this; 16 | 17 | try { 18 | yield this.session.authenticate('authenticator:oauth2', email, password); 19 | } catch (error) { 20 | this.set('error', error); 21 | } 22 | }).drop()) 23 | loginTask; 24 | } 25 | -------------------------------------------------------------------------------- /ember/public/svg/icons/asterisk.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/download.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /migrations/versions/1f33435e1753_added_valid_until_co.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "1f33435e1753" 3 | down_revision = "ffa5706b1fb" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | ### commands auto generated by Alembic - please adjust! ### 11 | op.add_column("airports", sa.Column("valid_until", sa.DateTime(), nullable=True)) 12 | ### end Alembic commands ### 13 | 14 | 15 | def downgrade(): 16 | ### commands auto generated by Alembic - please adjust! ### 17 | op.drop_column("airports", "valid_until") 18 | ### end Alembic commands ### 19 | -------------------------------------------------------------------------------- /migrations/versions/8a8e921cb88_added_scoring_window_columns.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "8a8e921cb88" 3 | down_revision = "54d325616135" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | def upgrade(): 10 | op.add_column( 11 | "flights", sa.Column("scoring_start_time", sa.DateTime(), nullable=True) 12 | ) 13 | op.add_column( 14 | "flights", sa.Column("scoring_end_time", sa.DateTime(), nullable=True) 15 | ) 16 | 17 | 18 | def downgrade(): 19 | op.drop_column("flights", "scoring_start_time") 20 | op.drop_column("flights", "scoring_end_time") 21 | -------------------------------------------------------------------------------- /ember/app/routes/flights/pinned.js: -------------------------------------------------------------------------------- 1 | import { inject as service } from '@ember/service'; 2 | 3 | import BaseRoute from './-base'; 4 | 5 | export default class PinnedRoute extends BaseRoute { 6 | @service pinnedFlights; 7 | 8 | model(params) { 9 | let pinned = this.get('pinnedFlights.pinned') || []; 10 | if (pinned.length === 0) { 11 | return { count: 0, flights: [] }; 12 | } 13 | 14 | return this.ajax.request(`/api/flights/list/${pinned.join(',')}`, { 15 | data: { 16 | page: params.page, 17 | column: params.column, 18 | order: params.order, 19 | }, 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ember/public/svg/icons/rotate-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/app/components/aircraft-model-select.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | import { computed } from '@ember/object'; 3 | 4 | export default Component.extend({ 5 | tagName: '', 6 | 7 | models: null, 8 | modelId: null, 9 | 10 | modelsWithNull: computed('models.[]', function () { 11 | return [{ id: null }].concat(this.models); 12 | }), 13 | 14 | model: computed('modelsWithNull.@each.id', 'modelId', function () { 15 | return this.modelsWithNull.findBy('id', this.modelId || null); 16 | }), 17 | 18 | actions: { 19 | onChange(model) { 20 | this.onChange(model.id); 21 | }, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /ember/public/svg/icons/sign-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/sign-out.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/upload.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /skylines/api/args.py: -------------------------------------------------------------------------------- 1 | from flask import abort 2 | from webargs import fields 3 | 4 | from skylines.model import Location 5 | 6 | 7 | pagination_args = { 8 | "page": fields.Integer(missing=1, location="query", validate=lambda val: val > 0), 9 | "per_page": fields.Integer( 10 | missing=30, location="query", validate=lambda val: val <= 100 11 | ), 12 | } 13 | 14 | 15 | def parse_location(args): 16 | try: 17 | latitude = float(args["lat"]) 18 | longitude = float(args["lon"]) 19 | return Location(latitude=latitude, longitude=longitude) 20 | 21 | except (KeyError, ValueError): 22 | abort(400) 23 | -------------------------------------------------------------------------------- /skylines/api/views/aircraft_models.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | from skylines.api.json import jsonify 4 | from skylines.model import AircraftModel 5 | from skylines.schemas import AircraftModelSchema 6 | 7 | aircraft_models_blueprint = Blueprint("aircraft_models", "skylines") 8 | 9 | 10 | @aircraft_models_blueprint.route("/aircraft-models", strict_slashes=False) 11 | def index(): 12 | models = ( 13 | AircraftModel.query() 14 | .order_by(AircraftModel.kind) 15 | .order_by(AircraftModel.name) 16 | .all() 17 | ) 18 | 19 | return jsonify(models=AircraftModelSchema().dump(models, many=True).data) 20 | -------------------------------------------------------------------------------- /ember/app/components/fullscreen-button.js: -------------------------------------------------------------------------------- 1 | import { action } from '@ember/object'; 2 | import { inject as service } from '@ember/service'; 3 | import Component from '@glimmer/component'; 4 | 5 | import screenfull from 'screenfull'; 6 | 7 | export default class FullscreenButton extends Component { 8 | @service intl; 9 | @service notifications; 10 | 11 | @action toggle() { 12 | if (screenfull.isEnabled) { 13 | let element = this.args.fullscreenElement; 14 | screenfull.toggle(document.querySelector(element)); 15 | } else { 16 | this.notifications.warning(this.intl.t('fullscreen-not-supported')); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ember/public/svg/icons/rotate-left.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/public/svg/icons/question.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /ember/lib/freestyle/app/templates/freestyle.hbs: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | {{#each components as |componentName|}} 17 | 18 | {{component (concat "usage/" componentName)}} 19 | 20 | {{/each}} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /migrations/versions/16f9f28f0ea3_added_privacy_level_column.py: -------------------------------------------------------------------------------- 1 | # revision identifiers, used by Alembic. 2 | revision = "16f9f28f0ea3" 3 | down_revision = "8a8e921cb88" 4 | 5 | from alembic import op 6 | import sqlalchemy as sa 7 | 8 | 9 | flights = sa.sql.table("flights", sa.sql.column("privacy_level", sa.SmallInteger)) 10 | 11 | 12 | def upgrade(): 13 | op.add_column("flights", sa.Column("privacy_level", sa.SmallInteger())) 14 | 15 | op.execute(flights.update().values({"privacy_level": 0})) 16 | 17 | op.alter_column("flights", "privacy_level", nullable=False) 18 | 19 | 20 | def downgrade(): 21 | op.drop_column("flights", "privacy_level") 22 | --------------------------------------------------------------------------------