kopia lustrzana https://github.com/Stopka/fedisearch
Porównaj commity
2 Commity
82e84c09fe
...
b8f5d28dd5
Autor | SHA1 | Data |
---|---|---|
Štěpán Škorpil | b8f5d28dd5 | |
Štěpán Škorpil | 5e3e152fa6 |
|
@ -1,2 +1,3 @@
|
|||
application/node_modules
|
||||
application/src/.next
|
||||
application/src/.vscode
|
||||
|
|
14
Dockerfile
14
Dockerfile
|
@ -2,12 +2,13 @@ FROM node:18-bullseye AS prebuild
|
|||
FROM prebuild AS build
|
||||
WORKDIR /srv
|
||||
COPY application/package*.json ./
|
||||
RUN npm install --frozen-lockfile
|
||||
RUN yarn install
|
||||
COPY application/. .
|
||||
RUN npm run build
|
||||
RUN chmod -R uog+r .
|
||||
RUN yarn build
|
||||
|
||||
FROM build as dev
|
||||
CMD npm run dev
|
||||
CMD yarn dev
|
||||
|
||||
FROM prebuild AS prod
|
||||
RUN groupadd -g 1001 nodejs
|
||||
|
@ -17,6 +18,7 @@ EXPOSE 3000
|
|||
WORKDIR /srv
|
||||
COPY --from=build /srv/node_modules ./node_modules
|
||||
COPY --from=build /srv/package*.json ./
|
||||
COPY --from=build --chown=nextjs:nodejs /srv/src/.next ./.next
|
||||
COPY --from=build /srv/src/public ./public
|
||||
CMD node_modules/.bin/next start
|
||||
COPY --from=build /srv/next.config.js ./
|
||||
COPY --from=build --chown=nextjs:nodejs /srv/src/.next ./src/.next
|
||||
COPY --from=build /srv/src/public ./src/public
|
||||
CMD yarn start
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import React, {ReactElement, ReactNode} from "react";
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
|
||||
export default function ResponsiveTable({children, className}: {
|
||||
children: ReactNode,
|
||||
className?: string
|
||||
export default function ResponsiveTable ({ children, className }: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
return (
|
||||
return (
|
||||
<div className={'table-responsive'}>
|
||||
<table className={`table table-dark table-striped table-bordered nodes ${className??''}`}>
|
||||
<table className={`table table-dark table-striped table-bordered nodes ${className ?? ''}`}>
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
'use client'
|
||||
import {faCircle} from "@fortawesome/free-solid-svg-icons";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React, {ReactElement} from "react";
|
||||
import { faCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
export default function SoftwareBadgePlaceholder():ReactElement{
|
||||
return <div className={'software-name placeholder-glow'}>
|
||||
export default function SoftwareBadgePlaceholder (): ReactElement {
|
||||
return <div className={'software-name placeholder-glow'}>
|
||||
<FontAwesomeIcon icon={faCircle} className={'icon'}/>
|
||||
<span className={'value placeholder col-6'}/>
|
||||
</div>
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
'use client'
|
||||
import React, {ReactElement, ReactNode, useContext, useState} from "react";
|
||||
import React, { ReactElement, ReactNode, useContext, useState } from 'react'
|
||||
|
||||
const AccordionContext = React.createContext<{
|
||||
expandedId: string | undefined,
|
||||
setExpandedId: (id: string | undefined) => void
|
||||
expandedId: string | undefined
|
||||
setExpandedId: (id: string | undefined) => void
|
||||
} | undefined>(undefined)
|
||||
|
||||
export const useAccordion = (id: string): [boolean, () => void] => {
|
||||
const context = useContext(AccordionContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('Hook useAccordion needs to be used in Accordion element')
|
||||
const context = useContext(AccordionContext)
|
||||
if (context === undefined) {
|
||||
throw new Error('Hook useAccordion needs to be used in Accordion element')
|
||||
}
|
||||
const { expandedId, setExpandedId } = context
|
||||
return [
|
||||
expandedId === id,
|
||||
() => {
|
||||
setExpandedId(expandedId === id ? undefined : id)
|
||||
}
|
||||
const {expandedId, setExpandedId} = context;
|
||||
return [
|
||||
expandedId === id,
|
||||
() => {
|
||||
setExpandedId(expandedId === id ? undefined : id)
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
export default function Accordion({children}: {
|
||||
children: ReactNode
|
||||
export default function Accordion ({ children }: {
|
||||
children: ReactNode
|
||||
}): ReactElement {
|
||||
const [expandedId, setExpandedId] = useState<string | undefined>(undefined)
|
||||
return <AccordionContext.Provider value={{expandedId, setExpandedId}}>
|
||||
const [expandedId, setExpandedId] = useState<string | undefined>(undefined)
|
||||
return <AccordionContext.Provider value={{ expandedId, setExpandedId }}>
|
||||
<div className="accordion" id="accordionExample">
|
||||
{children}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
'use client'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { FeedQueryInput } from '../../graphql/generated/types'
|
||||
import SearchInput from "../form/SearchInput";
|
||||
import SubmitButton from "../form/SubmitButton";
|
||||
import SearchInput from '../form/SearchInput'
|
||||
import SubmitButton from '../form/SubmitButton'
|
||||
|
||||
export default function FeedForm (
|
||||
{ onSubmit, onQueryChange, query }: {
|
||||
|
@ -42,7 +41,7 @@ export default function FeedForm (
|
|||
<SubmitButton
|
||||
faIcon={faSearch}
|
||||
label={'Search'}
|
||||
id={"search-feeds-button"}
|
||||
id={'search-feeds-button'}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {ReactElement, ReactNode} from "react";
|
||||
import React, { ReactElement, ReactNode } from 'react'
|
||||
|
||||
export default function FeedInfo({children, show}: { children?: ReactNode, show?: boolean }): ReactElement {
|
||||
if (show === false) {
|
||||
return <>{children}</>
|
||||
}
|
||||
return <></>
|
||||
export default function FeedInfo ({ children, show }: { children?: ReactNode, show?: boolean }): ReactElement {
|
||||
if (show === false) {
|
||||
return <>{children}</>
|
||||
}
|
||||
return <></>
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { faCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React, { ReactElement } from 'react'
|
||||
import SoftwareBadge from "../SoftwareBadge";
|
||||
import SoftwareBadgePlaceholder from "../SoftwareBadgePlaceholder";
|
||||
import SoftwareBadgePlaceholder from '../SoftwareBadgePlaceholder'
|
||||
import Avatar from './Avatar'
|
||||
import Badge from './badges/Badge'
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, {ChangeEventHandler, ReactElement} from "react";
|
||||
import React, { ChangeEventHandler, ReactElement } from 'react'
|
||||
|
||||
export default function SearchInput({label, onChange, value, describedBy}: {
|
||||
label: string,
|
||||
onChange?: ChangeEventHandler,
|
||||
value?: string,
|
||||
describedBy?: string
|
||||
export default function SearchInput ({ label, onChange, value, describedBy }: {
|
||||
label: string
|
||||
onChange?: ChangeEventHandler
|
||||
value?: string
|
||||
describedBy?: string
|
||||
}): ReactElement {
|
||||
return <input
|
||||
return <input
|
||||
name={'search'}
|
||||
id={'search'}
|
||||
type={'search'}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import {IconProp} from "@fortawesome/fontawesome-svg-core";
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
|
||||
import React, {ReactElement} from "react";
|
||||
import { IconProp } from '@fortawesome/fontawesome-svg-core'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
export default function SubmitButton({faIcon, label, id}: {
|
||||
faIcon: IconProp,
|
||||
label: string,
|
||||
id?: string
|
||||
export default function SubmitButton ({ faIcon, label, id }: {
|
||||
faIcon: IconProp
|
||||
label: string
|
||||
id?: string
|
||||
}): ReactElement {
|
||||
return <button type={'submit'} className={'btn btn-primary'} id={id}>
|
||||
return <button type={'submit'} className={'btn btn-primary'} id={id}>
|
||||
<FontAwesomeIcon icon={faIcon}/>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
|
|
|
@ -1,35 +1,35 @@
|
|||
'use client'
|
||||
import {faSearch} from '@fortawesome/free-solid-svg-icons'
|
||||
import React, {ReactElement} from 'react'
|
||||
import {NodeQueryInput} from '../../graphql/generated/types'
|
||||
import SearchInput from "../form/SearchInput";
|
||||
import SubmitButton from "../form/SubmitButton";
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { NodeQueryInput } from '../../graphql/generated/types'
|
||||
import SearchInput from '../form/SearchInput'
|
||||
import SubmitButton from '../form/SubmitButton'
|
||||
|
||||
export default function NodeForm(
|
||||
{onSubmit, onQueryChange, query}: {
|
||||
onSubmit: () => void
|
||||
onQueryChange: (query: NodeQueryInput) => void
|
||||
query: NodeQueryInput
|
||||
}
|
||||
export default function NodeForm (
|
||||
{ onSubmit, onQueryChange, query }: {
|
||||
onSubmit: () => void
|
||||
onQueryChange: (query: NodeQueryInput) => void
|
||||
query: NodeQueryInput
|
||||
}
|
||||
): ReactElement {
|
||||
const handleQueryChange = (event): void => {
|
||||
const inputElement = event.target
|
||||
const value = inputElement.value
|
||||
const name = inputElement.name
|
||||
const newQuery = {
|
||||
...query
|
||||
}
|
||||
newQuery[name] = value
|
||||
onQueryChange(newQuery)
|
||||
event.preventDefault()
|
||||
const handleQueryChange = (event): void => {
|
||||
const inputElement = event.target
|
||||
const value = inputElement.value
|
||||
const name = inputElement.name
|
||||
const newQuery = {
|
||||
...query
|
||||
}
|
||||
newQuery[name] = value
|
||||
onQueryChange(newQuery)
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const handleSubmit = (event): void => {
|
||||
event.preventDefault()
|
||||
onSubmit()
|
||||
}
|
||||
const handleSubmit = (event): void => {
|
||||
event.preventDefault()
|
||||
onSubmit()
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className={'input-group mb-3'}>
|
||||
<SearchInput
|
||||
|
@ -45,5 +45,5 @@ export default function NodeForm(
|
|||
/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import React, {ReactElement} from "react";
|
||||
import {NodeQueryInput, NodeSortingByEnum} from "../../graphql/generated/types";
|
||||
import SortToggle from "../SortToggle";
|
||||
import React, { ReactElement } from 'react'
|
||||
import { NodeQueryInput, NodeSortingByEnum } from '../../graphql/generated/types'
|
||||
import SortToggle from '../SortToggle'
|
||||
|
||||
export default function NodeHeader({query,onSortToggle}:{
|
||||
query: NodeQueryInput
|
||||
onSortToggle: (sortBy: NodeSortingByEnum)=> void
|
||||
}):ReactElement{
|
||||
return (
|
||||
export default function NodeHeader ({ query, onSortToggle }: {
|
||||
query: NodeQueryInput
|
||||
onSortToggle: (sortBy: NodeSortingByEnum) => void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan={2}>
|
||||
|
@ -59,5 +59,5 @@ export default function NodeHeader({query,onSortToggle}:{
|
|||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
import React, {ReactElement} from "react";
|
||||
import {ListNodesItemFragment} from "../../graphql/generated/types";
|
||||
import NodeResult from "./NodeResult";
|
||||
import React, { ReactElement } from 'react'
|
||||
import { ListNodesItemFragment } from '../../graphql/generated/types'
|
||||
import NodeResult from './NodeResult'
|
||||
|
||||
export default function NodeResults({nodes}:{
|
||||
nodes:ListNodesItemFragment[]|undefined,
|
||||
}):ReactElement{
|
||||
if(nodes === undefined){
|
||||
return <></>
|
||||
}
|
||||
export default function NodeResults ({ nodes }: {
|
||||
nodes: ListNodesItemFragment[] | undefined
|
||||
}): ReactElement {
|
||||
if (nodes === undefined) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
return (
|
||||
<tbody>
|
||||
{(nodes.length > 0)
|
||||
? nodes.map((node, index) => {
|
||||
return (
|
||||
? nodes.map((node, index) => {
|
||||
return (
|
||||
<NodeResult node={node} key={index}/>
|
||||
)
|
||||
})
|
||||
: (
|
||||
)
|
||||
})
|
||||
: (
|
||||
<tr>
|
||||
<td colSpan={9}>No servers found</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use client'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { ReactElement, useEffect, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import {
|
||||
ListStatsDocument,
|
||||
SortingWayEnum, StatsAggregationFragment,
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import {ReactElement} from "react";
|
||||
import {StatsAggregationFragment} from "../../graphql/generated/types";
|
||||
import React, { ReactElement } from 'react'
|
||||
import { StatsAggregationFragment } from '../../graphql/generated/types'
|
||||
|
||||
export default function StatsFooter({sumAggregation}: { sumAggregation: StatsAggregationFragment | undefined }): ReactElement {
|
||||
return (
|
||||
export default function StatsFooter ({ sumAggregation }: { sumAggregation: StatsAggregationFragment | undefined }): ReactElement {
|
||||
return (
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th>Summary</th>
|
||||
<th className={'text-end'}>{sumAggregation?.nodeCount??0}</th>
|
||||
<th className={'text-end'}>{sumAggregation?.accountFeedCount??0}</th>
|
||||
<th className={'text-end'}>{sumAggregation?.channelFeedCount??0}</th>
|
||||
<th className={'text-end'}>{sumAggregation?.nodeCount ?? 0}</th>
|
||||
<th className={'text-end'}>{sumAggregation?.accountFeedCount ?? 0}</th>
|
||||
<th className={'text-end'}>{sumAggregation?.channelFeedCount ?? 0}</th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'use client'
|
||||
import {ReactElement} from "react";
|
||||
import {NodeSortingByEnum, StatsQueryInput, StatsSortingByEnum} from "../../graphql/generated/types";
|
||||
import SortToggle from "../SortToggle";
|
||||
import React, { ReactElement } from 'react'
|
||||
import { StatsQueryInput, StatsSortingByEnum } from '../../graphql/generated/types'
|
||||
import SortToggle from '../SortToggle'
|
||||
|
||||
export default function StatsHeader({query,onSortToggle}: {
|
||||
query: StatsQueryInput,
|
||||
onSortToggle: (sortBy: StatsSortingByEnum) => void
|
||||
export default function StatsHeader ({ query, onSortToggle }: {
|
||||
query: StatsQueryInput
|
||||
onSortToggle: (sortBy: StatsSortingByEnum) => void
|
||||
}): ReactElement {
|
||||
return <thead>
|
||||
return <thead>
|
||||
<tr>
|
||||
<th>
|
||||
<SortToggle onToggle={onSortToggle} field={'softwareName'} sort={query}>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ReactElement } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import { StatsAggregationFragment, StatsItemFragment } from '../../graphql/generated/types'
|
||||
import StatsResult from './StatsResult'
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue