Merge pull request #233 from openreplay/dev

v1.3.6 frontend fixes
This commit is contained in:
Mehdi Osman 2021-12-04 21:51:13 +01:00 committed by GitHub
commit 3d4df4a14a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 93 additions and 53 deletions

View file

@ -108,8 +108,8 @@ class Notifications extends React.Component {
content={
<div className="">
<NoContent
title="No Alerts!"
subtext="There are no alerts."
title=""
subtext="There are no alerts to show."
icon="exclamation-circle"
show={ !loading && notifications.size === 0 }
size="small"

View file

@ -73,7 +73,7 @@ class Announcements extends React.Component {
content={
<div className="mx-4">
<NoContent
title="No Announcements found!"
title=""
subtext="There are no announcements to show."
icon="exclamation-circle"
show={ !loading && announcements.size === 0 }

View file

@ -101,7 +101,7 @@ function AssistActions({ toggleChatWindow, userId, calling, peerConnectionStatus
<span className={cn("ml-2", { 'color-red' : inCall })}>{ inCall ? 'End Call' : 'Call' }</span>
</div>
}
content={ `Call ${userId ? userId : 'User'}` }
content={ cannotCall ? "You dont have the permissions to perform this action." : `Call ${userId ? userId : 'User'}` }
size="tiny"
inverted
position="top right"

View file

@ -130,7 +130,8 @@ export default class EventFilter extends React.PureComponent {
onClick={ this.onPlaceholderClick }
>
{ !searchQuery &&
<RandomPlaceholder onClick={ this.onPlaceholderItemClick } appliedFilterKeys={ appliedFilterKeys } />
<div className={ stl.placeholder }>Search for users, clicks, page visits, requests, errors and more</div>
// <RandomPlaceholder onClick={ this.onPlaceholderItemClick } appliedFilterKeys={ appliedFilterKeys } />
}
</div>
}

View file

@ -56,4 +56,22 @@
position: absolute;
right: 9px;
top: 9px;
}
.placeholder {
color: $gray-medium;
font-weight: 300;
font-size: 16px;
user-select: none;
& span {
font-weight: 400;
color: $teal;
cursor: pointer;
border-bottom: dashed thin $teal;
&:hover {
color: $teal-dark;
}
}
}

View file

@ -100,6 +100,7 @@ class CustomFields extends React.Component {
title="No data available."
size="small"
show={ fields.size === 0 }
icon
>
<div className={ styles.list }>
{ fields.filter(i => i.index).map(field => (

View file

@ -23,7 +23,7 @@ function AssistScript(props) {
r.issue=function(k,p){r.push([6,k,p])};
r.isActive=function(){return false};
r.getSessionToken=function(){};
})(0, "${props.projectKey}", "//static.openreplay.com/3.4.0/openreplay-assist.js",1,28);
})(0, "${props.projectKey}", "//static.openreplay.com/3.4.9/openreplay-assist.js",1,28);
</script>`}
</Highlight>
</div>

View file

@ -50,7 +50,7 @@ class ManageUsers extends React.PureComponent {
}
adminLabel = (user) => {
if (user.superAdmin) return 'Owner';
if (user.superAdmin) return null;
return user.admin ? 'Admin' : '';
};
@ -207,8 +207,8 @@ class ManageUsers extends React.PureComponent {
<div className={ styles.wrapper }>
<div className={ cn(styles.tabHeader, 'flex items-center') }>
<div className="flex items-center mr-auto">
{ !hideHeader && <h3 className={ cn(styles.tabTitle, "text-2xl") }>{ (isAdmin ? 'Manage ' : '') + 'Users' }</h3> }
{ hideHeader && <h3 className={ cn(styles.tabTitle, "text-xl") }>{ `Team Size ${members.size}` }</h3>}
{ !hideHeader && <h3 className={ cn(styles.tabTitle, "text-2xl") }>{ (isAdmin ? 'Manage ' : '') + `Users (${members.size})` }</h3> }
{ hideHeader && <h3 className={ cn(styles.tabTitle, "text-xl") }>{ `Users (${members.size})` }</h3>}
<Popup
trigger={
<div>

View file

@ -6,6 +6,7 @@ const UserItem = ({ user, adminLabel, deleteHandler, editHandler, generateInvite
<div className={ styles.wrapper } id="user-row">
<Icon name="user-alt" size="16" marginRight="10" />
<div id="user-name">{ user.name || user.email }</div>
<div className="px-2"/>
{ adminLabel && <div className={ styles.adminLabel }>{ adminLabel }</div>}
{ user.roleName && <div className={ styles.adminLabel }>{ user.roleName }</div>}
<div className={ styles.actions }>

View file

@ -16,11 +16,11 @@
}
& .adminLabel {
margin-left: 10px;
margin-left: 5px;
padding: 0 10px;
border-radius: 3px;
background-color: $gray-lightest;
font-size: 12px;
font-size: 10px;
border: solid thin $gray-light;
}
}

View file

@ -6,8 +6,8 @@ function Licenses({ account }) {
<div>
<div>{account.license}</div>
{account.expirationDate && (
<div className="font-medium text-sm">
Expires At: {account.expirationDate.toFormat('LLL dd, yyyy')}
<div className="">
(Expires on {account.expirationDate.toFormat('LLL dd, yyyy')})
</div>
)}
</div>

View file

@ -76,6 +76,7 @@ export default class ProfileSettings extends React.PureComponent {
<div className="flex items-center">
<div className={ styles.left }>
<h4 className="text-lg mb-4">{ 'License' }</h4>
<div className={ styles.info }>{ 'License key and expiration date.' }</div>
</div>
<div><Licenses /></div>
</div>

View file

@ -7,6 +7,7 @@ import RoleForm from './components/RoleForm'
import { init, edit, fetchList, remove as deleteRole } from 'Duck/roles';
import RoleItem from './components/RoleItem'
import { confirm } from 'UI/Confirmation';
import { toast } from 'react-toastify';
interface Props {
loading: boolean
@ -14,20 +15,18 @@ interface Props {
edit: (role: any) => void,
instance: any,
roles: any[],
deleteRole: (id: any) => void,
deleteRole: (id: any) => Promise<void>,
fetchList: () => Promise<void>,
account: any,
permissionsMap: any
permissionsMap: any,
removeErrors: any
}
function Roles(props: Props) {
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap } = props
const { loading, instance, roles, init, edit, deleteRole, account, permissionsMap, removeErrors } = props
const [showModal, setShowmModal] = useState(false)
const isAdmin = account.admin || account.superAdmin;
console.log('permissionsMap', permissionsMap)
useEffect(() => {
props.fetchList()
}, [])
@ -49,7 +48,13 @@ function Roles(props: Props) {
header: 'Roles',
confirmation: `Are you sure you want to remove this role?`
})) {
deleteRole(role.roleId)
deleteRole(role.roleId).then(() => {
if (removeErrors && removeErrors.size > 0) {
removeErrors.forEach(e => {
toast.error(e)
})
}
})
}
}
@ -80,6 +85,7 @@ function Roles(props: Props) {
/>
</div>
}
content="You dont have the permissions to perform this action."
disabled={ isAdmin }
size="tiny"
inverted
@ -98,6 +104,7 @@ function Roles(props: Props) {
{roles.map(role => (
<RoleItem
role={role}
isAdmin={isAdmin}
permissions={permissionsMap}
editHandler={editHandler}
deleteHandler={deleteHandler}
@ -121,6 +128,7 @@ export default connect(state => {
instance: state.getIn(['roles', 'instance']) || null,
permissionsMap: permissionsMap,
roles: state.getIn(['roles', 'list']),
removeErrors: state.getIn(['roles', 'removeRequest', 'errors']),
loading: state.getIn(['roles', 'fetchRequest', 'loading']),
account: state.getIn([ 'user', 'account' ])
}

View file

@ -13,34 +13,37 @@ interface Props {
role: any,
deleteHandler?: (role: any) => void,
editHandler?: (role: any) => void,
permissions: any
permissions: any,
isAdmin: boolean
}
function RoleItem({ role, deleteHandler, editHandler, permissions }: Props) {
function RoleItem({ role, deleteHandler, editHandler, isAdmin, permissions }: Props) {
return (
<div className={cn(stl.wrapper)}>
<Icon name="user-alt" size="16" marginRight="10" />
<div className="flex items-center">
<span>{ role.name }</span>
<div className="grid grid-flow-col auto-cols-max gap-2">
<div className="mr-4">{ role.name }</div>
<div className="grid grid-flow-col auto-cols-max">
{role.permissions.map((permission: any) => (
<PermisionLabel permission={permissions[permission]} key={permission.id} />
// <span key={permission.id} className={cn(stl.permission)}>{ permissions[permission].name }</span>
))}
</div>
</div>
{ isAdmin && (
<div className={ stl.actions }>
{ !!deleteHandler &&
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => deleteHandler(role) } id="trash">
<Icon name="trash" size="16" color="teal"/>
</div>
}
{ !!editHandler &&
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => editHandler(role) }>
<Icon name="edit" size="16" color="teal"/>
</div>
}
</div>
)}
<div className={ stl.actions }>
{ !!deleteHandler &&
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => deleteHandler(role) } id="trash">
<Icon name="trash" size="16" color="teal"/>
</div>
}
{ !!editHandler &&
<div className={ cn(stl.button, {[stl.disabled] : role.protected }) } onClick={ () => editHandler(role) }>
<Icon name="edit" size="16" color="teal"/>
</div>
}
</div>
</div>
);
}

View file

@ -38,10 +38,10 @@
.label {
margin-left: 10px;
padding: 0 10px;
padding: 0 5px;
border-radius: 3px;
background-color: $gray-lightest;
font-size: 12px;
font-size: 10px;
border: solid thin $gray-light;
width: fit-content;
}

View file

@ -56,7 +56,7 @@ class Webhooks extends React.PureComponent {
<Loader loading={ loading }>
<NoContent
title="No webhooks are available."
title="No webhooks available."
size="small"
show={ noSlackWebhooks.size === 0 }
icon

View file

@ -4,7 +4,7 @@ import cn from "classnames";
function ErrorText({ className, icon, name, message, bold, lineThrough = false }) {
return (
<div className={ cn("mb-1 truncate", { "font-weight-bold": bold }) }>
{ icon && <Icon name={icon} className="float-left mr-2" size="14" style={{ marginTop: '1px'}}/> }
{/* { icon && <Icon name={icon} className="float-left mr-2" size="14" style={{ marginTop: '1px'}}/> } */}
<span className={cn("code-font color-red", className, { 'line-through' : lineThrough })}>{ name }</span>
<span className={cn('color-gray-darkest ml-2', { 'line-through' : lineThrough })}>{ message }</span>
</div>

View file

@ -5,6 +5,7 @@ import { fetch as fetchSession } from 'Duck/sessions';
import { fetchList as fetchSlackList } from 'Duck/integrations/slack';
import { Link, NoContent, Loader } from 'UI';
import { sessions as sessionsRoute } from 'App/routes';
import withPermissions from 'HOCs/withPermissions'
import LivePlayer from './LivePlayer';
import WebPlayer from './WebPlayer';
@ -56,7 +57,7 @@ function Session({
);
}
export default connect((state, props) => {
export default withPermissions(['SESSION_REPLAY'])(connect((state, props) => {
const { match: { params: { sessionId } } } = props;
return {
sessionId,
@ -67,4 +68,4 @@ export default connect((state, props) => {
}, {
fetchSession,
fetchSlackList,
})(Session);
})(Session));

View file

@ -24,7 +24,7 @@ function Autoplay(props) {
name="sessionsLive"
onChange={ props.toggleAutoplay }
checked={ autoplay }
style={{ margin: '0 10px' }}
style={{ margin: '0px 10px 0px 12px'}}
/>
}
tooltip={'Autoplay'}

View file

@ -38,7 +38,7 @@ export default connect(state => ({
/>
{ visible &&
<div className={ stl.modal } >
<NoContent show={ metadata.size === 0 } size="small" >
<NoContent show={ metadata.size === 0 } size="small">
{ metadata.map((i) => {
const key = Object.keys(i)[0]
const value = i[key]

View file

@ -49,7 +49,7 @@ export default class Fetch extends React.PureComponent {
const { filter, current, currentIndex } = this.state;
const filterRE = getRE(filter, 'i');
const filtered = list
.filter(({ name }) => filterRE.test(name));
.filter((r) => filterRE.test(r.name) || filterRE.test(r.url) || filterRE.test(r.method) || filterRE.test(r.status));
return (
<React.Fragment>
@ -90,7 +90,7 @@ export default class Fetch extends React.PureComponent {
<h4 className="text-lg">Fetch</h4>
<Input
className="input-small"
placeholder="Filter by Name"
placeholder="Filter"
icon="search"
iconPosition="left"
name="filter"

View file

@ -55,11 +55,13 @@ export default class PlayerBlockHeader extends React.PureComponent {
backHandler = () => {
const { history, siteId, funnelPage } = this.props;
if (funnelPage) {
if (funnelPage.get('issueId')) {
history.push(withSiteId(funnelIssueRoute(funnelPage.get('funnelId'), funnelPage.get('issueId')), siteId))
const funnelId = funnelPage && funnelPage.get('funnelId');
const issueId = funnelPage && funnelPage.get('issueId');
if (funnelId || issueId) {
if (issueId) {
history.push(withSiteId(funnelIssueRoute(funnelId, issueId), siteId))
} else
history.push(withSiteId(funnelRoute(funnelPage.get('funnelId')), siteId));
history.push(withSiteId(funnelRoute(funnelId), siteId));
} else
history.push(withSiteId(SESSIONS_ROUTE), siteId);
}

View file

@ -13,8 +13,8 @@ const initialState = Map({
{ name: 'Developer Tools', value: 'DEV_TOOLS' },
{ name: 'Errors', value: 'ERRORS' },
{ name: 'Metrics', value: 'METRICS' },
{ name: 'Assist Live', value: 'ASSIST_LIVE' },
{ name: 'Assist Call', value: 'ASSIST_CALL' },
{ name: 'Assist (Live)', value: 'ASSIST_LIVE' },
{ name: 'Assist (Call)', value: 'ASSIST_CALL' },
])
});

View file

@ -309,4 +309,8 @@
a:hover {
color: $teal;
}
.ui.toggle.checkbox {
min-height: 20px !important;
}

View file

@ -93,7 +93,7 @@ aws_region: "us-east-1"
assets_bucket: sessions-assets
recordings_bucket: mobs
sourcemaps_bucket: sourcemaps
kafka_endpoint: kafka.db.svc.cluster.local:9042
kafka_endpoint: kafka.db.svc.cluster.local:9092
kafka_ssl: 'false'
postgres_endpoint: postgresql.db.svc.cluster.local
postgres_port: 5432