Skip to content

Commit 01d2f5a

Browse files
authored
Implement Sentry error logging for GUI (#1153)
2 parents a06161b + 07542aa commit 01d2f5a

27 files changed

+662
-119
lines changed

.github/workflows/build-gui.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,15 @@ jobs:
7979
node-version-file: '.node-version'
8080
cache: 'pnpm'
8181

82+
- name: Install dependencies
83+
shell: bash
84+
run: pnpm i
85+
8286
- name: Build
8387
shell: bash
84-
run: |
85-
pnpm i
86-
pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
88+
env:
89+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
90+
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
8791

8892
- if: matrix.os == 'windows-latest'
8993
name: Upload a Build Artifact (Windows)

.github/workflows/gradle.yaml

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,13 @@ jobs:
112112
node-version-file: '.node-version'
113113
cache: 'pnpm'
114114

115-
- name: Build GUI
116-
run: |
117-
pnpm i
118-
cd gui && pnpm run build
115+
- name: Install dependencies
116+
run: pnpm i
117+
118+
- name: Build
119+
env:
120+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
121+
run: cd gui && pnpm run build
119122

120123
- name: Build with Gradle
121124
run: ./gradlew :server:android:assembleDebug
@@ -191,10 +194,13 @@ jobs:
191194
node-version-file: '.node-version'
192195
cache: 'pnpm'
193196

197+
- name: Install dependencies
198+
run: pnpm i
199+
194200
- name: Build
195-
run: |
196-
pnpm i
197-
pnpm run tauri build --config $( ./gui/scripts/gitversion.mjs )
201+
env:
202+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
203+
run: pnpm run tauri build --config $( ./gui/scripts/gitversion.mjs )
198204

199205
- name: Make GUI tarball
200206
run: |
@@ -266,11 +272,15 @@ jobs:
266272
node-version-file: '.node-version'
267273
cache: 'pnpm'
268274

269-
- name: Build
275+
- name: Install dependencies
270276
run: |
271277
rustup target add x86_64-apple-darwin
272278
pnpm i
273-
pnpm run tauri build --target universal-apple-darwin --config $( ./gui/scripts/gitversion.mjs )
279+
280+
- name: Build
281+
env:
282+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
283+
run: pnpm run tauri build --target universal-apple-darwin --config $( ./gui/scripts/gitversion.mjs )
274284

275285
- name: Modify Application
276286
run: |
@@ -338,11 +348,15 @@ jobs:
338348
node-version-file: '.node-version'
339349
cache: 'pnpm'
340350

351+
- name: Install dependencies
352+
shell: bash
353+
run: pnpm i
354+
341355
- name: Build
342356
shell: bash
343-
run: |
344-
pnpm i
345-
pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
357+
env:
358+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
359+
run: pnpm run skipbundler --config $( ./gui/scripts/gitversion.mjs )
346360

347361
- name: Bundle to zips
348362
shell: bash

gui/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ vite.config.ts.timestamp*
3232

3333
# eslint
3434
.eslintcache
35+
36+
# Sentry Config File
37+
.env.sentry-build-plugin

gui/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"@hookform/resolvers": "^3.6.0",
1212
"@react-three/drei": "^9.114.3",
1313
"@react-three/fiber": "^8.17.10",
14+
"@sentry/react": "^9.9.0",
15+
"@sentry/vite-plugin": "^2.22.7",
1416
"@tailwindcss/typography": "^0.5.15",
1517
"@tanstack/react-query": "^5.48.0",
1618
"@tauri-apps/api": "^2.0.2",
@@ -91,7 +93,7 @@
9193
"spdx-satisfies": "^5.0.1",
9294
"tailwind-gradient-mask-image": "^1.2.0",
9395
"tailwindcss": "^3.4.13",
94-
"vite": "^5.4.8",
95-
"typescript-eslint": "^8.8.0"
96+
"typescript-eslint": "^8.8.0",
97+
"vite": "^5.4.8"
9698
}
97-
}
99+
}

gui/public/i18n/en/translation.ftl

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ settings-sidebar-utils = Utilities
359359
settings-sidebar-serial = Serial console
360360
settings-sidebar-appearance = Appearance
361361
settings-sidebar-notifications = Notifications
362+
settings-sidebar-behavior = Behavior
362363
settings-sidebar-firmware-tool = DIY Firmware Tool
363364
settings-sidebar-advanced = Advanced
364365
@@ -531,9 +532,6 @@ settings-general-gesture_control-numberTrackersOverThreshold-description = Incre
531532
532533
## Appearance settings
533534
settings-interface-appearance = Appearance
534-
settings-general-interface-dev_mode = Developer Mode
535-
settings-general-interface-dev_mode-description = This mode can be useful if you need in-depth data or to interact with connected trackers on a more advanced level.
536-
settings-general-interface-dev_mode-label = Developer Mode
537535
settings-general-interface-theme = Color theme
538536
settings-general-interface-show-navbar-onboarding = Show "{ navbar-onboarding }" on navigation bar
539537
settings-general-interface-show-navbar-onboarding-description = This changes if the "{ navbar-onboarding }" button shows on the navigation bar.
@@ -565,6 +563,12 @@ settings-general-interface-feedback_sound-volume = Feedback sound volume
565563
settings-general-interface-connected_trackers_warning = Connected trackers warning
566564
settings-general-interface-connected_trackers_warning-description = This option will show a pop-up every time you try exiting SlimeVR while having one or more connected trackers. It reminds you to turn off your trackers when you are done to preserve battery life.
567565
settings-general-interface-connected_trackers_warning-label = Connected trackers warning on exit
566+
567+
## Behavior settings
568+
settings-interface-behavior = Behavior
569+
settings-general-interface-dev_mode = Developer Mode
570+
settings-general-interface-dev_mode-description = This mode can be useful if you need in-depth data or to interact with connected trackers on a more advanced level.
571+
settings-general-interface-dev_mode-label = Developer Mode
568572
settings-general-interface-use_tray = Minimize to system tray
569573
settings-general-interface-use_tray-description = Lets you close the window without closing the SlimeVR Server so you can continue using it without having the GUI bothering you.
570574
settings-general-interface-use_tray-label = Minimize to system tray
@@ -576,6 +580,15 @@ settings-general-interface-discord_presence-message = { $amount ->
576580
[one] Using 1 tracker
577581
*[other] Using { $amount } trackers
578582
}
583+
settings-interface-behavior-error_tracking = Error collection via Sentry.io
584+
settings-interface-behavior-error_tracking-description =
585+
To provide the best user experience, we collect anonymized error reports, performance metrics, and operating system information. This helps us detect bugs and issues with SlimeVR. These metrics are collected via Sentry.io.
586+
587+
<b>We do not collect personal information</b> such as your IP address or wireless credentials. SlimeVR values your privacy!
588+
589+
Do you consent to the collection of anonymized error data?
590+
591+
settings-interface-behavior-error_tracking-label = Send errors to developers
579592
580593
## Serial settings
581594
settings-serial = Serial Console
@@ -1291,3 +1304,11 @@ unknown_device-modal-description = There is a new tracker with MAC address <b>{$
12911304
Do you want to connect it to SlimeVR?
12921305
unknown_device-modal-confirm = Sure!
12931306
unknown_device-modal-forget = Ignore it
1307+
1308+
## Error collection consent modal
1309+
error_collection_modal-title = Can we collect errors?
1310+
error_collection_modal-description = { settings-interface-behavior-error_tracking-description }
1311+
1312+
You can change this setting later in the Behavior section of the settings page.
1313+
error_collection_modal-confirm = I agree
1314+
error_collection_modal-cancel = I don't want to

gui/src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { AppLayout } from './AppLayout';
5454
import { Preload } from './components/Preload';
5555
import { UnknownDeviceModal } from './components/UnknownDeviceModal';
5656
import { useDiscordPresence } from './hooks/discord-presence';
57+
import { withSentryReactRouterV6Routing } from '@sentry/react';
5758
import { ScaledProportionsPage } from './components/onboarding/pages/body-proportions/ScaledProportions';
5859
import { AdvancedSettings } from './components/settings/pages/AdvancedSettings';
5960
import { FirmwareUpdate } from './components/firmware-update/FirmwareUpdate';
@@ -64,6 +65,8 @@ export const VersionContext = createContext('');
6465
export const DOCS_SITE = 'https://docs.slimevr.dev';
6566
export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr';
6667

68+
const SentryRoutes = withSentryReactRouterV6Routing(Routes);
69+
6770
function Layout() {
6871
const { isMobile } = useBreakpoint('mobile');
6972
useDiscordPresence();
@@ -73,7 +76,7 @@ function Layout() {
7376
<SerialDetectionModal></SerialDetectionModal>
7477
<VersionUpdateModal></VersionUpdateModal>
7578
<UnknownDeviceModal></UnknownDeviceModal>
76-
<Routes>
79+
<SentryRoutes>
7780
<Route element={<AppLayout />}>
7881
<Route
7982
path="/"
@@ -165,7 +168,7 @@ function Layout() {
165168
</Route>
166169
<Route path="*" element={<TopBar></TopBar>}></Route>
167170
</Route>
168-
</Routes>
171+
</SentryRoutes>
169172
</>
170173
);
171174
}

gui/src/AppLayout.tsx

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import { useLayoutEffect } from 'react';
22
import { useConfig } from './hooks/config';
3-
import { Outlet, useNavigate } from 'react-router-dom';
3+
import { Outlet } from 'react-router-dom';
44

55
export function AppLayout() {
6-
const { loading, config } = useConfig();
7-
const navigate = useNavigate();
6+
const { config } = useConfig();
87

98
useLayoutEffect(() => {
10-
if (loading || !config) return;
9+
if (!config) return;
1110
if (config.theme !== undefined) {
1211
document.documentElement.dataset.theme = config.theme;
1312
}
@@ -25,23 +24,7 @@ export function AppLayout() {
2524
`${config.textSize}rem`
2625
);
2726
}
28-
}, [config, loading]);
29-
30-
useLayoutEffect(() => {
31-
if (config && !config.doneOnboarding) {
32-
navigate('/onboarding/home');
33-
}
34-
}, [config?.doneOnboarding]);
35-
36-
// const location = useLocation();
37-
// const navigationType = useNavigationType();
38-
// useEffect(() => {
39-
// if (import.meta.env.PROD) return;
40-
// console.log('The current URL is', { ...location });
41-
// console.log('The last navigation action was', navigationType);
42-
// }, [location, navigationType]);
43-
44-
if (loading) return <></>;
27+
}, [config]);
4528

4629
return (
4730
<>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Localized, useLocalization } from '@fluent/react';
2+
import { BaseModal } from './commons/BaseModal';
3+
import { Button } from './commons/Button';
4+
import { Typography } from './commons/Typography';
5+
6+
export function ErrorConsentModal({
7+
isOpen = true,
8+
cancel,
9+
accept,
10+
}: {
11+
/**
12+
* Is the parent/sibling component opened?
13+
*/
14+
isOpen: boolean;
15+
/**
16+
* Function to trigger when you still want to close the app
17+
*/
18+
accept: () => void;
19+
/**
20+
* Function to trigger when cancelling app close
21+
*/
22+
cancel?: () => void;
23+
}) {
24+
const { l10n } = useLocalization();
25+
26+
return (
27+
<BaseModal isOpen={isOpen} onRequestClose={cancel} closeable={false}>
28+
<div className="flex flex-col gap-3">
29+
<>
30+
<div className="flex flex-col items-center gap-3 fill-accent-background-20">
31+
<div className="flex flex-col items-center gap-2 max-w-[512px]">
32+
<Typography variant="main-title">
33+
{l10n.getString('error_collection_modal-title')}
34+
</Typography>
35+
<Localized
36+
id={'error_collection_modal-description'}
37+
elems={{ b: <b></b> }}
38+
>
39+
<Typography
40+
variant="standard"
41+
whitespace="whitespace-pre-line"
42+
/>
43+
</Localized>
44+
</div>
45+
</div>
46+
47+
<Button variant="primary" onClick={accept}>
48+
{l10n.getString('error_collection_modal-confirm')}
49+
</Button>
50+
<Button variant="tertiary" onClick={cancel}>
51+
{l10n.getString('error_collection_modal-cancel')}
52+
</Button>
53+
</>
54+
</div>
55+
</BaseModal>
56+
);
57+
}

gui/src/components/SerialDetectionModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export function SerialDetectionModal() {
110110
{l10n.getString('serial_detection-new_device-p1')}
111111
</Typography>
112112
</div>
113-
<div className="flex flex-col gap-3 rounded-xl max-w-sm">
113+
<div className="flex flex-col gap-3 rounded-xl max-w-sm sentry-mask">
114114
<Localized
115115
id="onboarding-wifi_creds-ssid"
116116
attrs={{ placeholder: true, label: true }}

gui/src/components/TopBar.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { TrayOrExitModal } from './TrayOrExitModal';
2929
import { error } from '@/utils/logging';
3030
import { useDoubleTap } from 'use-double-tap';
3131
import { isTrayAvailable } from '@/utils/tauri';
32+
import { ErrorConsentModal } from './ErrorConsentModal';
3233
import {
3334
CloseRequestedEvent,
3435
getCurrentWindow,
@@ -333,6 +334,11 @@ export function TopBar({
333334
getCurrentWindow().requestUserAttention(null);
334335
}}
335336
></TrackersStillOnModal>
337+
<ErrorConsentModal
338+
isOpen={config?.errorTracking === null}
339+
accept={() => setConfig({ errorTracking: true })}
340+
cancel={() => setConfig({ errorTracking: false })}
341+
/>
336342
</>
337343
);
338344
}

gui/src/components/commons/BaseModal.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import ReactModal from 'react-modal';
55
export function BaseModal({
66
children,
77
important = false,
8+
closeable = true,
89
...props
910
}: {
1011
isOpen: boolean;
1112
children: ReactNode;
1213
important?: boolean;
14+
closeable?: boolean;
1315
} & ReactModal.Props) {
1416
return (
1517
<ReactModal
1618
{...props}
17-
shouldCloseOnOverlayClick
18-
shouldCloseOnEsc
19+
shouldCloseOnOverlayClick={closeable}
20+
shouldCloseOnEsc={closeable}
1921
overlayClassName={
2022
props.overlayClassName ||
2123
classNames(

gui/src/components/commons/Input.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,9 @@ export const InputInside = forwardRef<
8787
<div className="relative w-full">
8888
<input
8989
type={forceText ? 'text' : type}
90-
className={classNames(classes, { 'pr-10': type === 'password' })}
90+
className={classNames(classes, {
91+
'pr-10 sentry-mask': type === 'password',
92+
})}
9193
placeholder={placeholder || undefined}
9294
autoComplete={autocomplete ? 'off' : 'on'}
9395
onChange={onChange}

gui/src/components/commons/Typography.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function Typography({
1111
italic = false,
1212
truncate = false,
1313
textAlign,
14+
sentryMask = false,
1415
}: {
1516
variant?:
1617
| 'main-title'
@@ -37,6 +38,7 @@ export function Typography({
3738
| 'text-start'
3839
| 'text-end';
3940
children?: ReactNode;
41+
sentryMask?: boolean;
4042
}) {
4143
const tag = useMemo(() => {
4244
const tags = {
@@ -72,6 +74,7 @@ export function Typography({
7274
truncate && 'leading-3 text-ellipsis',
7375
truncate && (config?.textSize ?? 12) > 12 && 'line-clamp-1',
7476
truncate && (config?.textSize ?? 12) <= 12 && 'line-clamp-2',
77+
sentryMask && 'sentry-mask',
7578
]),
7679
},
7780
children || []

0 commit comments

Comments
 (0)