feat(Insights): add anonymous usage data collection (#3543)
* feat(insights): initial code (WIP) * feat(insights): add more info * feat(insights): add fs info * feat(insights): export insights.Data Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): more config info Signed-off-by: Deluan <deluan@navidrome.org> * refactor(insights): move data struct to its own package Signed-off-by: Deluan <deluan@navidrome.org> * refactor(insights): omit some attrs if empty Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): send insights to server, add option to disable Signed-off-by: Deluan <deluan@navidrome.org> * fix(insights): remove info about anonymous login Signed-off-by: Deluan <deluan@navidrome.org> * chore(insights): fix lint Signed-off-by: Deluan <deluan@navidrome.org> * fix(insights): disable collector if EnableExternalServices is false Signed-off-by: Deluan <deluan@navidrome.org> * fix(insights): fix type casting for 32bit platforms Signed-off-by: Deluan <deluan@navidrome.org> * fix(insights): remove EnableExternalServices from the collection (as it will always be false) Signed-off-by: Deluan <deluan@navidrome.org> * chore(insights): fix lint Signed-off-by: Deluan <deluan@navidrome.org> * refactor(insights): rename function for consistency Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): log the data sent to the collector server Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): add last collection timestamp to the "about" dialog. Also add opt-out info to the SignUp form Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): only sends the initial data collection after an admin user is created Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): remove dangling comment Signed-off-by: Deluan <deluan@navidrome.org> * feat(insights): Translate insights messages Signed-off-by: Deluan <deluan@navidrome.org> * fix(insights): reporting empty library Signed-off-by: Deluan <deluan@navidrome.org> * refactor: move URL to consts.js Signed-off-by: Deluan <deluan@navidrome.org> --------- Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
@@ -6,6 +6,7 @@ import Button from '@material-ui/core/Button'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import Link from '@material-ui/core/Link'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import { ThemeProvider, makeStyles } from '@material-ui/core/styles'
|
||||
import {
|
||||
@@ -24,6 +25,7 @@ import useCurrentTheme from '../themes/useCurrentTheme'
|
||||
import config from '../config'
|
||||
import { clearQueue } from '../actions'
|
||||
import { retrieveTranslation } from '../i18n'
|
||||
import { INSIGHTS_DOC_URL } from '../consts.js'
|
||||
|
||||
const useStyles = makeStyles(
|
||||
(theme) => ({
|
||||
@@ -81,6 +83,13 @@ const useStyles = makeStyles(
|
||||
systemNameLink: {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
message: {
|
||||
marginTop: '1em',
|
||||
padding: '0 1em 1em 1em',
|
||||
textAlign: 'center',
|
||||
wordBreak: 'break-word',
|
||||
fontSize: '0.875em',
|
||||
},
|
||||
}),
|
||||
{ name: 'NDLogin' },
|
||||
)
|
||||
@@ -173,6 +182,62 @@ const FormLogin = ({ loading, handleSubmit, validate }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const InsightsNotice = ({ url }) => {
|
||||
const translate = useTranslate()
|
||||
const classes = useStyles()
|
||||
|
||||
const anchorRegex = /\[(.+?)]/g
|
||||
const originalMsg = translate('ra.auth.insightsCollectionNote')
|
||||
|
||||
// Split the entire message on newlines
|
||||
const lines = originalMsg.split('\n')
|
||||
|
||||
const renderedLines = lines.map((line, lineIndex) => {
|
||||
const segments = []
|
||||
let lastIndex = 0
|
||||
let match
|
||||
|
||||
// Find bracketed text in each line
|
||||
while ((match = anchorRegex.exec(line)) !== null) {
|
||||
// match.index is where "[something]" starts
|
||||
// match[1] is the text inside the brackets
|
||||
const bracketText = match[1]
|
||||
|
||||
// Push the text before the bracket
|
||||
segments.push(line.slice(lastIndex, match.index))
|
||||
|
||||
// Push the <Link> component
|
||||
segments.push(
|
||||
<Link
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key={`${lineIndex}-${match.index}`}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{bracketText}
|
||||
</Link>,
|
||||
)
|
||||
|
||||
// Update lastIndex to the character right after the bracketed text
|
||||
lastIndex = match.index + match[0].length
|
||||
}
|
||||
|
||||
// Push the remaining text after the last bracket
|
||||
segments.push(line.slice(lastIndex))
|
||||
|
||||
// Return this line’s parts, plus a <br/> if not the last line
|
||||
return (
|
||||
<React.Fragment key={lineIndex}>
|
||||
{segments}
|
||||
{lineIndex < lines.length - 1 && <br />}
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
|
||||
return <div className={classes.message}>{renderedLines}</div>
|
||||
}
|
||||
|
||||
const FormSignUp = ({ loading, handleSubmit, validate }) => {
|
||||
const translate = useTranslate()
|
||||
const classes = useStyles()
|
||||
@@ -237,6 +302,7 @@ const FormSignUp = ({ loading, handleSubmit, validate }) => {
|
||||
{translate('ra.auth.buttonCreateAdmin')}
|
||||
</Button>
|
||||
</CardActions>
|
||||
<InsightsNotice url={INSIGHTS_DOC_URL} />
|
||||
</Card>
|
||||
<Notification />
|
||||
</div>
|
||||
@@ -245,6 +311,7 @@ const FormSignUp = ({ loading, handleSubmit, validate }) => {
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const Login = ({ location }) => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const translate = useTranslate()
|
||||
|
||||
Reference in New Issue
Block a user