Properly applied code quality tools

main
Štěpán Škorpil 2022-11-03 19:38:01 +01:00
rodzic 8958ab363a
commit 2dda770993
63 zmienionych plików z 5936 dodań i 21460 usunięć

Wyświetl plik

@ -1,38 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"plugin:react/recommended",
"eslint:recommended",
"plugin:@next/next/recommended",
"standard"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"react",
"react-hooks",
"jsx-a11y",
"import",
"@next/next"
],
"rules": {
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": ["error"]
},
"settings": {
"react": {
"version": "detect"
}
}
}

Wyświetl plik

@ -1,4 +0,0 @@
{
"printWidth": 100,
"singleQuote": true
}

Wyświetl plik

@ -15,5 +15,8 @@ module.exports = {
permanent: true permanent: true
} }
] ]
},
typescript: {
tsconfigPath: '../tsconfig.json'
} }
} }

21099
application/package-lock.json wygenerowano

Plik diff jest za duży Load Diff

Wyświetl plik

@ -16,17 +16,20 @@
"@apollo/client": "^3.6.9", "@apollo/client": "^3.6.9",
"@datapunt/matomo-tracker-js": "^0.5.1", "@datapunt/matomo-tracker-js": "^0.5.1",
"@elastic/elasticsearch": "^8.2.1", "@elastic/elasticsearch": "^8.2.1",
"@fortawesome/fontawesome-svg-core": "^1.2.36", "@fortawesome/fontawesome-common-types": "^6.2.0",
"@fortawesome/free-brands-svg-icons": "^5.15.4", "@fortawesome/fontawesome-svg-core": "^6.2.0",
"@fortawesome/free-regular-svg-icons": "^5.15.4", "@fortawesome/free-brands-svg-icons": "^6.2.0",
"@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/free-regular-svg-icons": "^6.2.0",
"@fortawesome/react-fontawesome": "^0.1.17", "@fortawesome/free-solid-svg-icons": "^6.2.0",
"@hookform/resolvers": "^2.8.5", "@fortawesome/react-fontawesome": "^0.2.0",
"@hookform/resolvers": "^2.9.10",
"@popperjs/core": "^2.11.6",
"@svgr/webpack": "^6.2.1", "@svgr/webpack": "^6.2.1",
"apollo-server-micro": "^3.10.1", "apollo-server-micro": "^3.10.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"graphql": "^16.5.0", "graphql": "^16.5.0",
"micro": "^9.4.1",
"micro-cors": "^0.1.1", "micro-cors": "^0.1.1",
"next": "^12.2.5", "next": "^12.2.5",
"nexus": "^1.3.0", "nexus": "^1.3.0",
@ -40,25 +43,48 @@
"zod": "^3.11.6" "zod": "^3.11.6"
}, },
"devDependencies": { "devDependencies": {
"@next/eslint-plugin-next": "^12.0.7", "@next/eslint-plugin-next": "^13.0.0",
"@types/jest": "^27.0.2", "@types/jest": "^29.2.0",
"@types/micro-cors": "^0.1.2", "@types/micro-cors": "^0.1.2",
"@types/node": "^18.7.18", "@types/node": "^18.7.18",
"@types/npmlog": "^4.1.3", "@types/npmlog": "^4.1.3",
"@types/react": "^17.0.14", "@types/react": "^17.0.14",
"@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.4.0", "@typescript-eslint/parser": "^5.37.0",
"eslint": "^7.32.0", "eslint": "^8.0.1",
"eslint-config-standard": "^16.0.3", "eslint-config-standard-react": "^12.0.0",
"eslint-plugin-import": "^2.25.3", "eslint-config-standard-with-typescript": "^23.0.0",
"eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-n": "^15.2.5",
"eslint-plugin-promise": "^5.1.1", "eslint-plugin-promise": "^6.0.1",
"eslint-plugin-react": "^7.27.1", "eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.3.0", "jest": "^29.2.2",
"jest": "^27.3.0", "ts-jest": "^29.0.3",
"standard": "*",
"ts-jest": "^27.0.7",
"typescript": "^4.3.5" "typescript": "^4.3.5"
},
"eslintConfig": {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"standard-with-typescript",
"standard-react"
],
"parserOptions": {
"project": [
"tsconfig.json"
]
},
"rules": {
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksVoidReturn": {
"attributes": false
}
}
]
}
} }
} }

Wyświetl plik

@ -1,14 +1,14 @@
import React from 'react' import React from 'react'
import FallbackImage from './FallbackImage' import FallbackImage from './FallbackImage'
const Avatar:React.FC<{url:string|null|undefined}> = ({ url }) => { const Avatar: React.FC<{ url: string | null | undefined }> = ({ url }) => {
return ( return (
<FallbackImage <FallbackImage
className={'avatar'} className={'avatar'}
src={url} src={url ?? undefined}
fallbackSrc={'/avatar.svg'} fallbackSrc={'/avatar.svg'}
alt={'Avatar'} alt={'Avatar'}
/> />
) )
} }
export default Avatar export default Avatar

Wyświetl plik

@ -1,14 +1,20 @@
import React, { ImgHTMLAttributes, useEffect, useState } from 'react' import React, { ImgHTMLAttributes, ReactElement, useEffect, useState } from 'react'
export default function FallbackImage ({ fallbackSrc, src, alt, ...props }: ImgHTMLAttributes<HTMLImageElement>&{fallbackSrc?:string}) {
export default function FallbackImage ({
fallbackSrc,
src,
alt,
...props
}: ImgHTMLAttributes<HTMLImageElement> & { fallbackSrc?: string }): ReactElement {
const [showFallback, setShowFallback] = useState<boolean>(false) const [showFallback, setShowFallback] = useState<boolean>(false)
useEffect(() => { useEffect(() => {
setShowFallback(!src) setShowFallback(src === undefined || src === null || src === '')
}, [src]) }, [src])
const handleError = (event): void => { const handleError = (event): void => {
if (props.onError) { if (props.onError != null) {
props.onError(event) props.onError(event)
} }
if (!fallbackSrc) { if (fallbackSrc === undefined || fallbackSrc === '') {
return return
} }
setShowFallback(true) setShowFallback(true)

Wyświetl plik

@ -1,4 +1,4 @@
import React, { useEffect } from 'react' import React, { ReactElement, useEffect } from 'react'
import striptags from 'striptags' import striptags from 'striptags'
import Avatar from './Avatar' import Avatar from './Avatar'
import SoftwareBadge from './badges/SoftwareBadge' import SoftwareBadge from './badges/SoftwareBadge'
@ -14,16 +14,16 @@ import { FeedResultItem } from '../graphql/client/queries/ListFeedsQuery'
const FeedResult = ({ const FeedResult = ({
feed feed
}:{ feed: FeedResultItem }) => { }: { feed: FeedResultItem }): ReactElement => {
const fallbackEmojiImage = '/emoji.svg' const fallbackEmojiImage = '/emoji.svg'
const handleEmojiImageError = (event) => { const handleEmojiImageError = (event): void => {
event.target.src = fallbackEmojiImage event.target.src = fallbackEmojiImage
} }
useEffect(() => { useEffect(() => {
document.querySelectorAll('.with-emoji img').forEach(element => { document.querySelectorAll('.with-emoji img').forEach(element => {
if (element.attributes['data-error-handler']) { if (element.attributes['data-error-handler'] === 'attached') {
return return
} }
element.addEventListener('error', handleEmojiImageError) element.addEventListener('error', handleEmojiImageError)

Wyświetl plik

@ -1,10 +1,10 @@
import React from 'react' import React, { ReactElement } from 'react'
import FeedResult from './FeedResult' import FeedResult from './FeedResult'
import { FeedResultItem } from '../graphql/client/queries/ListFeedsQuery' import { FeedResultItem } from '../graphql/client/queries/ListFeedsQuery'
const FeedResults = ({ const FeedResults = ({
feeds feeds
}:{ feeds: FeedResultItem[] }) => { }: { feeds: FeedResultItem[] }): ReactElement => {
if (feeds.length === 0) { if (feeds.length === 0) {
return ( return (
<> <>

Wyświetl plik

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
const Footer:React.FC = () => { const Footer: React.FC = () => {
return ( return (
<footer className={'text-center mt-5'}> <footer className={'text-center mt-5'}>
©{(new Date()).getFullYear()} <a href={'https://skorpil.cz'}>Štěpán Škorpil</a> ©{(new Date()).getFullYear()} <a href={'https://skorpil.cz'}>Štěpán Škorpil</a>

Wyświetl plik

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
export function getFlagEmoji (countryCode:string):string { export function getFlagEmoji (countryCode: string): string {
const codePoints = countryCode const codePoints = countryCode
.toUpperCase() .toUpperCase()
.split('') .split('')
@ -8,18 +8,28 @@ export function getFlagEmoji (countryCode:string):string {
return String.fromCodePoint(...codePoints) return String.fromCodePoint(...codePoints)
} }
export interface GeoParams{ export interface GeoParams {
countryCode?: string, countryCode?: string
countryName?:string, countryName?: string
city?: string city?: string
} }
export default function Geo ({ countryCode, countryName, city }:GeoParams):React.ReactElement|null { export default function Geo ({ countryCode, countryName, city }: GeoParams): React.ReactElement | null {
if (!countryCode && !city) { const alignedCountryCode = countryCode ?? ''
const alignedCity = city ?? ''
if (alignedCountryCode === '' && alignedCity === '') {
return null return null
} }
return <div className={'geo'}> return <div className={'geo'}>
{city ? <span className={'city'}>{city}</span> : ''} {
{countryCode ? <span className={'country'} title={`${countryName ?? countryCode}`}>{getFlagEmoji(countryCode)}</span> : ''} alignedCity !== ''
? <span className={'city'}>{alignedCity}</span>
: ''
}
{
alignedCountryCode !== ''
? <span className={'country'} title={`${countryName ?? alignedCountryCode}`}>{getFlagEmoji(alignedCountryCode)}</span>
: ''
}
</div> </div>
} }

Wyświetl plik

@ -17,10 +17,10 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
</div> </div>
) )
if (table) { if (table !== undefined || table !== 0) {
return ( return (
<> <>
{showTop && loading {(showTop ?? false) && loading
? ( ? (
<tbody> <tbody>
<tr className={className}> <tr className={className}>
@ -31,8 +31,8 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
</tbody> </tbody>
) )
: ''} : ''}
{hideContent && loading ? '' : children} {(hideContent ?? false) && loading ? '' : children}
{showBottom && loading {(showBottom ?? false) && loading
? ( ? (
<tbody> <tbody>
<tr className={className}> <tr className={className}>
@ -50,9 +50,9 @@ const Loader: React.FC<{ children: ReactNode, loading: boolean, hideContent?: bo
} }
return ( return (
<> <>
{showTop && loading ? spinner : ''} {(showTop ?? false) && loading ? spinner : ''}
{hideContent && loading ? '' : children} {(hideContent ?? false) && loading ? '' : children}
{showBottom && loading ? spinner : ''} {(showBottom ?? false) && loading ? spinner : ''}
</> </>
) )
} }

Wyświetl plik

@ -3,7 +3,7 @@ import NavItem from './NavItem'
import { faUser, faServer, faChartPie } from '@fortawesome/free-solid-svg-icons' import { faUser, faServer, faChartPie } from '@fortawesome/free-solid-svg-icons'
import FallbackImage from './FallbackImage' import FallbackImage from './FallbackImage'
const NavBar:React.FC = () => { const NavBar: React.FC = () => {
const [showMenu, setShowMenu] = useState<boolean>(false) const [showMenu, setShowMenu] = useState<boolean>(false)
return ( return (
<nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4"> <nav className="navbar navbar-expand-lg navbar-dark bg-dark mb-4">

Wyświetl plik

@ -4,7 +4,7 @@ import { useRouter } from 'next/router'
import { IconProp } from '@fortawesome/fontawesome-svg-core' import { IconProp } from '@fortawesome/fontawesome-svg-core'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
const NavItem:FC<{path:string, label:string, icon:IconProp}> = ({ path, label, icon }) => { const NavItem: FC<{ path: string, label: string, icon: IconProp }> = ({ path, label, icon }) => {
const router = useRouter() const router = useRouter()
const active = router.pathname === path const active = router.pathname === path
return ( return (

Wyświetl plik

@ -2,8 +2,8 @@ import React from 'react'
import Avatar from './Avatar' import Avatar from './Avatar'
import { ParentFeedItem } from '../graphql/client/queries/ListFeedsQuery' import { ParentFeedItem } from '../graphql/client/queries/ListFeedsQuery'
const ParentFeed: React.FC<{feed:ParentFeedItem|null}> = ({ feed }) => { const ParentFeed: React.FC<{ feed: ParentFeedItem | null }> = ({ feed }) => {
if (!feed) { if (feed == null) {
return (<></>) return (<></>)
} }
return ( return (

Wyświetl plik

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
const ProgressBar: React.FC<{ percents: number, way?: 'left' | 'right' | 'top' | 'bottom', color?:string }> = ({ percents, way, color }) => { const ProgressBar: React.FC<{ percents: number, way?: 'left' | 'right' | 'top' | 'bottom', color?: string }> = ({ percents, way, color }) => {
way = way ?? 'right' way = way ?? 'right'
percents = Math.round(percents) percents = Math.round(percents)
color = color ?? 'var(--accent-color)' color = color ?? 'var(--accent-color)'

Wyświetl plik

@ -4,8 +4,8 @@ import { faSortUp, faSortDown } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
const SortToggle: React.FC<{ const SortToggle: React.FC<{
onToggle:(StatsRequestSortBy)=>void, onToggle: (StatsRequestSortBy) => void
field:string, field: string
sort: Sort sort: Sort
}> = ({ onToggle, field, sort, children }) => { }> = ({ onToggle, field, sort, children }) => {
return ( return (

Wyświetl plik

@ -2,12 +2,12 @@ import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconProp } from '@fortawesome/fontawesome-svg-core' import { IconProp } from '@fortawesome/fontawesome-svg-core'
const Badge:React.FC<{ faIcon:IconProp, label:string, value:string|number|null, className?:string, showUnknown?:boolean }> = ({ faIcon, label, value, className, showUnknown }) => { const Badge: React.FC<{ faIcon: IconProp, label: string, value: string | number | null, className?: string, showUnknown?: boolean }> = ({ faIcon, label, value, className, showUnknown }) => {
if (value === null && showUnknown !== true) { if (value === null && showUnknown !== true) {
return (<></>) return (<></>)
} }
return ( return (
<div className={'badge bg-secondary ' + className} title={label}> <div className={`badge bg-secondary ${className ?? ''}`} title={label}>
<FontAwesomeIcon icon={faIcon} className={'margin-right'}/> <FontAwesomeIcon icon={faIcon} className={'margin-right'}/>
<span className="visually-hidden">{label}:</span> <span className="visually-hidden">{label}:</span>
<span className={'value'}>{value === null ? '?' : value}</span> <span className={'value'}>{value === null ? '?' : value}</span>

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react'
import { faRobot } from '@fortawesome/free-solid-svg-icons' import { faRobot } from '@fortawesome/free-solid-svg-icons'
import Badge from './Badge' import Badge from './Badge'
const BotBadge:React.FC<{ bot: boolean | null}> = ({ bot }) => { const BotBadge: React.FC<{ bot: boolean | null }> = ({ bot }) => {
return ( return (
<Badge faIcon={faRobot} <Badge faIcon={faRobot}
label={'Bot'} label={'Bot'}

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react'
import { faCalendarPlus } from '@fortawesome/free-solid-svg-icons' import { faCalendarPlus } from '@fortawesome/free-solid-svg-icons'
import Badge from './Badge' import Badge from './Badge'
const CreatedAtBadge:React.FC<{ createdAt: string | null }> = ({ createdAt }) => { const CreatedAtBadge: React.FC<{ createdAt: string | null }> = ({ createdAt }) => {
return ( return (
<Badge faIcon={faCalendarPlus} <Badge faIcon={faCalendarPlus}
label={'Created at'} label={'Created at'}

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react'
import { faRss, faUser } from '@fortawesome/free-solid-svg-icons' import { faRss, faUser } from '@fortawesome/free-solid-svg-icons'
import Badge from './Badge' import Badge from './Badge'
const FeedTypeBadge:React.FC<{ type: 'account' | 'channel' }> = ({ type }) => { const FeedTypeBadge: React.FC<{ type: 'account' | 'channel' }> = ({ type }) => {
return ( return (
<Badge faIcon={type === 'channel' ? faRss : faUser} <Badge faIcon={type === 'channel' ? faRss : faUser}
label={'Feed type'} label={'Feed type'}

Wyświetl plik

@ -2,7 +2,7 @@ import { faUserFriends } from '@fortawesome/free-solid-svg-icons'
import React from 'react' import React from 'react'
import Badge from './Badge' import Badge from './Badge'
const FollowersBadge:React.FC<{ followers: number|null}> = ({ followers }) => { const FollowersBadge: React.FC<{ followers: number | null }> = ({ followers }) => {
return ( return (
<Badge faIcon={faUserFriends} <Badge faIcon={faUserFriends}
label={'Followers'} label={'Followers'}

Wyświetl plik

@ -2,7 +2,7 @@ import { faEye } from '@fortawesome/free-solid-svg-icons'
import React from 'react' import React from 'react'
import Badge from './Badge' import Badge from './Badge'
const FollowingBadge:React.FC<{ following: number|null}> = ({ following }) => { const FollowingBadge: React.FC<{ following: number | null }> = ({ following }) => {
return ( return (
<Badge faIcon={faEye} <Badge faIcon={faEye}
label={'Following'} label={'Following'}

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react'
import Badge from './Badge' import Badge from './Badge'
import { faCalendarCheck } from '@fortawesome/free-solid-svg-icons' import { faCalendarCheck } from '@fortawesome/free-solid-svg-icons'
const LastPostAtBadge:React.FC<{ lastStatusAt: string | null }> = ({ lastStatusAt }) => { const LastPostAtBadge: React.FC<{ lastStatusAt: string | null }> = ({ lastStatusAt }) => {
return ( return (
<Badge faIcon={faCalendarCheck} <Badge faIcon={faCalendarCheck}
label={'Last status at'} label={'Last status at'}

Wyświetl plik

@ -8,8 +8,8 @@ const SoftwareBadge: React.FC<{ softwareName: string | null }> = ({ softwareName
<FallbackImage className={'icon'} <FallbackImage className={'icon'}
src={softwareName !== null ? `/software/${softwareName}.svg` : fallbackImage} src={softwareName !== null ? `/software/${softwareName}.svg` : fallbackImage}
fallbackSrc={fallbackImage} fallbackSrc={fallbackImage}
alt={softwareName} alt={softwareName ?? undefined}
title={softwareName} title={softwareName ?? undefined}
/> />
<span className={'value'}>{softwareName}</span> <span className={'value'}>{softwareName}</span>
</div>) </div>)

Wyświetl plik

@ -2,7 +2,7 @@ import React from 'react'
import { faCommentAlt } from '@fortawesome/free-solid-svg-icons' import { faCommentAlt } from '@fortawesome/free-solid-svg-icons'
import Badge from './Badge' import Badge from './Badge'
const StatusesCountBadge:React.FC<{ statusesCount: number | null }> = ({ statusesCount }) => { const StatusesCountBadge: React.FC<{ statusesCount: number | null }> = ({ statusesCount }) => {
return ( return (
<Badge faIcon={faCommentAlt} <Badge faIcon={faCommentAlt}
label={'Status count'} label={'Status count'}

Wyświetl plik

@ -1,6 +1,6 @@
import { ApolloClient, InMemoryCache } from '@apollo/client' import { ApolloClient, InMemoryCache, NormalizedCacheObject } from '@apollo/client'
export default function createGraphqlClient () { export default function createGraphqlClient (): ApolloClient<NormalizedCacheObject> {
return new ApolloClient({ return new ApolloClient({
uri: '/api/graphql', uri: '/api/graphql',
cache: new InMemoryCache() cache: new InMemoryCache()

Wyświetl plik

@ -57,56 +57,57 @@ export const ListFeedsQuery = gql`
} }
` `
export type ParentFeedItem = { export interface ParentFeedItem {
id: string, id: string
avatar: string, avatar: string
displayName: string displayName: string
name: string
domain: string
url: string
}
export interface FeedResultItem {
id: string
avatar: string
displayName: string
foundAt: string
bot: boolean
createdAt: string
description: string
followersCount: number
followingCount: number
lastStatusAt: string
locked: boolean
name: string
refreshedAt: string
statusesCount: number
type: 'account' | 'channel'
url: string
fields: Array<{
name: string name: string
value: string
}>
node: {
domain: string domain: string
url:string foundAt: string
geoip: {
// eslint-disable-next-line camelcase
city_name: string
// eslint-disable-next-line camelcase
country_iso_code: string
}
halfYearActiveUserCount: number
id: string
monthActiveUserCount: number
name: string
openRegistrations: boolean
refreshAttemptedAt: string
refreshedAt: string
softwareName: string
}
parent: ParentFeedItem | null
} }
export type FeedResultItem = { export interface ListFeedsResult {
id: string, listFeeds: List<FeedResultItem>
avatar: string,
displayName: string,
foundAt: string,
bot: boolean,
createdAt: string,
description: string,
followersCount: number,
followingCount: number,
lastStatusAt: string,
locked: boolean,
name: string,
refreshedAt: string,
statusesCount: number,
type: 'account' | 'channel'
url: string,
fields: {
name: string, value: string
}[],
node: {
domain: string,
foundAt: string,
geoip: {
// eslint-disable-next-line camelcase
city_name: string,
// eslint-disable-next-line camelcase
country_iso_code: string,
},
halfYearActiveUserCount: number,
id: string,
monthActiveUserCount: number,
name: string,
openRegistrations: boolean,
refreshAttemptedAt: string,
refreshedAt: string,
softwareName: string
},
parent: ParentFeedItem|null
}
export type ListFeedsResult = {
listFeeds: List<FeedResultItem>
} }

Wyświetl plik

@ -32,30 +32,30 @@ export const ListNodesQuery = gql`
} }
` `
export type NodeResultItem = { export interface NodeResultItem {
domain: string, domain: string
foundAt: string, foundAt: string
geoip: { geoip: {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
city_name: string, city_name: string
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
country_iso_code: string, country_iso_code: string
}, }
halfYearActiveUserCount: number, halfYearActiveUserCount: number
id: string, id: string
monthActiveUserCount: number, monthActiveUserCount: number
name: string, name: string
openRegistrations: boolean, openRegistrations: boolean
refreshAttemptedAt: string, refreshAttemptedAt: string
refreshedAt: string, refreshedAt: string
softwareName: string, softwareName: string
softwareVersion: string, softwareVersion: string
standardizedSoftwareVersion:string, standardizedSoftwareVersion: string
totalUserCount: number, totalUserCount: number
statusesCount: number, statusesCount: number
accountFeedCount: number accountFeedCount: number
} }
export type ListNodesResult = { export interface ListNodesResult {
listNodes: List<NodeResultItem>; listNodes: List<NodeResultItem>
} }

Wyświetl plik

@ -14,13 +14,13 @@ export const ListStatsQuery = gql`
} }
` `
export type StatsResultItem = { export interface StatsResultItem {
softwareName: string, softwareName: string
nodeCount: number, nodeCount: number
accountFeedCount: number, accountFeedCount: number
channelFeedCount: number channelFeedCount: number
} }
export type ListStatsResult = { export interface ListStatsResult {
listStats: List<StatsResultItem>; listStats: List<StatsResultItem>
} }

Wyświetl plik

@ -1,6 +1,6 @@
import { PagingType } from '../../server/schema/types' import { PagingType } from '../../server/schema/types'
export type List<TItem> = { export interface List<TItem> {
paging: PagingType, paging: PagingType
items: TItem[] items: TItem[]
} }

Wyświetl plik

@ -1,7 +1,7 @@
import { FeedQueryInputType } from '../types/FeedQueryInput' import { FeedQueryInputType } from '../types/FeedQueryInput'
import { PagingInputType } from '../types/PagingInput' import { PagingInputType } from '../types/PagingInput'
export type ListFeedsVariables = { export interface ListFeedsVariables {
paging: PagingInputType; paging: PagingInputType
query: FeedQueryInputType query: FeedQueryInputType
} }

Wyświetl plik

@ -1,7 +1,7 @@
import { PagingInputType } from '../types/PagingInput' import { PagingInputType } from '../types/PagingInput'
import { NodeQueryInputType } from '../types/NodeQueryInput' import { NodeQueryInputType } from '../types/NodeQueryInput'
export type ListNodesVariables = { export interface ListNodesVariables {
paging: PagingInputType; paging: PagingInputType
query: NodeQueryInputType query: NodeQueryInputType
} }

Wyświetl plik

@ -1,5 +1,5 @@
import { StatsQueryInputType } from '../types/StatsQueryInput' import { StatsQueryInputType } from '../types/StatsQueryInput'
export type ListStatsVariables = { export interface ListStatsVariables {
query: StatsQueryInputType query: StatsQueryInputType
} }

Wyświetl plik

@ -1,6 +1,6 @@
import { z } from 'zod' import { z } from 'zod'
export const NodeSortingByValues:readonly [string, ...string[]] = [ export const NodeSortingByValues: readonly [string, ...string[]] = [
'domain', 'domain',
'softwareName', 'softwareName',
'totalUserCount', 'totalUserCount',
@ -14,4 +14,4 @@ export const NodeSortingByValues:readonly [string, ...string[]] = [
export const nodeSortingBySchema = z.enum(NodeSortingByValues) export const nodeSortingBySchema = z.enum(NodeSortingByValues)
export type NodeSoringByEnumType = z.infer<typeof nodeSortingBySchema>; export type NodeSoringByEnumType = z.infer<typeof nodeSortingBySchema>

Wyświetl plik

@ -1,3 +1,3 @@
export type PagingInputType = { export interface PagingInputType {
page: number page: number
} }

Wyświetl plik

@ -1,13 +1,14 @@
import { z } from 'zod' import { z } from 'zod'
export const createSortingInputSchema = (members:z.ZodEnum<[string, ...string[]]>) => { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export const createSortingInputSchema = (members: z.ZodEnum<[string, ...string[]]>) => {
return z.object({ return z.object({
sortBy: members, sortBy: members,
sortWay: z.enum(['asc', 'desc']) sortWay: z.enum(['asc', 'desc'])
}) })
} }
export type SortingInputType<TMembers> = { export interface SortingInputType<TMembers> {
sortBy: TMembers sortBy: TMembers
sortWay: 'asc'|'desc' sortWay: 'asc' | 'desc'
} }

Wyświetl plik

@ -1,6 +1,6 @@
import { z } from 'zod' import { z } from 'zod'
export const StatsSortingByValues:readonly [string, ...string[]] = [ export const StatsSortingByValues: readonly [string, ...string[]] = [
'softwareName', 'softwareName',
'nodeCount', 'nodeCount',
'accountFeedCount', 'accountFeedCount',
@ -9,4 +9,4 @@ export const StatsSortingByValues:readonly [string, ...string[]] = [
export const statsSortingBySchema = z.enum(StatsSortingByValues) export const statsSortingBySchema = z.enum(StatsSortingByValues)
export type StatsSoringByEnumType = z.infer<typeof statsSortingBySchema>; export type StatsSoringByEnumType = z.infer<typeof statsSortingBySchema>

Wyświetl plik

@ -1,10 +1,10 @@
import { ElasticClient } from '../../../lib/storage/ElasticClient' import { ElasticClient } from '../../../lib/storage/ElasticClient'
type Context = { interface Context {
elasticClient: ElasticClient elasticClient: ElasticClient
defaultPaging: { defaultPaging: {
limit: 20 limit: 20
} }
} }
export default Context export default Context

Wyświetl plik

@ -3,7 +3,7 @@ import resolvers from './resolvers'
import schema from './schema' import schema from './schema'
import { createContext } from './context' import { createContext } from './context'
export default function createGraphqlServer () { export default function createGraphqlServer (): ApolloServer {
return new ApolloServer({ return new ApolloServer({
schema, schema,
resolvers, resolvers,

Wyświetl plik

@ -3,7 +3,6 @@ import { join } from 'path'
import * as types from './types' import * as types from './types'
import * as queries from './queries' import * as queries from './queries'
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
import * as elastic from './sources/elastic'
const schema = makeSchema({ const schema = makeSchema({
types: { types: {

Wyświetl plik

@ -21,7 +21,7 @@ export const listNodes = extendType({
default: { default: '', sortBy: 'refreshedAt', sortWay: 'desc' } default: { default: '', sortBy: 'refreshedAt', sortWay: 'desc' }
}) })
}, },
resolve: async (event, { paging, query }:ListNodesVariables, { elasticClient, defaultPaging }: Context) => { resolve: async (event, { paging, query }: ListNodesVariables, { elasticClient, defaultPaging }: Context) => {
console.info('Searching nodes', { paging, query }) console.info('Searching nodes', { paging, query })
const results = await elasticClient.search<Node>({ const results = await elasticClient.search<Node>({

Wyświetl plik

@ -5,6 +5,7 @@ import { ListStatsVariables } from '../../../common/queries/listStats'
import nodeIndex from '../../../../lib/storage/Definitions/nodeIndex' import nodeIndex from '../../../../lib/storage/Definitions/nodeIndex'
import { StatsQueryInputType } from '../../../common/types/StatsQueryInput' import { StatsQueryInputType } from '../../../common/types/StatsQueryInput'
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const getSort = (query: StatsQueryInputType) => { const getSort = (query: StatsQueryInputType) => {
switch (query.sortBy) { switch (query.sortBy) {
case 'nodeCount': case 'nodeCount':
@ -30,7 +31,7 @@ export const listStats = extendType({
default: { sortBy: 'nodeCount', sortWay: 'desc' } default: { sortBy: 'nodeCount', sortWay: 'desc' }
}) })
}, },
resolve: async (event, { query }:ListStatsVariables, { elasticClient }: Context) => { resolve: async (event, { query }: ListStatsVariables, { elasticClient }: Context) => {
console.info('Searching stats', { query }) console.info('Searching stats', { query })
const results = await elasticClient.search({ const results = await elasticClient.search({
@ -59,7 +60,7 @@ export const listStats = extendType({
sort: { sort: {
bucket_sort: { bucket_sort: {
sort: [ sort: [
// @ts-ignore // @ts-expect-error
getSort(query) getSort(query)
] ]
} }
@ -68,16 +69,16 @@ export const listStats = extendType({
} }
} }
}) })
type Aggregation = { interface Aggregation {
buckets:{ buckets: Array<{
key:string, key: string
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
doc_count:number doc_count: number
accountFeedCount: {value:number} accountFeedCount: { value: number }
channelFeedCount: {value:number} channelFeedCount: { value: number }
}[] }>
} }
const software = results.aggregations.software as Aggregation const software = results?.aggregations?.software as Aggregation
return { return {
items: software.buckets.map(bucket => { items: software.buckets.map(bucket => {
return { return {

Wyświetl plik

@ -1,4 +1,4 @@
export default function getFlagEmoji (countryCode:string):string { export default function getFlagEmoji (countryCode: string): string {
const codePoints = countryCode const codePoints = countryCode
.toUpperCase() .toUpperCase()
.split('') .split('')

Wyświetl plik

@ -1,10 +1,10 @@
import { UserOptions } from '@datapunt/matomo-tracker-js/es/types' import { UserOptions } from '@datapunt/matomo-tracker-js/es/types'
import MatomoTracker from '@datapunt/matomo-tracker-js' import MatomoTracker from '@datapunt/matomo-tracker-js'
let matomo:MatomoTracker|undefined let matomo: MatomoTracker | undefined
const getMatomo = (config:UserOptions):MatomoTracker => { const getMatomo = (config: UserOptions): MatomoTracker => {
if (!matomo) { if (matomo == null) {
console.info('Starting Matomo', config) console.info('Starting Matomo', config)
matomo = new MatomoTracker(config) matomo = new MatomoTracker(config)
} }

Wyświetl plik

@ -1,3 +1,3 @@
export default function getNodeId (name:string, domain:string):string { export default function getNodeId (name: string, domain: string): string {
return `${name}@${domain}` return `${name}@${domain}`
} }

Wyświetl plik

@ -1,10 +1,12 @@
export const matomoConfig = { export const matomoConfig = {
urlBase: typeof process.env.MATOMO_URL === 'string' && process.env.MATOMO_URL !== '' urlBase:
? process.env.MATOMO_URL process.env.MATOMO_URL !== undefined && process.env.MATOMO_URL !== ''
: 'https://domain.tld', ? process.env.MATOMO_URL
siteId: parseInt(typeof process.env.MATOMO_SITE_ID === 'string' && process.env.MATOMO_SITE_ID !== '' : 'https://domain.tld',
? process.env.MATOMO_SITE_ID siteId: parseInt(
: '1' process.env.MATOMO_SITE_ID !== undefined && process.env.MATOMO_SITE_ID !== ''
? process.env.MATOMO_SITE_ID
: '1'
), ),
disabled: !process.env.MATOMO_URL || !process.env.MATOMO_SITE_ID disabled: (process.env.MATOMO_URL ?? '') === '' || (process.env.MATOMO_SITE_ID ?? '') === ''
} }

Wyświetl plik

@ -1,4 +1,4 @@
export default function prepareSimpleQuery (search:string):string { export default function prepareSimpleQuery (search: string): string {
const tokens = search.split(/\s+/) const tokens = search.split(/\s+/)
const searchContainsWildcard = tokens.filter(token => token.length > 0 && token.slice(-1) === '*').length > 0 const searchContainsWildcard = tokens.filter(token => token.length > 0 && token.slice(-1) === '*').length > 0
return tokens.map(token => searchContainsWildcard ? token : token + '*').join(' ') return tokens.map(token => searchContainsWildcard ? token : token + '*').join(' ')

Wyświetl plik

@ -1,29 +1,29 @@
import Field from './Field' import Field from './Field'
interface Feed { interface Feed {
domain: string domain: string
foundAt: number, foundAt: number
refreshedAt?: number, refreshedAt?: number
name: string, name: string
fullName: string, fullName: string
displayName: string, displayName: string
description: string, description: string
strippedDescription?: string, strippedDescription?: string
followersCount?: number, followersCount?: number
followingCount?: number, followingCount?: number
statusesCount?: number, statusesCount?: number
lastStatusAt?: number, lastStatusAt?: number
createdAt?: number, createdAt?: number
bot?: boolean, bot?: boolean
locked: boolean, locked: boolean
url: string, url: string
avatar?: string, avatar?: string
type: 'account' | 'channel', type: 'account' | 'channel'
parentFeedName?: string, parentFeedName?: string
parentFeedDomain?: string parentFeedDomain?: string
fields: Field[], fields: Field[]
extractedEmails: string[], extractedEmails: string[]
extractedTags: string[] extractedTags: string[]
} }
export default Feed export default Feed

Wyświetl plik

@ -1,8 +1,8 @@
interface Field { interface Field {
name: string, name: string
value: string value: string
strippedName?: string strippedName?: string
strippedValue?: string strippedValue?: string
} }
export default Field export default Field

Wyświetl plik

@ -1,13 +1,13 @@
interface Geo { interface Geo {
cityName?: string, cityName?: string
continentName?: string, continentName?: string
countryIsoCode?: string, countryIsoCode?: string
countryName?: string, countryName?: string
latitude: number latitude: number
longitude: number longitude: number
location?: string, location?: string
regionIsoCode?: string, regionIsoCode?: string
regionName?: string regionName?: string
} }
export default Geo export default Geo

Wyświetl plik

@ -1,25 +1,25 @@
import Geo from './Geo' import Geo from './Geo'
interface Node { interface Node {
name?:string, name?: string
strippedName?:string, strippedName?: string
foundAt: number, foundAt: number
refreshAttemptedAt?: number refreshAttemptedAt?: number
refreshedAt?: number refreshedAt?: number
openRegistrations?: boolean openRegistrations?: boolean
domain: string, domain: string
serverIps?: string[], serverIps?: string[]
geoip?: Geo[], geoip?: Geo[]
softwareName?: string; softwareName?: string
softwareVersion?: string softwareVersion?: string
standardizedSoftwareVersion?: string standardizedSoftwareVersion?: string
halfYearActiveUserCount?: number, halfYearActiveUserCount?: number
monthActiveUserCount?: number, monthActiveUserCount?: number
statusesCount?: number, statusesCount?: number
totalUserCount?: number, totalUserCount?: number
discoveredByDomain?:string, discoveredByDomain?: string
accountFeedCount?: number, accountFeedCount?: number
channelFeedCount?: number, channelFeedCount?: number
} }
export default Node export default Node

Wyświetl plik

@ -6,7 +6,7 @@ const elasticClient = new Client({
}, },
auth: { auth: {
username: process.env.ELASTIC_USER ?? 'elastic', username: process.env.ELASTIC_USER ?? 'elastic',
password: process.env.ELASTIC_PASSWORD password: process.env.ELASTIC_PASSWORD ?? ''
} }
}) })

Wyświetl plik

@ -1,7 +1,7 @@
import { ZodSchema } from 'zod' import { ZodSchema } from 'zod'
export function preserveUndefined<Source, Target> (cast: (value:Source)=>Target) { export function preserveUndefined<Source, Target> (cast: (value: Source) => Target) {
return (value:Source|undefined):Target|undefined => { return (value: Source | undefined): Target | undefined => {
if (value === undefined) { if (value === undefined) {
return undefined return undefined
} }
@ -9,8 +9,8 @@ export function preserveUndefined<Source, Target> (cast: (value:Source)=>Target)
} }
} }
export function preserveNull<Source, Target> (cast: (value:Source)=>Target) { export function preserveNull<Source, Target> (cast: (value: Source) => Target) {
return (value:Source|null):Target|null => { return (value: Source | null): Target | null => {
if (value === null) { if (value === null) {
return null return null
} }
@ -18,11 +18,11 @@ export function preserveNull<Source, Target> (cast: (value:Source)=>Target) {
} }
} }
export function undefinedToDefault<Type> (defaultValue:Type): (value:Type|undefined)=>Type { export function undefinedToDefault<Type> (defaultValue: Type): (value: Type | undefined) => Type {
return (value) => typeof value === 'undefined' ? defaultValue : value return (value) => typeof value === 'undefined' ? defaultValue : value
} }
export function stringTrimmed (value: string|undefined): string { export function stringTrimmed (value: string | undefined): string {
return (value ?? '').trim().replace(/^\++|\++$/g, '') return (value ?? '').trim().replace(/^\++|\++$/g, '')
} }
@ -30,7 +30,7 @@ export function stringToInt (value: string): number {
return parseInt(value) return parseInt(value)
} }
export function stringToBool (value:string):boolean { export function stringToBool (value: string): boolean {
switch (value) { switch (value) {
case 'true': case 'true':
case '1': case '1':
@ -42,7 +42,8 @@ export function stringToBool (value:string):boolean {
} }
} }
export function transform<Target> (originalSchema:ZodSchema<any>, cast:(value:unknown)=>Target, newSchema:ZodSchema<any>) { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function transform<Target> (originalSchema: ZodSchema<any>, cast: (value: unknown) => Target, newSchema: ZodSchema<any>) {
return originalSchema.refine( return originalSchema.refine(
value => { value => {
try { try {

Wyświetl plik

@ -1,12 +1,12 @@
import '../styles/global.scss' import '../styles/global.scss'
import { AppProps } from 'next/app' import { AppProps } from 'next/app'
import React from 'react' import React, { ReactElement } from 'react'
import { ApolloProvider } from '@apollo/client' import { ApolloProvider } from '@apollo/client'
import createGraphqlClient from '../graphql/client/createGraphqlClient' import createGraphqlClient from '../graphql/client/createGraphqlClient'
const graphqlClient = createGraphqlClient() const graphqlClient = createGraphqlClient()
const App = ({ Component, pageProps }: AppProps) => { const App = ({ Component, pageProps }: AppProps): ReactElement => {
return <ApolloProvider client={graphqlClient}> return <ApolloProvider client={graphqlClient}>
<Component {...pageProps} /> <Component {...pageProps} />
</ApolloProvider> </ApolloProvider>

Wyświetl plik

@ -8,7 +8,7 @@ const graphqlServer = createGraphqlServer()
const startedServer = graphqlServer.start() const startedServer = graphqlServer.start()
const handler = async (req: NextApiRequest, res: NextApiResponse) :Promise<void> => { const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
res.end() res.end()
return return

Wyświetl plik

@ -1,5 +1,5 @@
import Head from 'next/head' import Head from 'next/head'
import React, { useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../components/Loader' import Loader from '../components/Loader'
import FeedResults from '../components/FeedResults' import FeedResults from '../components/FeedResults'
import Layout, { siteTitle } from '../components/Layout' import Layout, { siteTitle } from '../components/Layout'
@ -16,7 +16,7 @@ import getMatomo from '../lib/getMatomo'
import { feedQueryInputSchema, FeedQueryInputType } from '../graphql/common/types/FeedQueryInput' import { feedQueryInputSchema, FeedQueryInputType } from '../graphql/common/types/FeedQueryInput'
import { ListFeedsVariables } from '../graphql/common/queries/listFeeds' import { ListFeedsVariables } from '../graphql/common/queries/listFeeds'
const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => { const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }): ReactElement => {
const router = useRouter() const router = useRouter()
const routerQuery = feedQueryInputSchema.parse(router.query) const routerQuery = feedQueryInputSchema.parse(router.query)
const [page, setPage] = useState<number>(0) const [page, setPage] = useState<number>(0)
@ -28,8 +28,9 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
query query
} }
}) })
useEffect(() => { useEffect((): void => {
router.push({ query }) router.push({ query })
.catch((error) => console.error(error))
getMatomo(matomoConfig).trackEvent({ getMatomo(matomoConfig).trackEvent({
category: 'feeds', category: 'feeds',
action: 'new-search' action: 'new-search'
@ -48,7 +49,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
}) })
}, [page]) }, [page])
const handleQueryChange = (event) => { const handleQueryChange = (event): void => {
const inputElement = event.target const inputElement = event.target
const value = inputElement.value const value = inputElement.value
const name = inputElement.name const name = inputElement.name
@ -60,7 +61,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
setPage(0) setPage(0)
} }
const handleSearchSubmit = async (event) => { const handleSearchSubmit = async (event): Promise<void> => {
event.preventDefault() event.preventDefault()
setPageLoading(true) setPageLoading(true)
setPage(0) setPage(0)
@ -68,7 +69,7 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
setPageLoading(false) setPageLoading(false)
} }
const handleLoadMore = async (event) => { const handleLoadMore = async (event): Promise<void> => {
event.preventDefault() event.preventDefault()
setPageLoading(true) setPageLoading(true)
await fetchMore({ await fetchMore({
@ -117,12 +118,12 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
<Loader loading={loading || pageLoading} showBottom={true}> <Loader loading={loading || pageLoading} showBottom={true}>
{ {
data && query.search (data != null) && query.search.length > 0
? <FeedResults feeds={data.listFeeds.items} /> ? <FeedResults feeds={data.listFeeds.items}/>
: '' : ''
} }
</Loader> </Loader>
{!loading && !pageLoading && data?.listFeeds?.paging?.hasNext {!loading && !pageLoading && data?.listFeeds?.paging?.hasNext !== undefined && data?.listFeeds?.paging?.hasNext
? ( ? (
<div className={'d-flex justify-content-center'}> <div className={'d-flex justify-content-center'}>
<button className={'btn btn-secondary'} onClick={handleLoadMore}> <button className={'btn btn-secondary'} onClick={handleLoadMore}>
@ -132,10 +133,10 @@ const Feeds: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
</div> </div>
) )
: ''} : ''}
{error {(error != null)
? (<div className={'d-flex justify-content-center'}> ? (<div className={'d-flex justify-content-center'}>
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/> <FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
<span>{error.message}</span> <span>{error.message}</span>
</div>) </div>)
: ''} : ''}
</Layout> </Layout>

Wyświetl plik

@ -21,7 +21,7 @@ import { NodeSoringByEnumType } from '../graphql/common/types/NodeSortingByEnum'
const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => { const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> = ({ matomoConfig }) => {
const router = useRouter() const router = useRouter()
let routerQuery:NodeQueryInputType let routerQuery: NodeQueryInputType
try { try {
routerQuery = nodeQueryInputSchema.parse(router.query) routerQuery = nodeQueryInputSchema.parse(router.query)
} catch (e) { } catch (e) {
@ -56,26 +56,26 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
] ]
}) })
}, [page]) }, [page])
useEffect(() => { useEffect((): void => {
router.push({ query }) void router.push({ query })
getMatomo(matomoConfig).trackEvent({ getMatomo(matomoConfig).trackEvent({
category: 'nodes', category: 'nodes',
action: 'new-search' action: 'new-search'
}) })
}, [query]) }, [query])
const handleQueryChange = (event) => { const handleQueryChange = (event): void => {
const targetInput = event.target const targetInput = event.target
const value = targetInput.value const value = targetInput.value
const name = targetInput.name const name = targetInput.name
const newQuery:NodeQueryInputType = { ...query } const newQuery: NodeQueryInputType = { ...query }
newQuery[name] = value newQuery[name] = value
console.info('Query changed', { name, value }) console.info('Query changed', { name, value })
setQuery(newQuery) setQuery(newQuery)
setPage(0) setPage(0)
} }
const handleSearchSubmit = async (event) => { const handleSearchSubmit = async (event): Promise<void> => {
setPageLoading(true) setPageLoading(true)
event.preventDefault() event.preventDefault()
setQuery(query) setQuery(query)
@ -84,7 +84,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
setPageLoading(false) setPageLoading(false)
} }
const handleLoadMore = async (event) => { const handleLoadMore = async (event): Promise<void> => {
event.preventDefault() event.preventDefault()
setPage(page + 1) setPage(page + 1)
console.info('Loading next page', { query, page }) console.info('Loading next page', { query, page })
@ -107,7 +107,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
setPageLoading(false) setPageLoading(false)
} }
const toggleSort = (sortBy: NodeSoringByEnumType) => { const toggleSort = (sortBy: NodeSoringByEnumType): void => {
const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc' const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc'
getMatomo(matomoConfig).trackEvent({ getMatomo(matomoConfig).trackEvent({
category: 'nodes', category: 'nodes',
@ -155,7 +155,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
</form> </form>
<Loader loading={loading || pageLoading} showBottom={true}> <Loader loading={loading || pageLoading} showBottom={true}>
{ {
data (data != null)
? ( ? (
<div className="table-responsive"> <div className="table-responsive">
<table className={'table table-dark table-striped table-bordered nodes'}> <table className={'table table-dark table-striped table-bordered nodes'}>
@ -212,7 +212,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{data.listNodes.items.length {(data.listNodes.items.length > 0)
? data.listNodes.items.map((node, index) => { ? data.listNodes.items.map((node, index) => {
return ( return (
<tr key={index}> <tr key={index}>
@ -228,7 +228,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
<td className={'text-end'}>{node.halfYearActiveUserCount ?? '?'}</td> <td className={'text-end'}>{node.halfYearActiveUserCount ?? '?'}</td>
<td className={'text-end'}>{node.statusesCount ?? '?'}</td> <td className={'text-end'}>{node.statusesCount ?? '?'}</td>
<td>{node.openRegistrations === null ? '?' : (node.openRegistrations ? 'Opened' : 'Closed')}</td> <td>{node.openRegistrations === null ? '?' : (node.openRegistrations ? 'Opened' : 'Closed')}</td>
<td>{node.refreshedAt ? (new Date(node.refreshedAt)).toLocaleDateString() : 'Never'}</td> <td>{node.refreshedAt !== '' ? (new Date(node.refreshedAt)).toLocaleDateString() : 'Never'}</td>
</tr> </tr>
) )
}) })
@ -244,7 +244,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
: '' : ''
} }
</Loader> </Loader>
{data?.listNodes?.paging?.hasNext && !loading && !pageLoading {!loading && !pageLoading && data?.listNodes?.paging?.hasNext !== undefined && data?.listNodes?.paging?.hasNext
? ( ? (
<div className={'d-flex justify-content-center'}> <div className={'d-flex justify-content-center'}>
<button className={'btn btn-secondary'} onClick={handleLoadMore}> <button className={'btn btn-secondary'} onClick={handleLoadMore}>
@ -254,7 +254,7 @@ const Nodes: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
</div> </div>
) )
: ''} : ''}
{error {(error != null)
? (<div className={'d-flex justify-content-center'}> ? (<div className={'d-flex justify-content-center'}>
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/> <FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
<span>{error.message}</span> <span>{error.message}</span>

Wyświetl plik

@ -37,14 +37,14 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
}) })
useEffect(() => { useEffect(() => {
router.push({ query }) void router.push({ query })
getMatomo(matomoConfig).trackEvent({ getMatomo(matomoConfig).trackEvent({
category: 'stats', category: 'stats',
action: 'new-search' action: 'new-search'
}) })
}, [query]) }, [query])
const toggleSort = (sortBy: StatsSoringByEnumType) => { const toggleSort = (sortBy: StatsSoringByEnumType): void => {
const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc' const sortWay = query.sortBy === sortBy && query.sortWay === 'asc' ? 'desc' : 'asc'
getMatomo(matomoConfig).trackEvent({ getMatomo(matomoConfig).trackEvent({
category: 'stats', category: 'stats',
@ -73,7 +73,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
accountFeedCount: 0, accountFeedCount: 0,
channelFeedCount: 0 channelFeedCount: 0
} }
if (data) { if (data != null) {
data.listStats.items.forEach(item => { data.listStats.items.forEach(item => {
if (item.softwareName === null) { if (item.softwareName === null) {
return return
@ -119,7 +119,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
</tr> </tr>
</thead> </thead>
<Loader loading={loading} table={4} showTop={true} hideContent={true}> <Loader loading={loading} table={4} showTop={true} hideContent={true}>
{!data {(data == null)
? ( ? (
<tbody> <tbody>
<tr> <tr>
@ -183,7 +183,7 @@ const Stats: React.FC<InferGetServerSidePropsType<typeof getServerSideProps>> =
} }
</Loader> </Loader>
</table> </table>
{error {(error != null)
? (<div className={'d-flex justify-content-center'}> ? (<div className={'d-flex justify-content-center'}>
<FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/> <FontAwesomeIcon icon={faExclamationTriangle} className={'margin-right'}/>
<span>{error.message}</span> <span>{error.message}</span>

Wyświetl plik

@ -1,4 +1,4 @@
export type Sort = { export interface Sort {
sortBy?: string, sortBy?: string
sortWay?: 'asc' | 'desc' sortWay?: 'asc' | 'desc'
} }

Wyświetl plik

@ -17,6 +17,7 @@
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"strictNullChecks": true,
"jsx": "preserve" "jsx": "preserve"
}, },
"include": [ "include": [

Wyświetl plik

@ -1,11 +0,0 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"no-console": false
},
"rulesDirectory": []
}

5576
application/yarn.lock 100644

Plik diff jest za duży Load Diff