mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-20 02:00:49 +00:00
parent
59cfee3f28
commit
18adbc2397
8 changed files with 91 additions and 16 deletions
|
@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Added support for EventSub Websocket subscriptions on Twitch, making Twitch integration fully in-app without having to rely on third party servers. Check the "Events" tab in Twitch configuration for setting it up. The new keys for redeems are `twitch/ev/eventsub-event` and `twitch/eventsub-history`. History has been reduced to 50 to alleviate memory concerns.
|
- Added support for EventSub Websocket subscriptions on Twitch, making Twitch integration fully in-app without having to rely on third party servers. Check the "Events" tab in Twitch configuration for setting it up. The new keys for redeems are `twitch/ev/eventsub-event` and `twitch/eventsub-history`. History has been reduced to 50 to alleviate memory concerns.
|
||||||
- Application logs are now visible from the UI, check the little floating boxes in the top right!
|
- Application logs are now visible from the UI, check the little floating boxes in the top right!
|
||||||
- A new app icon drawn by [Sonic_chan](https://twitter.com/Sonic__Chan), say hello to Renko, strimertul's mascot!
|
- A new app icon drawn by [Sonic_chan](https://twitter.com/Sonic__Chan), say hello to Renko, strimertul's mascot!
|
||||||
|
- Hidden fields (Client secret, Kilovolt password) now have a "Reveal" toggle to show the hidden value
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
|
@ -261,7 +261,9 @@
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"warning-delete": "This cannot be undone",
|
"warning-delete": "This cannot be undone",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"submit": "Submit"
|
"submit": "Submit",
|
||||||
|
"password-reveal": "Reveal",
|
||||||
|
"password-hide": "Hide"
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"dev-build": "Development build"
|
"dev-build": "Development build"
|
||||||
|
|
23
frontend/src/ui/components/utils/PasswordField.tsx
Normal file
23
frontend/src/ui/components/utils/PasswordField.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export interface PasswordFieldProps {
|
||||||
|
reveal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PasswordField(
|
||||||
|
props: PasswordFieldProps &
|
||||||
|
React.PropsWithChildren<
|
||||||
|
React.DetailedHTMLProps<
|
||||||
|
React.InputHTMLAttributes<HTMLInputElement>,
|
||||||
|
HTMLInputElement
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<input type={props.reveal ? 'text' : 'password'} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</input>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(PasswordField);
|
31
frontend/src/ui/components/utils/RevealLink.tsx
Normal file
31
frontend/src/ui/components/utils/RevealLink.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Button } from '../../theme';
|
||||||
|
|
||||||
|
export interface RevealLinkProps {
|
||||||
|
value: boolean;
|
||||||
|
setter: (newValue: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RevealLink({ value, setter }: RevealLinkProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const text = value
|
||||||
|
? t('form-actions.password-hide')
|
||||||
|
: t('form-actions.password-reveal');
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
css={{ display: 'inline-flex', marginLeft: '0.5rem' }}
|
||||||
|
onClick={() => {
|
||||||
|
if (setter) {
|
||||||
|
setter(!value);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size={'smaller'}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(RevealLink);
|
|
@ -1,8 +1,9 @@
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useModule, useStatus } from '../../lib/react-utils';
|
import { useModule, useStatus } from '../../lib/react-utils';
|
||||||
import { useAppDispatch } from '../../store';
|
import { useAppDispatch } from '../../store';
|
||||||
import apiReducer, { modules } from '../../store/api/reducer';
|
import apiReducer, { modules } from '../../store/api/reducer';
|
||||||
|
import RevealLink from '../components/utils/RevealLink';
|
||||||
import SaveButton from '../components/utils/SaveButton';
|
import SaveButton from '../components/utils/SaveButton';
|
||||||
import {
|
import {
|
||||||
APPNAME,
|
APPNAME,
|
||||||
|
@ -13,6 +14,7 @@ import {
|
||||||
PageContainer,
|
PageContainer,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageTitle,
|
PageTitle,
|
||||||
|
PasswordInputBox,
|
||||||
} from '../theme';
|
} from '../theme';
|
||||||
|
|
||||||
export default function ServerSettingsPage(): React.ReactElement {
|
export default function ServerSettingsPage(): React.ReactElement {
|
||||||
|
@ -24,6 +26,7 @@ export default function ServerSettingsPage(): React.ReactElement {
|
||||||
const status = useStatus(loadStatus.save);
|
const status = useStatus(loadStatus.save);
|
||||||
const busy =
|
const busy =
|
||||||
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
|
loadStatus.load?.type !== 'success' || loadStatus.save?.type === 'pending';
|
||||||
|
const [revealKVPassword, setRevealKVPassword] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
|
@ -59,22 +62,23 @@ export default function ServerSettingsPage(): React.ReactElement {
|
||||||
<Field size="fullWidth">
|
<Field size="fullWidth">
|
||||||
<Label htmlFor="kvpassword">
|
<Label htmlFor="kvpassword">
|
||||||
{t('pages.http.kilovolt-password')}
|
{t('pages.http.kilovolt-password')}
|
||||||
</Label>
|
<RevealLink value={revealKVPassword} setter={setRevealKVPassword} />
|
||||||
<InputBox
|
</Label>{' '}
|
||||||
type="password"
|
<PasswordInputBox
|
||||||
|
reveal={revealKVPassword}
|
||||||
id="kvpassword"
|
id="kvpassword"
|
||||||
placeholder={t('pages.http.kilovolt-placeholder')}
|
placeholder={t('pages.http.kilovolt-placeholder')}
|
||||||
value={serverConfig?.kv_password ?? ''}
|
value={serverConfig?.kv_password ?? ''}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
onChange={(e) =>
|
onChange={(e) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
apiReducer.actions.httpConfigChanged({
|
apiReducer.actions.httpConfigChanged({
|
||||||
...serverConfig,
|
...serverConfig,
|
||||||
kv_password: e.target.value,
|
kv_password: e.target.value,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
}
|
}}
|
||||||
/>
|
/>
|
||||||
<FieldNote>{t('pages.http.kilovolt-placeholder')}</FieldNote>
|
<FieldNote>{t('pages.http.kilovolt-placeholder')}</FieldNote>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { RootState, useAppDispatch } from '../../store';
|
||||||
import apiReducer, { modules } from '../../store/api/reducer';
|
import apiReducer, { modules } from '../../store/api/reducer';
|
||||||
import BrowserLink from '../components/BrowserLink';
|
import BrowserLink from '../components/BrowserLink';
|
||||||
import DefinitionTable from '../components/DefinitionTable';
|
import DefinitionTable from '../components/DefinitionTable';
|
||||||
|
import RevealLink from '../components/utils/RevealLink';
|
||||||
import SaveButton from '../components/utils/SaveButton';
|
import SaveButton from '../components/utils/SaveButton';
|
||||||
import {
|
import {
|
||||||
APPNAME,
|
APPNAME,
|
||||||
|
@ -26,6 +27,7 @@ import {
|
||||||
PageContainer,
|
PageContainer,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageTitle,
|
PageTitle,
|
||||||
|
PasswordInputBox,
|
||||||
SectionHeader,
|
SectionHeader,
|
||||||
styled,
|
styled,
|
||||||
TabButton,
|
TabButton,
|
||||||
|
@ -59,6 +61,7 @@ function TwitchBotSettings() {
|
||||||
const status = useStatus(loadStatus.save);
|
const status = useStatus(loadStatus.save);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [revealBotToken, setRevealBotToken] = useState(false);
|
||||||
const active = twitchConfig?.enable_bot ?? false;
|
const active = twitchConfig?.enable_bot ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -138,9 +141,10 @@ function TwitchBotSettings() {
|
||||||
<Field size="fullWidth">
|
<Field size="fullWidth">
|
||||||
<Label htmlFor="bot-oauth">
|
<Label htmlFor="bot-oauth">
|
||||||
{t('pages.twitch-settings.bot-oauth')}
|
{t('pages.twitch-settings.bot-oauth')}
|
||||||
|
<RevealLink value={revealBotToken} setter={setRevealBotToken} />
|
||||||
</Label>
|
</Label>
|
||||||
<InputBox
|
<PasswordInputBox
|
||||||
type="password"
|
reveal={revealBotToken}
|
||||||
id="bot-oauth"
|
id="bot-oauth"
|
||||||
required={active}
|
required={active}
|
||||||
disabled={!active || status?.type === 'pending'}
|
disabled={!active || status?.type === 'pending'}
|
||||||
|
@ -199,6 +203,7 @@ function TwitchAPISettings() {
|
||||||
);
|
);
|
||||||
const status = useStatus(loadStatus.save);
|
const status = useStatus(loadStatus.save);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
const [revealClientSecret, setRevealClientSecret] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
@ -265,9 +270,13 @@ function TwitchAPISettings() {
|
||||||
<Field size="fullWidth">
|
<Field size="fullWidth">
|
||||||
<Label htmlFor="clientsecret">
|
<Label htmlFor="clientsecret">
|
||||||
{t('pages.twitch-settings.app-client-secret')}
|
{t('pages.twitch-settings.app-client-secret')}
|
||||||
|
<RevealLink
|
||||||
|
value={revealClientSecret}
|
||||||
|
setter={setRevealClientSecret}
|
||||||
|
/>
|
||||||
</Label>
|
</Label>
|
||||||
<InputBox
|
<PasswordInputBox
|
||||||
type="password"
|
reveal={revealClientSecret}
|
||||||
id="clientsecret"
|
id="clientsecret"
|
||||||
placeholder={t('pages.twitch-settings.app-client-secret')}
|
placeholder={t('pages.twitch-settings.app-client-secret')}
|
||||||
required={true}
|
required={true}
|
||||||
|
|
|
@ -2,9 +2,9 @@ import * as UnstyledLabel from '@radix-ui/react-label';
|
||||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
||||||
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
||||||
import { CSS } from '@stitches/react';
|
import { CSS } from '@stitches/react';
|
||||||
import { styled } from './theme';
|
import { styled, theme } from './theme';
|
||||||
import { theme } from '.';
|
|
||||||
import ControlledInput from '../components/utils/ControlledInput';
|
import ControlledInput from '../components/utils/ControlledInput';
|
||||||
|
import PasswordField from '../components/utils/PasswordField';
|
||||||
|
|
||||||
export const Field = styled('fieldset', {
|
export const Field = styled('fieldset', {
|
||||||
all: 'unset',
|
all: 'unset',
|
||||||
|
@ -80,6 +80,7 @@ const inputStyles: CSS = {
|
||||||
|
|
||||||
export const InputBox = styled('input', inputStyles);
|
export const InputBox = styled('input', inputStyles);
|
||||||
export const ControlledInputBox = styled(ControlledInput, inputStyles);
|
export const ControlledInputBox = styled(ControlledInput, inputStyles);
|
||||||
|
export const PasswordInputBox = styled(PasswordField, inputStyles);
|
||||||
|
|
||||||
export const Textarea = styled('textarea', {
|
export const Textarea = styled('textarea', {
|
||||||
all: 'unset',
|
all: 'unset',
|
||||||
|
@ -178,6 +179,11 @@ const button = {
|
||||||
padding: '0.3rem 0.5rem',
|
padding: '0.3rem 0.5rem',
|
||||||
fontSize: '0.9rem',
|
fontSize: '0.9rem',
|
||||||
},
|
},
|
||||||
|
smaller: {
|
||||||
|
padding: '5px',
|
||||||
|
paddingBottom: '3px',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
variation: {
|
variation: {
|
||||||
primary: {
|
primary: {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* eslint-disable import/prefer-default-export */
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
|
||||||
import { theme } from '.';
|
import { styled, theme } from './theme';
|
||||||
import { styled } from './theme';
|
|
||||||
|
|
||||||
export const FlexRow = styled('div', {
|
export const FlexRow = styled('div', {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|
Loading…
Reference in a new issue