Skip to content

Commit 218d6d3

Browse files
authored
Sync Community with Pro (#1914)
Resolves tiny-pilot/tinypilot-pro#1620 This PR backports "forgotten" code from Pro to reduce the diff between the two repos. This is a non-functional change. Notes: 1. Some changes were purposefully not backported (yet): - tiny-pilot/tinypilot-pro#1516 2. Added the Networking->Tailscale "teaser" menu item: <img width="548" height="379" alt="Screenshot 2025-09-18 at 16 23 38" src="https://github.com/user-attachments/assets/391afd23-f677-49ec-b7e1-39bd9b1be73d" /> 3. Added disk usage to the debug logs: <img width="310" height="307" alt="Screenshot 2025-09-18 at 16 25 12" src="https://github.com/user-attachments/assets/654824d7-12a1-44e4-98f5-26a2a3888eef" /> <a data-ca-tag href="https://codeapprove.com/pr/tiny-pilot/tinypilot/1914"><img src="https://codeapprove.com/external/github-tag-allbg.png" alt="Review on CodeApprove" /></a>
1 parent 71b153b commit 218d6d3

File tree

20 files changed

+176
-91
lines changed

20 files changed

+176
-91
lines changed

.circleci/config.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ executors:
33
ubuntu:
44
docker:
55
- image: cimg/base:2024.02
6+
ubuntu-machine:
7+
machine:
8+
image: ubuntu-2204:2024.01.2
69
python:
710
docker:
811
- image: cimg/python:3.9.17
@@ -42,6 +45,9 @@ jobs:
4245
root: ./dev-scripts
4346
paths:
4447
- ./build-timestamp.txt
48+
##############################################################################
49+
# Test and build
50+
##############################################################################
4551
check_whitespace:
4652
executor: ubuntu
4753
steps:
@@ -53,7 +59,8 @@ jobs:
5359
name: Check that all text files end in a trailing newline
5460
command: ./dev-scripts/check-trailing-newline
5561
check_bash:
56-
executor: ubuntu
62+
# Use machine executor to allow for mounting a filesystem.
63+
executor: ubuntu-machine
5764
steps:
5865
- checkout
5966
- run:
@@ -78,7 +85,7 @@ jobs:
7885
shellcheck --version
7986
- run:
8087
name: Run tests and static analysis on bash scripts
81-
command: ./dev-scripts/check-bash
88+
command: sudo ./dev-scripts/check-bash
8289
check_privilege_guard:
8390
executor: ubuntu
8491
steps:

app/db/licenses.py

Lines changed: 0 additions & 23 deletions
This file was deleted.

app/db/wake_on_lan.py

Lines changed: 0 additions & 26 deletions
This file was deleted.

app/env.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@
1414
os.environ.get('TINYPILOT_HOME_DIR', '/home/tinypilot'))
1515

1616

17+
class Error(Exception):
18+
pass
19+
20+
21+
class PathNotRelativeToHomeDirectoryError(Error):
22+
pass
23+
24+
1725
def abs_path_in_home_dir(relative_path):
1826
"""Resolves the full, absolute path for an object in the tinypilot home dir.
1927
@@ -32,8 +40,9 @@ def abs_path_in_home_dir(relative_path):
3240
home dir, without leading slash (as string).
3341
3442
Raises:
35-
ValueError if input path has leading slash (i.e., is absolute), or if
36-
resolved path would be outside tinypilot home dir.
43+
ValueError if input path has leading slash (i.e., is absolute).
44+
PathNotRelativeToHomeDirectoryError if resolved path would be outside
45+
tinypilot home dir.
3746
3847
Returns:
3948
The eventual, absolute path (as string).
@@ -42,5 +51,6 @@ def abs_path_in_home_dir(relative_path):
4251
raise ValueError('Input path must not start with slash.')
4352
target = _TINYPILOT_HOME_PATH.joinpath(relative_path).resolve()
4453
if not target.is_relative_to(_TINYPILOT_HOME_PATH):
45-
raise ValueError('Resolved path must be inside tinypilot home dir.')
54+
raise PathNotRelativeToHomeDirectoryError(
55+
'Resolved path must be inside tinypilot home dir.')
4656
return str(target)

app/env_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ def test_rejects_input_with_leading_slash(self):
3333
env.abs_path_in_home_dir('/foo')
3434

3535
def test_rejects_path_traversal_outside_home_dir(self):
36-
with self.assertRaises(ValueError):
36+
with self.assertRaises(env.PathNotRelativeToHomeDirectoryError):
3737
env.abs_path_in_home_dir('../foo')
3838

39-
with self.assertRaises(ValueError):
39+
with self.assertRaises(env.PathNotRelativeToHomeDirectoryError):
4040
env.abs_path_in_home_dir('foo/../../bar')

app/request_parsers/mouse_event.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ class Error(Exception):
55
pass
66

77

8-
class MissingFieldErrorError(Error):
8+
class MalformedMessageError(Error):
9+
pass
10+
11+
12+
class MissingFieldError(Error):
913
pass
1014

1115

@@ -46,16 +50,38 @@ class MouseEvent:
4650

4751

4852
def parse_mouse_event(message):
53+
"""Parses a mouse event from a JSON dictionary.
54+
55+
Args:
56+
message: A JSON dictionary with the following fields:
57+
(int) buttons
58+
(float) relativeX
59+
(float) relativeY
60+
(int) verticalWheelDelta
61+
(int) horizontalWheelDelta
62+
63+
Returns:
64+
A MouseEvent object.
65+
66+
Raises:
67+
MalformedMessageError: If the request payload is malformed.
68+
MissingFieldError: If a required field is missing from the JSON payload.
69+
InvalidButtonStateError: If the `buttons` field has an invalid value.
70+
InvalidRelativePositionError: If the `relativeX` or `relativeY` fields
71+
has an invalid value.
72+
InvalidWheelValueError: If the `verticalWheelDelta` or
73+
`horizontalWheelDelta` fields has an invalid value.
74+
"""
4975
if not isinstance(message, dict):
50-
raise MissingFieldErrorError(
51-
'Mouse event parameter is invalid, expecting a dictionary data type'
52-
)
76+
raise MalformedMessageError(
77+
'Message is invalid, expecting a JSON dictionary')
78+
5379
required_fields = ('buttons', 'relativeX', 'relativeY',
5480
'verticalWheelDelta', 'horizontalWheelDelta')
5581
for field in required_fields:
5682
if field not in message:
57-
raise MissingFieldErrorError(
58-
f'Mouse event request is missing required field: {field}')
83+
raise MissingFieldError(f'Missing required field: {field}')
84+
5985
return MouseEvent(
6086
buttons=_parse_button_state(message['buttons']),
6187
relative_x=_parse_relative_position(message['relativeX']),

app/request_parsers/mouse_event_test.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,12 @@ def test_rejects_non_float_relative_y_value(self):
200200
'horizontalWheelDelta': 0,
201201
})
202202

203+
def test_rejects_malformed_message(self):
204+
with self.assertRaises(mouse_event.MalformedMessageError):
205+
mouse_event.parse_mouse_event('Malformed message')
206+
203207
def test_rejects_missing_buttons_field(self):
204-
with self.assertRaises(mouse_event.MissingFieldErrorError):
208+
with self.assertRaises(mouse_event.MissingFieldError):
205209
mouse_event.parse_mouse_event({
206210
'relativeX': 0,
207211
'relativeY': 0,
@@ -210,7 +214,7 @@ def test_rejects_missing_buttons_field(self):
210214
})
211215

212216
def test_rejects_missing_relative_x_field(self):
213-
with self.assertRaises(mouse_event.MissingFieldErrorError):
217+
with self.assertRaises(mouse_event.MissingFieldError):
214218
mouse_event.parse_mouse_event({
215219
'buttons': 0,
216220
'relativeY': 0,
@@ -219,7 +223,7 @@ def test_rejects_missing_relative_x_field(self):
219223
})
220224

221225
def test_rejects_missing_relative_y_field(self):
222-
with self.assertRaises(mouse_event.MissingFieldErrorError):
226+
with self.assertRaises(mouse_event.MissingFieldError):
223227
mouse_event.parse_mouse_event({
224228
'buttons': 0,
225229
'relativeX': 0,
@@ -228,15 +232,15 @@ def test_rejects_missing_relative_y_field(self):
228232
})
229233

230234
def test_rejects_missing_vertical_wheel_field(self):
231-
with self.assertRaises(mouse_event.MissingFieldErrorError):
235+
with self.assertRaises(mouse_event.MissingFieldError):
232236
mouse_event.parse_mouse_event({
233237
'buttons': 0,
234238
'relativeX': 0,
235239
'horizontalWheelDelta': 0,
236240
})
237241

238242
def test_rejects_missing_horizontal_wheel_field(self):
239-
with self.assertRaises(mouse_event.MissingFieldErrorError):
243+
with self.assertRaises(mouse_event.MissingFieldError):
240244
mouse_event.parse_mouse_event({
241245
'buttons': 0,
242246
'relativeX': 0,

app/static/js/app.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,6 @@ menuBar.addEventListener("shutdown-dialog-requested", () => {
319319
});
320320
menuBar.addEventListener("update-dialog-requested", () => {
321321
document.getElementById("update-overlay").show();
322-
document.getElementById("update-dialog").checkVersion();
323322
});
324323
menuBar.addEventListener("change-hostname-dialog-requested", () => {
325324
document.getElementById("change-hostname-overlay").show();
@@ -342,17 +341,20 @@ menuBar.addEventListener("about-dialog-requested", () => {
342341
menuBar.addEventListener("mass-storage-dialog-requested", () => {
343342
document.getElementById("feature-pro-overlay").show();
344343
});
344+
menuBar.addEventListener("video-settings-dialog-requested", () => {
345+
document.getElementById("video-settings-overlay").show();
346+
});
347+
menuBar.addEventListener("paste-dialog-requested", () => {
348+
document.getElementById("paste-overlay").show();
349+
});
345350
menuBar.addEventListener("wake-on-lan-dialog-requested", () => {
346351
document.getElementById("feature-pro-overlay").show();
347352
});
348353
menuBar.addEventListener("static-ip-dialog-requested", () => {
349354
document.getElementById("feature-pro-overlay").show();
350355
});
351-
menuBar.addEventListener("video-settings-dialog-requested", () => {
352-
document.getElementById("video-settings-overlay").show();
353-
});
354-
menuBar.addEventListener("paste-dialog-requested", () => {
355-
document.getElementById("paste-overlay").show();
356+
menuBar.addEventListener("tailscale-dialog-requested", () => {
357+
document.getElementById("feature-pro-overlay").show();
356358
});
357359
menuBar.addEventListener("ssh-dialog-requested", () => {
358360
document.getElementById("feature-pro-overlay").show();

app/static/js/controllers.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,12 +409,20 @@ export async function disableWifi() {
409409
.then(() => true);
410410
}
411411

412-
export async function checkStatus(baseURL = "") {
412+
/**
413+
* Checks the status of the TinyPilot server.
414+
* @param {string} [baseURL] - The URL origin of the TinyPilot server.
415+
* @param {AbortSignal} [signal] - The signal that can be used to abort the
416+
* asynchronous request.
417+
* @returns {Promise<Response>} https://developer.mozilla.org/en-US/docs/Web/API/Response
418+
*/
419+
export async function checkStatus(baseURL = "", signal) {
413420
return fetch(baseURL + "/api/status", {
414421
method: "GET",
415422
mode: "cors",
416423
cache: "no-cache",
417424
redirect: "error",
425+
signal,
418426
})
419427
.then(processJsonResponse)
420428
.then(() => true);

app/static/js/hostname.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ describe("determineFutureOrigin", () => {
5454
),
5555
"https://new-tinypilot"
5656
);
57+
assert.strictEqual(
58+
determineFutureOrigin(
59+
new URL("https://10.0.0.123/"),
60+
undefined,
61+
"new-tinypilot"
62+
),
63+
"https://new-tinypilot"
64+
);
5765
});
5866
it("maintains port number", () => {
5967
assert.strictEqual(

0 commit comments

Comments
 (0)