mobile styles and info panel

This commit is contained in:
ansible user/allowed to read system logs 2023-08-07 13:52:15 -07:00
parent 48028193cc
commit bcc032413c
20 changed files with 280 additions and 56 deletions

View File

@ -1,26 +1,68 @@
import React from 'react';
import React, {useState} from 'react';
import { createTheme, ThemeProvider } from '@mui/material/styles';
import { yellow } from "@mui/material/colors";
import GitHubIcon from '@mui/icons-material/GitHub';
import ModePanel from "./components/modePanel";
import Button from "@mui/material/Button";
import IconButton from "@mui/material/IconButton";
import InfoModal from "./components/modals/infoModal";
import {Info} from "@mui/icons-material";
const REPO_URL = "https://github.com/sneakers-the-rat/chatbridge"
const theme = createTheme({
palette:{
primary: yellow,
mode: "dark"
mode: "dark",
}
})
import ModePanel from "./components/modePanel";
function App() {
return (
const [infoOpen, setInfoOpen] = useState(false);
const handleInfo = () => {
setInfoOpen(!infoOpen)
}
return (
<ThemeProvider theme={theme}>
<div className={"App-Container"}>
<div className="App">
<header className="App-header">
ChatBridge
</header>
<div className={"App-header-bar"}>
<header className="App-header">
ChatBridge
</header>
<div className={"header-padding"}/>
<Button
className={"App-header-item"}
variant="contained"
aria-label={"source code"}
color={"primary"}
onClick={handleInfo}
>
INFO
</Button>
<Button
variant="contained"
aria-label={"source code"}
color={"primary"}
href={REPO_URL}
>
<GitHubIcon/>
</Button>
</div>
<ModePanel/>
<InfoModal open={infoOpen} setOpen={setInfoOpen}/>
</div>
</div>
</ThemeProvider>

View File

@ -3,9 +3,7 @@ export const getBridgeByStateToken = (callback: CallableFunction) => {
fetch('api/bridge')
.then(res => res.json())
.then((res) => {
console.log('bridge result', res)
if (res.status === 'success'){
console.log('successful get bridge')
callback(res.data)
}
})

View File

@ -22,8 +22,8 @@ export default function GroupPanel({
<TableHead>
<TableRow>
<TableCell>Group</TableCell>
<TableCell align="right">Created At</TableCell>
<TableCell align="right">Invite Token</TableCell>
<TableCell align="right">Created At</TableCell>
<TableCell align="right">Delete</TableCell>
</TableRow>
</TableHead>

View File

@ -32,8 +32,8 @@ export default function GroupRow(
<TableCell component="th" scope="row">
{name}
</TableCell>
<TableCell align="right">{created_at}</TableCell>
<TableCell align="right">{invite_token}</TableCell>
<TableCell align="right">{created_at}</TableCell>
<TableCell align="right">
<IconButton
onClick={handleDeleteGroup}

View File

@ -36,7 +36,7 @@ const JoinChannel = ({
useEffect(() => {
if (bridge){
if (bridge && !channels){
switch(platform){
case "Slack":
getSlackChannels(setChannels)

View File

@ -59,11 +59,11 @@ export const JoinForm = ({group}: JoinFormProps) => {
return (
<>
<header className={'section-header'}>
Joining group: <code>{group.name}</code>
<code>{group.name}</code>
</header>
<JoinStep
title={"1) Login"}
details={"Select your chat platform"}
// details={"Select your chat platform"}
id={'login'}
completed={stepComplete.login}
>
@ -78,7 +78,7 @@ export const JoinForm = ({group}: JoinFormProps) => {
</JoinStep>
<JoinStep
title={"2) Configure Bridge"}
details={"Settings for all channels bridged from this platform"}
// details={"Settings for all channels bridged from this platform"}
id={'bridge'}
disabled={!stepComplete.login}
completed={stepComplete.bridge}
@ -92,7 +92,7 @@ export const JoinForm = ({group}: JoinFormProps) => {
</JoinStep>
<JoinStep
title={"3) Select a channel!"}
details={"The bot will join :)"}
// details={"The bot will join :)"}
id={'channel'}
disabled={!stepComplete.login}
completed={stepComplete.channel}

View File

@ -29,7 +29,6 @@ export const JoinGroup = ({
setAuthError(false);
setErrorText('');
setGroup(response.data)
console.log(response)
}
}

View File

@ -30,18 +30,31 @@ export function JoinStep(
return(
<Accordion
disabled={disabled}
id={id}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1bh-content"
id="panel1bh-header"
>
<Typography sx={{ width: '33%', flexShrink: 0 }}>
<Typography sx={details === '' ?
{
width:'66%',
flexGrow: 1
}
:
{
width:'33%',
flexShrink: 0
}
}>
{ title }
</Typography>
{details !== '' ?
<Typography sx={{ color: 'text.secondary', flexGrow: 1 }}>
{ details }
</Typography>
: undefined}
{
completed ?
<TaskAltIcon color={"success"}/>
@ -50,9 +63,7 @@ export function JoinStep(
}
</AccordionSummary>
<AccordionDetails>
<Typography>
{ children }
</Typography>
</AccordionDetails>
</Accordion>
)

View File

@ -0,0 +1,82 @@
import * as React from "react";
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from "@mui/material/DialogContentText";
export interface InfoModalProps {
open: boolean
setOpen: CallableFunction
}
const InfoModal = ({
open,
setOpen
}: InfoModalProps) => {
const handleClose = () => {
setOpen(false)
}
return(
<Dialog
open={open}
onClose={handleClose}
scroll={'body'}
>
<DialogTitle>
ChatBridge INFO
</DialogTitle>
<DialogContent>
<DialogContentText>
ChatBridge is a tool for making groupchats that span multiple chat protocols and platforms.
It is a web frontend for <a href={"https://github.com/42wim/matterbridge"}>matterbridge</a> and manages the API logins for platforms like Slack and Discord to lower the barriers to
bridging.
<br/><br/>
This is ALPHA software and functionality is not guaranteed! The eventual goal is to make <a href={"https://a.gup.pe/"}>guppe groups</a> for proprietary chat platforms, where anyone can create and join groups with just an invite token.
For now, creating groups is limited to an administrator token until we can be more sure about the security and performance demands of running many matterbridge processes.
</DialogContentText>
<h3>Security & Privacy Notes</h3>
<DialogContentText>
This tool is made by privacy advocates and activists. The developers have no interest in storing or monetizing your information, full stop. We will <span style={{"fontStyle":"italic"}}>never</span> abuse the app tokens created by logging into the ChatBridge App for anything except configuring the underlying matterbridge processes
<br/><br/>
To be as transparent as possible about the operation of the service:
</DialogContentText>
<h4>What is stored</h4>
<DialogContentText>
<ul>
<li>Slack/Discord: App login tokens that are generated for your workspace/server</li>
<li>Limited metadata about your bridged chat, specifically its name and unique ID (usually these are considered public anyway), and the short label you provide to the service</li>
<li>The names of channels you have bridged</li>
</ul>
</DialogContentText>
<h4>What is NOT stored</h4>
<DialogContentText>
<ul>
<li>Message content and metadata of any kind, even in debugging logs</li>
<li>No other metadata about your bridged chat except for that listed above - channel lists in the interface are requested and discarded during the API call</li>
<li>The bot access tokens can NOT access DMs or private channels</li>
</ul>
</DialogContentText>
<h4>Security of Data at Rest</h4>
<DialogContentText>
Matterbridge relies on <code>.toml</code> files that contain the app tokens in plain text. This sets a hard limit on how secure the data can be at rest - ie. encryption at rest is not possible. The tokens that are granted to ChatBridge CAN be used to exfiltrate chat history of public channels for slack and discord if they were to be lost in a data breach. We therfore do NOT recommend you use ChatBridge in a context where the contents of your public channels or the membership in your chatroom becoming public could pose a risk to your members.
<br/><br/>
For the main development instance, the service is run as its own user, and the configuration files are stored such that only that user can read them. For the data to be breached, an attacker would need to compromise a root SSH key.
<br/><br/>
For information accessed through the chatbridge interface, we authenticate using ephemeral signed cookies that expire 24 hours after they are issued.
<br/><br/>
We of course can make no guarantee about security of data at any other instances that deploy ChatBridge.
<br/><br/>
To revoke access to ChatBridge at any time, you can uninstall the app from your slack workspace or discord server - this makes the access keys obsolete and will require them to be reissued if you choose to rejoin ChatBridge.
</DialogContentText>
</DialogContent>
</Dialog>
)
}
export default InfoModal

View File

@ -30,8 +30,8 @@ export default function ModePanel() {
onChange={handleChange}
selectionFollowsFocus>
{/*<TabsList className={"TabsList"}>*/}
<Tab label={"Join Group"}></Tab>
<Tab label={"Create Group"}></Tab>
<Tab className={"Tab"} label={"Join Group"}></Tab>
<Tab className={"Tab"} label={"Create Group"}></Tab>
{/*</TabsList>*/}
</StyledTabs>

View File

@ -14,7 +14,8 @@ export default function TabPanel(props) {
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Box
className={"TabPanel"}>
{children}
</Box>
)}

View File

@ -11,6 +11,8 @@
//}
border: 2px solid $color-primary;
height:100%;
box-sizing: border-box;
overflow-y: auto;
}
.App-Container {
@ -23,6 +25,11 @@
width:100%;
height:100%;
box-sizing: border-box;
@media (max-width: $breakpoint-mobile) {
padding: 0;
box-sizing: border-box;
}
}
@ -37,17 +44,49 @@
}
}
.App-header {
text-align: center;
margin: {
top: 1em;
}
.App-header-bar{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
flex-direction: row;
padding: 0.5em 0.5em;
gap: 0.5em;
font: {
family: "Courier", monospace;
}
background: repeating-linear-gradient(
45deg,
$color-primary,
$color-primary 20px,
$color-background 20px,
$color-background 40px
);
.header-padding {
flex-grow: 1;
}
.App-header-item {
font: {
family: "Courier", monospace;
}
}
}
.App-header {
font: {
size: calc(10px + 2vmin);
weight: bold;
}
margin: auto;
color: $color-background;
background-color: $color-primary;
padding: 0.25em;
text-align: left;
//border-radius: 4px;
//box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2),
// 0px 4px 5px 0px rgba(0,0,0,0.14),
// 0px 1px 10px 0px rgba(0,0,0,0.12);
}
.App-link {

View File

@ -18,3 +18,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
//@media (max-width: $breakpoint-mobile){
// .MuiBox-root {
// padding: 6px !important;
// }
//}

View File

@ -4,6 +4,10 @@
align-items: flex-start;
gap: 2em;
@media (max-width: $breakpoint-mobile) {
gap: 0.5em;
}
&>div {
flex-basis: 50%;
}
@ -39,7 +43,9 @@
gap: 2em;
align-items: flex-start;
@media (max-width: $breakpoint-mobile){
gap: 0.5em;
}
.Input {
flex-basis: 70%;

View File

@ -1,7 +1,10 @@
.ModePanel {
max-width: 80%;
//max-width: 80%;
margin: auto;
font: {
family: "Courier", monospace;
}
.TabsList {
min-width: 400px;
@ -19,22 +22,7 @@
justify-content: center;
align-content: space-between;
.Tab {
font-family: 'IBM Plex Sans', sans-serif;
color: white;
cursor: pointer;
font-size: 1.5rem;
font-weight: bold;
background-color: transparent;
width: 100%;
line-height: 1.5;
padding: 8px 12px;
margin: 6px;
border-radius: 8px;
display: flex;
justify-content: center;
border: none;
}
.Tab.Mui-selected {
background-color: $color-primary;
@ -42,4 +30,32 @@
}
}
.Tab {
//font-size: 1000px;
font: {
family: 'Courier', monospace;
size: 1.2em;
}
color: white;
cursor: pointer;
//font-size: 1.5rem;
//font-weight: bold;
background-color: transparent;
width: 100%;
line-height: 1.5;
padding: 8px 12px;
margin: 6px;
border-radius: 8px;
display: flex;
justify-content: center;
border: none;
}
}
.TabPanel {
padding: 24px;
@media (max-width: $breakpoint-mobile){
padding: 12px 12px;
}
}

View File

@ -10,6 +10,16 @@
}
}
p a {
background-color: $color-primary;
color: $color-background;
padding: 0.2em;
font: {
family: "Courier", monospace;
}
border-radius: 3px;
}
code {
font: {
family: 'Courier', 'Courier New', monospace;

View File

@ -1,7 +1,9 @@
$color-primary: #DED03A;
$color-primary: #ffeb3b;
$color-primary-darker: scale-color($color-primary, $lightness: -10%);
$color-primary-background: adjust-color($color-primary, $alpha: -0.9, $saturation: -50%);
$color-primary-border: adjust-color($color-primary, $alpha: -0.9, $lightness: 50%, $saturation: -50%);
$color-secondary: #A167A5;
$color-background: #1C1B22;
$color-text: white;
$color-text: white;
$breakpoint-mobile: 600px;

10
client/src/types.d.ts vendored
View File

@ -3,3 +3,13 @@ declare module "*.svg" {
const content: any;
export default content;
}
// declare module '@mui/material/styles' {
// interface Palette {
// paleYellow: Palette['primary'];
// }
//
// interface PaletteOptions {
// paleYellow?: PaletteOptions['primary'];
// }
// }

View File

@ -15,7 +15,7 @@ const scopes = ['bot', 'channels:write', 'channels:write.invites', 'chat:write:b
const slackBridgeRepository = AppDataSource.getRepository(SlackBridge)
const groupRepository = AppDataSource.getRepository(Group)
const SLACK_COOKIE_NAME = "slack-oauth-state";
// const SLACK_COOKIE_NAME = "slack-oauth-state";
const slackConfig = config.get<slackConfigType>('slackConfig');
@ -45,7 +45,7 @@ export const SlackInstallLinkHandler = async(
state_token
);
res.cookie(SLACK_COOKIE_NAME, state_token, { maxAge: 60*5 })
// res.cookie(SLACK_COOKIE_NAME, state_token, { maxAge: 60*5 })
res.status(200).json({
status: 'success',
data: {
@ -78,7 +78,9 @@ export const SlackCallbackHandler = async(
// check if we have an entity
let bridge = await slackBridgeRepository.findOneBy({Token: installation.bot.token})
logger.debug('found existing bridge %s', bridge)
if (!bridge){
logger.debug('creating new bridge')
bridge = await slackBridgeRepository.create(bridge_data);
await slackBridgeRepository.save(bridge);
logger.debug('created bridge')

View File

@ -19,7 +19,7 @@ export const cookieMiddleware = cookieSession({
name: 'session',
keys: [cookieConfig.key1, cookieConfig.key2],
signed: true,
maxAge: 24*60*60*1000 // 24 hours
})
export const requireAdmin = (req: Request, res: Response, next: NextFunction) => {