Skip to content

Commit f9beaef

Browse files
mtlynchjotaen
andauthored
Add an About page (#976)
* Add an About page * work in progress * work in progress * Add credits * work in progress * Fix power menu item * work in progress * work in progress * Add a route to TinyPilot license * Tweak credits * work in progress * work in progress * work in progress * Match licenses by glob * Add more licenses * Use brand color * More style tweaks * work in progress * Convert credit box to HTML custom element * Finish populating third-party dependencies * Add notes about missing Python licenses * Reorder licenses * Add docstring * Clarify args * Fix wording in comment * Narrow scope of JSON dict * Update comment * Handle license URLs in Python * Restructuring project metadata * Restructure license route * Add Overpass font * About page hacky suggestions (#977) * Hacky layout suggestions * Use columns for credits Co-authored-by: Jan Heuermann <[email protected]> * Improve CSS * Match git's style for hash length * Fix TinyPilot license URL * Rename modules * Restructure project metadata * Centralize license metadata * Update docstring * Refactor license metadata * Move TinyPilot license logic to frontend * work in progress * Initialize about page explicitly * Move Github link to About dialog * Add docstring for all licenses route * Delete unused CSS * Fold credit-box template into about-dialog * Fail gracefully if license isn't found in expected path * Get rid of duplicate glob * Refactor call to make_plaintext_response * Explain 302 * Move template outside of other template * Refactor CSS * Fix CSS * Change copyright holder to TinyPilot, LLC * Update based on code review notes Co-authored-by: Jan Heuermann <[email protected]>
1 parent f34beaa commit f9beaef

11 files changed

+522
-17
lines changed

LICENSE

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright 2020 Michael Lynch
1+
Copyright 2022 TinyPilot, LLC
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
44

app/licensing.py

+330
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
import dataclasses
2+
import glob
3+
4+
import flask
5+
6+
import json_response
7+
8+
9+
@dataclasses.dataclass
10+
class LicenseMetadata:
11+
name: str
12+
homepage_url: str
13+
# The license_glob_pattern and license_url properties are mutually
14+
# exclusive.
15+
# Glob pattern that points to the license file on the local system.
16+
license_glob_pattern: str = ''
17+
# URL of license file if it's not available on the local system.
18+
license_url: str = ''
19+
20+
21+
# For code where the source or license is on the local device, prefer linking to
22+
# the locally-available copy instead of the remote URL so that the license ties
23+
# tightly to the version we're using. When that's not possible, look for
24+
# permalink versions of the license that match the version of the software we're
25+
# using.
26+
27+
# pylint: disable=line-too-long
28+
_LICENSE_METADATA = [
29+
LicenseMetadata(name='TinyPilot',
30+
license_glob_pattern='./LICENSE',
31+
homepage_url='https://tinypilotkvm.com'),
32+
LicenseMetadata(
33+
name='uStreamer',
34+
license_url=
35+
'https://raw.githubusercontent.com/tiny-pilot/ustreamer/v4.13/LICENSE',
36+
homepage_url='https://github.com/pikvm/ustreamer'),
37+
LicenseMetadata(name='Python',
38+
homepage_url='https://python.org',
39+
license_glob_pattern='/usr/lib/python3.7/LICENSE.txt'),
40+
LicenseMetadata(name='nginx',
41+
license_url='https://nginx.org/LICENSE',
42+
homepage_url='https://nginx.org'),
43+
LicenseMetadata(
44+
name='Janus',
45+
homepage_url='https://janus.conf.meetecho.com',
46+
license_url=
47+
'https://raw.githubusercontent.com/tiny-pilot/janus-gateway/v1.0.0/COPYING'
48+
),
49+
50+
# JavaScript dependencies.
51+
LicenseMetadata(
52+
name='janus.js',
53+
homepage_url='https://janus.conf.meetecho.com',
54+
license_glob_pattern='./app/static/third-party/janus-gateway/*/janus.js',
55+
),
56+
LicenseMetadata(
57+
name='webrtc-adapter',
58+
homepage_url='https://github.com/webrtcHacks/adapter',
59+
license_url=
60+
'https://github.com/webrtcHacks/adapter/blob/18a8b4127cbc1376320cac5742d817b5b7dd0085/LICENSE.md',
61+
),
62+
LicenseMetadata(
63+
name='socket.io',
64+
homepage_url='https://socket.io',
65+
license_url='https://github.com/socketio/socket.io/blob/3.1.2/LICENSE',
66+
),
67+
68+
# Python dependencies, from requirements.txt.
69+
LicenseMetadata(
70+
name='eventlet',
71+
homepage_url='https://eventlet.net',
72+
license_glob_pattern=
73+
'./venv/lib/python3.7/site-packages/eventlet-*.dist-info/LICENSE*',
74+
),
75+
LicenseMetadata(
76+
name='Flask',
77+
homepage_url='https://flask.palletsprojects.com',
78+
license_glob_pattern=
79+
'./venv/lib/python3.7/site-packages/Flask-*.dist-info/LICENSE*',
80+
),
81+
LicenseMetadata(
82+
name='Flask-SocketIO',
83+
homepage_url='https://flask-socketio.readthedocs.io',
84+
license_glob_pattern=
85+
'./venv/lib/python3.7/site-packages/Flask_SocketIO-*.dist-info/LICENSE*',
86+
),
87+
LicenseMetadata(
88+
name='Flask-WTF',
89+
homepage_url='https://flask-wtf.readthedocs.io',
90+
license_glob_pattern=
91+
'./venv/lib/python3.7/site-packages/Flask_WTF-*.dist-info/LICENSE*',
92+
),
93+
LicenseMetadata(
94+
name='pyyaml',
95+
homepage_url='https://pyyaml.org',
96+
license_url=
97+
'https://raw.githubusercontent.com/yaml/pyyaml/5.4.1/LICENSE',
98+
),
99+
LicenseMetadata(
100+
name='bidict',
101+
homepage_url='https://bidict.readthedocs.io/en/main',
102+
license_glob_pattern=
103+
'./venv/lib/python3.7/site-packages/bidict-*.dist-info/LICENSE*',
104+
),
105+
LicenseMetadata(
106+
name='click',
107+
homepage_url='https://palletsprojects.com/p/click',
108+
license_glob_pattern=
109+
'./venv/lib/python3.7/site-packages/click-*.dist-info/LICENSE*',
110+
),
111+
LicenseMetadata(
112+
name='dnspython',
113+
homepage_url='https://www.dnspython.org',
114+
license_glob_pattern=
115+
'./venv/lib/python3.7/site-packages/dnspython-*.dist-info/LICENSE*',
116+
),
117+
LicenseMetadata(
118+
name='greenlet',
119+
homepage_url='https://greenlet.readthedocs.io',
120+
license_glob_pattern=
121+
'./venv/lib/python3.7/site-packages/greenlet-*.dist-info/LICENSE*',
122+
),
123+
LicenseMetadata(
124+
name='itsdangerous',
125+
homepage_url='https://palletsprojects.com/p/itsdangerous/',
126+
license_glob_pattern=
127+
'./venv/lib/python3.7/site-packages/itsdangerous-*.dist-info/LICENSE*',
128+
),
129+
LicenseMetadata(
130+
name='Jinja2',
131+
homepage_url='https://palletsprojects.com/p/jinja/',
132+
license_glob_pattern=
133+
'./venv/lib/python3.7/site-packages/Jinja2-*.dist-info/LICENSE*',
134+
),
135+
LicenseMetadata(
136+
name='MarkupSafe',
137+
homepage_url='https://palletsprojects.com/p/markupsafe/',
138+
license_glob_pattern=
139+
'./venv/lib/python3.7/site-packages/MarkupSafe-*.dist-info/LICENSE*',
140+
),
141+
LicenseMetadata(
142+
name='monotonic',
143+
homepage_url='https://github.com/atdt/monotonic',
144+
license_url=
145+
'https://raw.githubusercontent.com/atdt/monotonic/1.5/LICENSE',
146+
),
147+
LicenseMetadata(
148+
name='python-engineio',
149+
homepage_url='https://github.com/miguelgrinberg/python-engineio',
150+
license_glob_pattern=
151+
'./venv/lib/python3.7/site-packages/python_engineio-*.dist-info/LICENSE*',
152+
),
153+
LicenseMetadata(
154+
name='python-socketio',
155+
homepage_url='https://github.com/miguelgrinberg/python-socketio',
156+
license_glob_pattern=
157+
'./venv/lib/python3.7/site-packages/python_socketio-*.dist-info/LICENSE*',
158+
),
159+
LicenseMetadata(
160+
name='six',
161+
homepage_url='https://github.com/benjaminp/six',
162+
license_glob_pattern=
163+
'./venv/lib/python3.7/site-packages/six-*.dist-info/LICENSE*',
164+
),
165+
LicenseMetadata(
166+
name='Werkzeug',
167+
homepage_url='https://palletsprojects.com/p/werkzeug/',
168+
license_glob_pattern=
169+
'./venv/lib/python3.7/site-packages/Werkzeug-*.dist-info/LICENSE*',
170+
),
171+
LicenseMetadata(
172+
name='WTForms',
173+
homepage_url='https://wtforms.readthedocs.io',
174+
license_glob_pattern=
175+
'./venv/lib/python3.7/site-packages/WTForms-*.dist-info/LICENSE*',
176+
),
177+
178+
# Ansible dependencies.
179+
# These licenses are technically available on the device, but the tinypilot
180+
# web app user doesn't have read access to them.
181+
LicenseMetadata(
182+
name='Ansible',
183+
homepage_url='https://www.ansible.com',
184+
license_url=
185+
'https://raw.githubusercontent.com/ansible/ansible/v2.9.10/COPYING'),
186+
LicenseMetadata(
187+
name='cffi',
188+
homepage_url='http://cffi.readthedocs.org/',
189+
license_glob_pattern=
190+
'/opt/tinypilot-updater/venv/lib/python3.7/site-packages/cffi-*.dist-info/LICENSE'
191+
),
192+
LicenseMetadata(
193+
name='cryptography',
194+
homepage_url='https://cryptography.io',
195+
license_glob_pattern=
196+
'/opt/tinypilot-updater/venv/lib/python3.7/site-packages/cryptography-*.dist-info/LICENSE.BSD'
197+
),
198+
LicenseMetadata(
199+
name='MarkupSafe',
200+
homepage_url='https://palletsprojects.com/p/markupsafe/',
201+
license_glob_pattern=
202+
'/opt/tinypilot-updater/venv/lib/python3.7/site-packages/MarkupSafe-*.dist-info/LICENSE.rst'
203+
),
204+
LicenseMetadata(
205+
name='pycparser',
206+
homepage_url='https://github.com/eliben/pycparser',
207+
license_glob_pattern=
208+
'/opt/tinypilot-updater/venv/lib/python3.7/site-packages/pycparser-*.dist-info/LICENSE'
209+
),
210+
LicenseMetadata(
211+
name='pyOpenSSL',
212+
homepage_url='https://pyopenssl.org',
213+
license_glob_pattern=
214+
'/opt/tinypilot-updater/venv/lib/python3.7/site-packages/pyOpenSSL-*.dist-info/LICENSE'
215+
),
216+
217+
# Indirect dependencies through Janus.
218+
LicenseMetadata(
219+
name='libnice',
220+
homepage_url='https://gitlab.freedesktop.org/libnice/libnice',
221+
license_url=
222+
'https://gitlab.freedesktop.org/libnice/libnice/-/blob/0.1.18/COPYING',
223+
),
224+
LicenseMetadata(
225+
name='libsrtp',
226+
homepage_url='https://github.com/cisco/libsrtp',
227+
license_url='https://github.com/cisco/libsrtp/blob/v2.2.0/LICENSE',
228+
),
229+
LicenseMetadata(
230+
name='libwebsockets',
231+
homepage_url='https://libwebsockets.org',
232+
license_url=
233+
'https://libwebsockets.org/git/libwebsockets/tree/LICENSE?h=v3.2-stable',
234+
),
235+
236+
# Fonts.
237+
LicenseMetadata(
238+
name='Overpass',
239+
homepage_url='https://overpassfont.org/',
240+
license_glob_pattern=
241+
'./app/static/third-party/fonts/Overpass-License.txt',
242+
),
243+
]
244+
245+
blueprint = flask.Blueprint('licensing', __name__, url_prefix='/licensing')
246+
247+
248+
@blueprint.route('', methods=['GET'])
249+
def all_licensing_get():
250+
"""Retrieves licensing metadata for TinyPilot and its dependencies.
251+
252+
Returns:
253+
A JSON-formatted list of licensing information about TinyPilot and its
254+
third-party dependencies.
255+
256+
Example:
257+
[
258+
{
259+
"name": "TinyPilot",
260+
"homepageUrl": "https://tinypilotkvm.com",
261+
"licenseUrl": "/licensing/TinyPilot/license"
262+
},
263+
{
264+
"name": "uStreamer",
265+
"homepageUrl": "https://github.com/pikvm/ustreamer",
266+
"licenseUrl": "/licensing/uStreamer/license"
267+
},
268+
...
269+
]
270+
"""
271+
response = []
272+
for license_data in _LICENSE_METADATA:
273+
response.append({
274+
'name': license_data.name,
275+
'licenseUrl': '/licensing/%s/license' % license_data.name,
276+
'homepageUrl': license_data.homepage_url,
277+
})
278+
279+
return json_response.success(response)
280+
281+
282+
@blueprint.route('/<project>/license', methods=['GET'])
283+
def project_license_get(project):
284+
"""Retrieves the license for a given project.
285+
286+
Args:
287+
project: The name of the project's license to retrieve.
288+
289+
Returns:
290+
A plaintext response with the license in plaintext if the license is
291+
available locally, or a redirect to a public URL if the license is not
292+
available locally.
293+
"""
294+
project_metadata = _get_project_metadata(project)
295+
if not project_metadata:
296+
return flask.make_response('Unknown project', 404)
297+
298+
if project_metadata.license_url:
299+
return _make_redirect_response(project_metadata.license_url)
300+
301+
matches = glob.glob(project_metadata.license_glob_pattern)
302+
if not matches:
303+
return flask.make_response('Project license not found', 404)
304+
305+
license_path = matches[0]
306+
return _make_plaintext_response(_read_file(license_path))
307+
308+
309+
def _get_project_metadata(project_name):
310+
for license_data in _LICENSE_METADATA:
311+
if license_data.name == project_name:
312+
return license_data
313+
return None
314+
315+
316+
def _make_plaintext_response(response_body):
317+
response = flask.make_response(response_body, 200)
318+
response.mimetype = 'text/plain'
319+
return response
320+
321+
322+
def _read_file(license_path):
323+
with open(license_path) as license_file:
324+
return license_file.read()
325+
326+
327+
def _make_redirect_response(license_url):
328+
# We intentionally use a 302 temporary redirect, as the license URL will
329+
# change when software versions change.
330+
return flask.redirect(license_url, code=302)

app/main.py

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import log
1313
import api
1414
import json_response
15+
import licensing
1516
import secret_key
1617
import socket_api
1718
import views
@@ -46,6 +47,7 @@
4647
csrf = flask_wtf.csrf.CSRFProtect(app)
4748

4849
app.register_blueprint(api.api_blueprint)
50+
app.register_blueprint(licensing.blueprint)
4951
app.register_blueprint(views.views_blueprint)
5052

5153

-3.95 KB
Binary file not shown.

app/static/js/app.js

+4
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,10 @@ menuBar.addEventListener("debug-logs-dialog-requested", () => {
337337
document.getElementById("debug-dialog").retrieveLogs();
338338
document.getElementById("debug-overlay").show();
339339
});
340+
menuBar.addEventListener("about-dialog-requested", () => {
341+
document.getElementById("about-dialog").initialize();
342+
document.getElementById("about-overlay").show();
343+
});
340344
menuBar.addEventListener("mass-storage-dialog-requested", () => {
341345
document.getElementById("feature-pro-overlay").show();
342346
});

app/static/js/controllers.js

+9
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,12 @@ export async function applyVideoSettings() {
343343
},
344344
}).then(processJsonResponse);
345345
}
346+
347+
export async function getLicensingMetadata() {
348+
return fetch("/licensing", {
349+
method: "GET",
350+
mode: "same-origin",
351+
cache: "no-cache",
352+
redirect: "error",
353+
}).then(processJsonResponse);
354+
}

0 commit comments

Comments
 (0)