mirror of
https://github.com/tloncorp/landscape.git
synced 2024-11-24 01:37:01 +03:00
vitals: add new connection check to frontend and improve treaty fetch
This commit is contained in:
parent
ff3c55e6a2
commit
733ce51ea8
2
desk/mar/run-check.hoon
Normal file
2
desk/mar/run-check.hoon
Normal file
@ -0,0 +1,2 @@
|
||||
/= mark /mar/ship
|
||||
mark
|
@ -26,12 +26,11 @@
|
||||
:- ['complete' [%s -.p.status.result]]
|
||||
?+ -.p.status.result ~
|
||||
%no-our-planet ['last-contact' (time:enjs last-contact.p.status.result)]~
|
||||
%no-our-sponsor ['last-contact' (time:enjs last-contact.p.status.result)]~
|
||||
%no-our-galaxy ['last-contact' (time:enjs last-contact.p.status.result)]~
|
||||
%no-sponsor-hit ['ship' (ship:enjs ship.p.status.result)]~
|
||||
%no-sponsor-miss ['ship' (ship:enjs ship.p.status.result)]~
|
||||
%no-their-galaxy ['last-contact' (time:enjs last-contact.p.status.result)]~
|
||||
%crash ['crash' (tang:enjs tang.p.status.result)]~
|
||||
%crash ['crash' a+(turn tang.p.status.result tank:enjs)]~
|
||||
==
|
||||
==
|
||||
==
|
||||
|
4
ui/package-lock.json
generated
4
ui/package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "landscape",
|
||||
"version": "0.0.0",
|
||||
"version": "1.11.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "landscape",
|
||||
"version": "0.0.0",
|
||||
"version": "1.11.0",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.348.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.348.0",
|
||||
|
79
ui/src/components/ShipConnection.tsx
Normal file
79
ui/src/components/ShipConnection.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import cn from 'classnames';
|
||||
import React from 'react';
|
||||
import {
|
||||
ConnectionCompleteStatus,
|
||||
ConnectionPendingStatus,
|
||||
ConnectionStatus,
|
||||
} from '@/state/vitals';
|
||||
import Bullet16Icon from './icons/Bullet16Icon';
|
||||
|
||||
interface ShipConnectionProps {
|
||||
ship: string;
|
||||
status?: ConnectionStatus;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function getCompletedText(status: ConnectionCompleteStatus, ship: string) {
|
||||
switch (status.complete) {
|
||||
case 'no-data':
|
||||
return 'No connection data';
|
||||
case 'yes':
|
||||
return 'Connected';
|
||||
case 'no-dns':
|
||||
return 'Unable to connect to DNS';
|
||||
case 'no-our-planet':
|
||||
return 'Unable to reach our planet';
|
||||
case 'no-our-galaxy':
|
||||
return 'Unable to reach our galaxy';
|
||||
case 'no-their-galaxy':
|
||||
return `Unable to reach ${ship}'s galaxy`;
|
||||
case 'no-sponsor-miss':
|
||||
return `${ship}'s sponsor can't reach them`;
|
||||
case 'no-sponsor-hit':
|
||||
return `${ship}'s sponsor can reach them, but we can't`;
|
||||
default:
|
||||
return 'Unable to connect';
|
||||
}
|
||||
}
|
||||
|
||||
function getPendingText(status: ConnectionPendingStatus, ship: string) {
|
||||
switch (status.pending) {
|
||||
case 'trying-dns':
|
||||
return 'Checking DNS';
|
||||
case 'trying-local':
|
||||
return 'Checking our galaxy';
|
||||
case 'trying-target':
|
||||
return `Checking ${ship}`;
|
||||
case 'trying-sponsor':
|
||||
return `Checking ${ship}'s sponsors (~${(status as any).ship})`;
|
||||
default:
|
||||
return 'Checking connection...';
|
||||
}
|
||||
}
|
||||
|
||||
function getConnectionColor(status?: ConnectionStatus) {
|
||||
if (!status || 'pending' in status) {
|
||||
return 'text-gray-400';
|
||||
}
|
||||
|
||||
return status.complete === 'yes' ? 'text-green-300' : 'text-red-400';
|
||||
}
|
||||
|
||||
export function ShipConnection({
|
||||
status,
|
||||
ship,
|
||||
className,
|
||||
}: ShipConnectionProps) {
|
||||
return (
|
||||
<span className={cn('flex text-base font-semibold', className)}>
|
||||
<Bullet16Icon
|
||||
className={cn('-ml-1 h-4 w-4', getConnectionColor(status))}
|
||||
/>{' '}
|
||||
{!status
|
||||
? 'No connection data'
|
||||
: 'pending' in status
|
||||
? getPendingText(status, ship)
|
||||
: getCompletedText(status, ship)}
|
||||
</span>
|
||||
);
|
||||
}
|
@ -8,6 +8,8 @@ import { useAppSearchStore } from '../Nav';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { addRecentDev } from './Home';
|
||||
import { Spinner } from '../../components/Spinner';
|
||||
import { ShipConnection } from '@/components/ShipConnection';
|
||||
import { pluralize } from '@/logic/utils';
|
||||
|
||||
type AppsProps = RouteComponentProps<{ ship: string }>;
|
||||
|
||||
@ -15,10 +17,12 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
const { searchInput, selectedMatch, select } = useAppSearchStore((state) => ({
|
||||
searchInput: state.searchInput,
|
||||
select: state.select,
|
||||
selectedMatch: state.selectedMatch
|
||||
selectedMatch: state.selectedMatch,
|
||||
}));
|
||||
const provider = match?.params.ship;
|
||||
const { treaties, status } = useAllyTreaties(provider);
|
||||
const { treaties, status, connection, awaiting } = useAllyTreaties(provider);
|
||||
console.log(status);
|
||||
const [showConnection, setShowConnection] = React.useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (provider) {
|
||||
@ -26,6 +30,16 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
}
|
||||
}, [provider]);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setShowConnection(true);
|
||||
}, 700);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const results = useMemo(() => {
|
||||
if (!treaties) {
|
||||
return undefined;
|
||||
@ -47,7 +61,8 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
const count = results?.length;
|
||||
|
||||
const getAppPath = useCallback(
|
||||
(app: Treaty) => `${match?.path.replace(':ship', provider)}/${app.ship}/${app.desk}`,
|
||||
(app: Treaty) =>
|
||||
`${match?.path.replace(':ship', provider)}/${app.ship}/${app.desk}`,
|
||||
[match]
|
||||
);
|
||||
|
||||
@ -66,44 +81,73 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
url: getAppPath(r),
|
||||
openInNewTab: false,
|
||||
value: r.desk,
|
||||
display: r.title
|
||||
}))
|
||||
display: r.title,
|
||||
})),
|
||||
});
|
||||
}
|
||||
}, [results]);
|
||||
|
||||
const showNone =
|
||||
status === 'error' || ((status === 'success' || status === 'initial') && results?.length === 0);
|
||||
const showLoader =
|
||||
status === 'loading' || status === 'initial' || status === 'awaiting';
|
||||
|
||||
return (
|
||||
<div className="dialog-inner-container md:px-6 md:py-8 h4 text-gray-400">
|
||||
{status === 'loading' && (
|
||||
<span className="mb-3">
|
||||
<Spinner className="w-7 h-7 mr-3" /> Finding software...
|
||||
</span>
|
||||
<div className="dialog-inner-container h4 text-gray-400 md:px-6 md:py-8">
|
||||
{showLoader && (
|
||||
<div className="mb-3 flex items-start">
|
||||
<Spinner className="mr-3 h-7 w-7 flex-none" />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<span>
|
||||
{status === 'awaiting'
|
||||
? `${awaiting} ${pluralize(
|
||||
'app',
|
||||
awaiting
|
||||
)} found, waiting for entries...`
|
||||
: 'Finding software...'}
|
||||
</span>
|
||||
{showConnection && (
|
||||
<ShipConnection ship={provider} status={connection?.status} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{results && results.length > 0 && (
|
||||
<>
|
||||
{(status === 'partial' || status === 'finished') &&
|
||||
results &&
|
||||
(results.length > 0 ? (
|
||||
<>
|
||||
<div id="developed-by">
|
||||
<h2 className="mb-3">
|
||||
Software developed by{' '}
|
||||
<ShipName name={provider} className="font-mono" />
|
||||
</h2>
|
||||
<p>
|
||||
{count} result{count === 1 ? '' : 's'}
|
||||
</p>
|
||||
</div>
|
||||
<AppList
|
||||
apps={results}
|
||||
labelledBy="developed-by"
|
||||
matchAgainst={selectedMatch}
|
||||
to={getAppPath}
|
||||
/>
|
||||
{status === 'finished' ? (
|
||||
<p>That's it!</p>
|
||||
) : (
|
||||
<p>Awaiting {awaiting} more</p>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div id="developed-by">
|
||||
<h2 className="mb-3">
|
||||
Software developed by <ShipName name={provider} className="font-mono" />
|
||||
Software developed by{' '}
|
||||
<ShipName name={provider} className="font-mono" />
|
||||
</h2>
|
||||
<p>
|
||||
{count} result{count === 1 ? '' : 's'}
|
||||
</p>
|
||||
<p>No apps found</p>
|
||||
</div>
|
||||
<AppList
|
||||
apps={results}
|
||||
labelledBy="developed-by"
|
||||
matchAgainst={selectedMatch}
|
||||
to={getAppPath}
|
||||
/>
|
||||
<p>That's it!</p>
|
||||
</>
|
||||
)}
|
||||
{showNone && (
|
||||
))}
|
||||
{status === 'error' && (
|
||||
<h2>
|
||||
Unable to find software developed by <ShipName name={provider} className="font-mono" />
|
||||
Unable to connect to{' '}
|
||||
<ShipName name={provider} className="font-mono" />
|
||||
</h2>
|
||||
)}
|
||||
</div>
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
import api from '@/api';
|
||||
import { normalizeUrbitColor } from '@/logic/utils';
|
||||
import { Status } from '@/logic/useAsyncCall';
|
||||
import { ConnectionStatus, useConnectivityCheck } from './vitals';
|
||||
|
||||
export interface ChargeWithDesk extends Charge {
|
||||
desk: string;
|
||||
@ -261,42 +262,43 @@ export function useAllies() {
|
||||
return useDocketState(selAllies);
|
||||
}
|
||||
|
||||
function getAllyTreatyStatus(
|
||||
treaties: Treaties,
|
||||
fetching: boolean,
|
||||
alliance?: string[],
|
||||
status?: ConnectionStatus
|
||||
): Status | 'awaiting' | 'partial' | 'finished' {
|
||||
const treatyCount = Object.keys(treaties).length;
|
||||
console.log(treatyCount, alliance?.length, fetching, status);
|
||||
if (alliance && alliance.length !== 0 && treatyCount === alliance.length) {
|
||||
return 'finished';
|
||||
}
|
||||
|
||||
if (treatyCount > 0) {
|
||||
return 'partial';
|
||||
}
|
||||
|
||||
if (!status || ('complete' in status && status.complete === 'no-data')) {
|
||||
return 'initial';
|
||||
}
|
||||
|
||||
if (fetching || 'pending' in status) {
|
||||
return 'loading';
|
||||
}
|
||||
|
||||
if ('complete' in status && status.complete === 'yes') {
|
||||
return alliance && alliance.length > 0 ? 'awaiting' : 'finished';
|
||||
}
|
||||
|
||||
return 'error';
|
||||
}
|
||||
|
||||
export function useAllyTreaties(ship: string) {
|
||||
const { data } = useConnectivityCheck(ship);
|
||||
const allies = useAllies();
|
||||
const isAllied = ship in allies;
|
||||
const [status, setStatus] = useState<Status>('initial');
|
||||
const [treaties, setTreaties] = useState<Treaties>();
|
||||
|
||||
useEffect(() => {
|
||||
if (Object.keys(allies).length > 0 && !isAllied) {
|
||||
setStatus('loading');
|
||||
useDocketState.getState().addAlly(ship);
|
||||
}
|
||||
}, [allies, isAllied, ship]);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchTreaties() {
|
||||
if (isAllied) {
|
||||
setStatus('loading');
|
||||
try {
|
||||
const newTreaties = await useDocketState
|
||||
.getState()
|
||||
.fetchAllyTreaties(ship);
|
||||
|
||||
if (Object.keys(newTreaties).length > 0) {
|
||||
setTreaties(newTreaties);
|
||||
setStatus('success');
|
||||
}
|
||||
} catch {
|
||||
setStatus('error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchTreaties();
|
||||
}, [ship, isAllied]);
|
||||
|
||||
const storeTreaties = useDocketState(
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const treaties = useDocketState(
|
||||
useCallback(
|
||||
(s) => {
|
||||
const charter = s.allies[ship];
|
||||
@ -305,27 +307,42 @@ export function useAllyTreaties(ship: string) {
|
||||
[ship]
|
||||
)
|
||||
);
|
||||
debugger;
|
||||
const status = getAllyTreatyStatus(
|
||||
treaties,
|
||||
fetching,
|
||||
allies[ship],
|
||||
data?.status
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setStatus('error');
|
||||
}, 30 * 1000); // wait 30 secs before timing out
|
||||
if (Object.keys(allies).length > 0 && !isAllied) {
|
||||
useDocketState.getState().addAlly(ship);
|
||||
}
|
||||
}, [allies, isAllied, ship]);
|
||||
|
||||
if (Object.keys(storeTreaties).length > 0) {
|
||||
setTreaties(storeTreaties);
|
||||
setStatus('success');
|
||||
clearTimeout(timeout);
|
||||
useEffect(() => {
|
||||
async function fetchTreaties() {
|
||||
try {
|
||||
setFetching(true);
|
||||
await useDocketState.getState().fetchAllyTreaties(ship);
|
||||
setFetching(false);
|
||||
} catch {
|
||||
console.log("couldn't fetch initial treaties");
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
}, [storeTreaties]);
|
||||
if (isAllied) {
|
||||
fetchTreaties();
|
||||
}
|
||||
}, [ship, isAllied]);
|
||||
|
||||
return {
|
||||
isAllied,
|
||||
treaties,
|
||||
status,
|
||||
connection: data,
|
||||
awaiting: allies[ship]?.length || 0 - Object.keys(treaties).length,
|
||||
};
|
||||
}
|
||||
|
||||
|
153
ui/src/state/vitals.ts
Normal file
153
ui/src/state/vitals.ts
Normal file
@ -0,0 +1,153 @@
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import api from '@/api';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
interface Connected {
|
||||
complete: 'yes';
|
||||
}
|
||||
|
||||
interface YetToCheck {
|
||||
complete: 'no-data';
|
||||
}
|
||||
|
||||
interface NoDNS {
|
||||
complete: 'no-dns';
|
||||
}
|
||||
|
||||
interface Crash {
|
||||
complete: 'crash';
|
||||
crash: string[][];
|
||||
}
|
||||
|
||||
interface NoOurPlanet {
|
||||
complete: 'no-our-planet';
|
||||
'last-contact': number;
|
||||
}
|
||||
|
||||
interface NoOurGalaxy {
|
||||
complete: 'no-our-galaxy';
|
||||
'last-contact': number;
|
||||
}
|
||||
|
||||
interface NoSponsorHit {
|
||||
complete: 'no-sponsor-hit';
|
||||
ship: string;
|
||||
}
|
||||
|
||||
interface NoSponsorMiss {
|
||||
complete: 'no-sponsor-miss';
|
||||
ship: string;
|
||||
}
|
||||
|
||||
interface NoTheirGalaxy {
|
||||
complete: 'no-their-galaxy';
|
||||
'last-contact': number;
|
||||
}
|
||||
|
||||
export type ConnectionCompleteStatusKey =
|
||||
| 'yes'
|
||||
| 'crash'
|
||||
| 'no-data'
|
||||
| 'no-dns'
|
||||
| 'no-our-planet'
|
||||
| 'no-our-galaxy'
|
||||
| 'no-sponsor-hit'
|
||||
| 'no-sponsor-miss'
|
||||
| 'no-their-galaxy';
|
||||
|
||||
export interface CompleteStatus {
|
||||
complete: ConnectionCompleteStatusKey;
|
||||
}
|
||||
|
||||
export type ConnectionCompleteStatus =
|
||||
| Connected
|
||||
| YetToCheck
|
||||
| Crash
|
||||
| NoDNS
|
||||
| NoOurPlanet
|
||||
| NoOurGalaxy
|
||||
| NoSponsorHit
|
||||
| NoSponsorMiss
|
||||
| NoTheirGalaxy;
|
||||
|
||||
export type ConnectionPendingStatusKey =
|
||||
| 'setting-up'
|
||||
| 'trying-dns'
|
||||
| 'trying-local'
|
||||
| 'trying-target'
|
||||
| 'trying-sponsor';
|
||||
|
||||
export type ConnectionPendingStatus =
|
||||
| {
|
||||
pending: Omit<ConnectionPendingStatusKey, 'trying-sponsor'>;
|
||||
}
|
||||
| {
|
||||
pending: 'trying-sponsor';
|
||||
ship: string;
|
||||
};
|
||||
|
||||
export type ConnectionStatus =
|
||||
| ConnectionCompleteStatus
|
||||
| ConnectionPendingStatus;
|
||||
|
||||
export interface ConnectionUpdate {
|
||||
status: ConnectionStatus;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export function useConnectivityCheck(ship: string, useStale = false) {
|
||||
const [subbed, setSubbed] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const query = useQuery(
|
||||
['vitals', ship],
|
||||
async (): Promise<ConnectionUpdate> => {
|
||||
const resp = await api.scry<ConnectionUpdate>({
|
||||
app: 'vitals',
|
||||
path: `/ship/${ship}`,
|
||||
});
|
||||
const now = Date.now();
|
||||
const diff = now - resp.timestamp;
|
||||
|
||||
// if status older than 30 seconds, re-run check
|
||||
if (diff > 30 * 1000 || !useStale) {
|
||||
api.poke({
|
||||
app: 'vitals',
|
||||
mark: 'run-check',
|
||||
json: ship,
|
||||
});
|
||||
|
||||
return {
|
||||
status: {
|
||||
pending: 'setting-up',
|
||||
},
|
||||
timestamp: now,
|
||||
};
|
||||
}
|
||||
|
||||
return resp;
|
||||
},
|
||||
{
|
||||
enabled: subbed,
|
||||
cacheTime: 0,
|
||||
initialData: {
|
||||
status: {
|
||||
pending: 'setting-up',
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
api.subscribe({
|
||||
app: 'vitals',
|
||||
path: `/status/${ship}`,
|
||||
event: (data: ConnectionUpdate) => {
|
||||
queryClient.setQueryData(['vitals', ship], data);
|
||||
},
|
||||
});
|
||||
setSubbed(true);
|
||||
}, [ship]);
|
||||
|
||||
return query;
|
||||
}
|
Loading…
Reference in New Issue
Block a user