mirror of
https://github.com/urbit/shrub.git
synced 2025-01-03 01:54:43 +03:00
grid: lag, blocking from kiln
This commit is contained in:
parent
93a9926ad0
commit
63515c573a
@ -40,6 +40,8 @@ export const useLeapStore = create<LeapStore>((set) => ({
|
||||
})
|
||||
}));
|
||||
|
||||
window.leap = useLeapStore.getState;
|
||||
|
||||
export type MenuState =
|
||||
| 'closed'
|
||||
| 'search'
|
||||
|
@ -1,105 +1,23 @@
|
||||
import React, { FC, useEffect, useState } from 'react';
|
||||
import cn from 'classnames';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useLeapStore } from './Nav';
|
||||
import { useBlockers, useLag } from '../state/kiln';
|
||||
import { useCharge } from '../state/docket';
|
||||
import { DocketImage } from '../components/DocketImage';
|
||||
import api from '../state/api';
|
||||
import { kilnSuspend } from '../../../npm/api/hood';
|
||||
import { Button } from '../components/Button';
|
||||
import { useHarkStore } from '../state/hark';
|
||||
import { Notification } from '../state/hark-types';
|
||||
import { BasicNotification } from './notifications/BasicNotification';
|
||||
import { SystemNotification } from './notifications/SystemNotification';
|
||||
|
||||
interface INotification {
|
||||
title: string;
|
||||
body: string;
|
||||
actions: {
|
||||
title: string;
|
||||
role: 'primary' | 'destructive' | 'none';
|
||||
link: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface NotificationProps {
|
||||
notification?: INotification;
|
||||
read?: boolean;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Notification: FC<NotificationProps> = ({ className, children }) => (
|
||||
<div className={cn('rounded-md flex flex-col p-4', className)}>{children}</div>
|
||||
);
|
||||
|
||||
const LagNotification = () => (
|
||||
<Notification read={false} className="bg-orange-100">
|
||||
<p className="text-black leading-normal">
|
||||
The runtime of this ship is out of date, and preventing a kernel upgrade. Please upgrade to
|
||||
the latest runtime version. If you are hosted, please contact your hosting provider
|
||||
</p>
|
||||
</Notification>
|
||||
);
|
||||
|
||||
const DeskIcon = ({ desk }) => {
|
||||
const { title, image, color } = useCharge(desk);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<DocketImage small color={color} image={image} />
|
||||
<p className="text-black font-medium">{title}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface BlockNotificationProps {
|
||||
desks: string[];
|
||||
}
|
||||
const BlockNotification: React.FC<BlockNotificationProps> = ({ desks }) => {
|
||||
const count = desks.length;
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
const onArchive = async () => {
|
||||
await Promise.all(desks.map((d) => api.poke(kilnSuspend(d))));
|
||||
};
|
||||
|
||||
if (dismissed) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Notification className="bg-orange-100 flex-col space-y-4 p-6">
|
||||
<p className="text-black"> The following {desks.length} apps blocked a System Update: </p>
|
||||
<div className="flex flex-col space-y-4">
|
||||
{desks.map((desk) => (
|
||||
<DeskIcon key={desk} desk={desk} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-black">
|
||||
In order to proceed with the System Update, you’ll need to temporarily archive these apps,
|
||||
which will render them unusable, but with data intact.
|
||||
<br />
|
||||
<br />
|
||||
Archived apps will automatically un-archive and resume operation when their developer
|
||||
provides an app update.
|
||||
</p>
|
||||
<div className="flex space-x-4">
|
||||
<button type="button" onClick={() => setDismissed(true)}>
|
||||
Dismiss
|
||||
</button>
|
||||
<button type="button" onClick={onArchive}>
|
||||
Archive {count} apps and System Update
|
||||
</button>
|
||||
</div>
|
||||
</Notification>
|
||||
);
|
||||
};
|
||||
import {
|
||||
BaseBlockedNotification,
|
||||
RuntimeLagNotification
|
||||
} from './notifications/SystemNotification';
|
||||
|
||||
function renderNotification(notification: Notification, key: string) {
|
||||
if (notification.type === 'system-updates-blocked') {
|
||||
return <SystemNotification key={key} notification={notification} />;
|
||||
return <BaseBlockedNotification key={key} notification={notification} />;
|
||||
}
|
||||
if (notification.type === 'runtime-lag') {
|
||||
return <RuntimeLagNotification key={key} />;
|
||||
}
|
||||
|
||||
return <BasicNotification key={key} notification={notification} />;
|
||||
}
|
||||
|
||||
@ -109,22 +27,31 @@ const Empty = () => (
|
||||
</section>
|
||||
);
|
||||
|
||||
function getSystemNotifications(lag: boolean, blockers: string[]) {
|
||||
const nots = [] as Notification[];
|
||||
if (lag) {
|
||||
nots.push({ type: 'runtime-lag' });
|
||||
}
|
||||
if (blockers.length > 0) {
|
||||
nots.push({ type: 'system-updates-blocked', desks: blockers });
|
||||
}
|
||||
return nots;
|
||||
}
|
||||
|
||||
export const Notifications = () => {
|
||||
const select = useLeapStore((s) => s.select);
|
||||
const notifications = useHarkStore((s) => s.notifications);
|
||||
const hasNotifications = notifications.length > 0;
|
||||
const blockers = useBlockers();
|
||||
const lag = useLag();
|
||||
const systemNotifications = getSystemNotifications(lag, blockers);
|
||||
const hasNotifications = notifications.length > 0 || systemNotifications.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
select('Notifications');
|
||||
}, []);
|
||||
|
||||
const blockers = useBlockers();
|
||||
const lag = useLag();
|
||||
|
||||
return (
|
||||
<div className="p-4 md:p-8">
|
||||
{lag && <LagNotification />}
|
||||
{blockers.length > 0 ? <BlockNotification desks={blockers} /> : null}
|
||||
<header className="space-x-2 mb-8">
|
||||
<Button variant="secondary" className="py-1.5 px-6 rounded-full">
|
||||
Mark All as Read
|
||||
@ -141,8 +68,11 @@ export const Notifications = () => {
|
||||
|
||||
{!hasNotifications && <Empty />}
|
||||
{hasNotifications && (
|
||||
<section className="min-h-[480px] text-gray-400 space-y-2">
|
||||
<section className="min-h-[480px] text-gray-400 space-y-2 overflow-y-auto">
|
||||
{notifications.map((n, index) => renderNotification(n, index.toString()))}
|
||||
{systemNotifications.map((n, index) =>
|
||||
renderNotification(n, (notifications.length + index).toString())
|
||||
)}
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Route, RouteComponentProps, Switch } from 'react-router-dom';
|
||||
import { AppInfo } from './search/AppInfo';
|
||||
import { TreatyInfo } from './search/TreatyInfo';
|
||||
import { Apps } from './search/Apps';
|
||||
import { Home } from './search/Home';
|
||||
import { Providers } from './search/Providers';
|
||||
@ -12,7 +12,7 @@ type SearchProps = RouteComponentProps<{
|
||||
export const Search = ({ match }: SearchProps) => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.path}/:ship/apps/:host/:desk`} component={AppInfo} />
|
||||
<Route path={`${match.path}/:ship/apps/:host/:desk`} component={TreatyInfo} />
|
||||
<Route path={`${match.path}/:ship/apps`} component={Apps} />
|
||||
<Route path={`${match.path}/:ship`} component={Providers} />
|
||||
<Route path={`${match.path}`} component={Home} />
|
||||
|
@ -1,30 +1,58 @@
|
||||
import { pick } from 'lodash-es';
|
||||
import React, { useCallback } from 'react';
|
||||
import { kilnSuspend } from '@urbit/api/hood';
|
||||
import { AppList } from '../../components/AppList';
|
||||
import { Button } from '../../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../components/Dialog';
|
||||
import { Elbow } from '../../components/icons/Elbow';
|
||||
import api from '../../state/api';
|
||||
import { useCharges } from '../../state/docket';
|
||||
import { SystemNotification as SystemNotificationType } from '../../state/hark-types';
|
||||
import {
|
||||
BaseBlockedNotification as BaseBlockedNotificationType,
|
||||
} from '../../state/hark-types';
|
||||
|
||||
import { NotificationButton } from './NotificationButton';
|
||||
|
||||
interface SystemNotificationProps {
|
||||
notification: SystemNotificationType;
|
||||
interface BaseBlockedNotificationProps {
|
||||
notification: BaseBlockedNotificationType;
|
||||
}
|
||||
|
||||
export const SystemNotification = ({ notification }: SystemNotificationProps) => {
|
||||
const keys = notification.charges;
|
||||
export const RuntimeLagNotification = () => (
|
||||
<section
|
||||
className="notification pl-12 space-y-2 text-black bg-orange-50"
|
||||
aria-labelledby="runtime-lag"
|
||||
>
|
||||
<header id="system-updates-blocked" className="relative -left-8 space-y-2">
|
||||
<div className="flex space-x-2">
|
||||
<span className="inline-block w-6 h-6 bg-orange-500 rounded-full" />
|
||||
<span className="font-medium">Landscape</span>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Elbow className="w-6 h-6 text-gray-300" />
|
||||
<h2 id="runtime-lag">The runtime blocked a System Update</h2>
|
||||
</div>
|
||||
</header>
|
||||
<div className="space-y-6">
|
||||
<p>
|
||||
In order to proceed with the System Update, you’ll need to upgrade the runtime. If you are
|
||||
using a hosted ship, you should contact your hosting provider.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
export const BaseBlockedNotification = ({ notification }: BaseBlockedNotificationProps) => {
|
||||
const { desks } = notification;
|
||||
const charges = useCharges();
|
||||
const blockedCharges = Object.values(pick(charges, keys));
|
||||
const blockedCharges = Object.values(pick(charges, desks));
|
||||
const count = blockedCharges.length;
|
||||
|
||||
const handlePauseOTAs = useCallback(() => {
|
||||
console.log('pause updates');
|
||||
}, []);
|
||||
const handlePauseOTAs = useCallback(() => {}, []);
|
||||
|
||||
const handleArchiveApps = useCallback(() => {
|
||||
console.log('archive apps');
|
||||
}, []);
|
||||
const handleArchiveApps = useCallback(async () => {
|
||||
await Promise.all(desks.map(d => api.poke(kilnSuspend(d))));
|
||||
// TODO: retrigger OTA?
|
||||
}, [desks]);
|
||||
|
||||
return (
|
||||
<section
|
||||
|
@ -25,6 +25,7 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
}));
|
||||
const provider = match?.params.ship;
|
||||
const treaties = useAllyTreaties(provider);
|
||||
console.log(treaties);
|
||||
const results = useMemo(() => {
|
||||
if (!treaties) {
|
||||
return undefined;
|
||||
@ -46,12 +47,14 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
const count = results?.length;
|
||||
|
||||
useEffect(() => {
|
||||
const { fetchAllyTreaties } = useDocketState.getState();
|
||||
fetchAllyTreaties(provider);
|
||||
select(
|
||||
<>
|
||||
Apps by <ShipName name={provider} className="font-mono" />
|
||||
</>
|
||||
);
|
||||
}, []);
|
||||
}, [provider]);
|
||||
|
||||
useEffect(() => {
|
||||
if (results) {
|
||||
@ -82,7 +85,7 @@ export const Apps = ({ match }: AppsProps) => {
|
||||
apps={results}
|
||||
labelledBy="developed-by"
|
||||
matchAgainst={selectedMatch}
|
||||
to={(app) => `${match?.path.replace(':ship', provider)}/${slugify(app.base)}`}
|
||||
to={(app) => `${match?.path.replace(':ship', provider)}/${app.ship}/${slugify(app.desk)}`}
|
||||
/>
|
||||
)}
|
||||
<p>That's it!</p>
|
||||
|
@ -58,9 +58,19 @@ export const useRecentsStore = create<RecentsStore>(
|
||||
)
|
||||
);
|
||||
|
||||
window.recents = useRecentsStore.getState;
|
||||
|
||||
export function addRecentDev(dev: Provider) {
|
||||
return useRecentsStore.getState().addRecentDev(dev);
|
||||
}
|
||||
|
||||
export function addRecentApp(docket: Docket) {
|
||||
return useRecentsStore.getState().addRecentApp(docket);
|
||||
}
|
||||
|
||||
export const Home = () => {
|
||||
const selectedMatch = useLeapStore((state) => state.selectedMatch);
|
||||
const { recentApps, recentDevs, addRecentApp, addRecentDev } = useRecentsStore();
|
||||
const { recentApps, recentDevs } = useRecentsStore();
|
||||
const charges = useCharges();
|
||||
const groups = charges?.groups;
|
||||
const zod = { shipName: '~zod' };
|
||||
|
@ -1,27 +1,17 @@
|
||||
import { chadIsRunning } from '@urbit/api/docket';
|
||||
import clipboardCopy from 'clipboard-copy';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Button, PillButton } from '../../components/Button';
|
||||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from '../../components/Dialog';
|
||||
import { DocketHeader } from '../../components/DocketHeader';
|
||||
import { AppInfo } from '../../components/AppInfo';
|
||||
import { ShipName } from '../../components/ShipName';
|
||||
import { Spinner } from '../../components/Spinner';
|
||||
import { TreatyMeta } from '../../components/TreatyMeta';
|
||||
import useDocketState, { useCharges, useTreaty } from '../../state/docket';
|
||||
import { getAppHref } from '../../state/util';
|
||||
import { useCharge, useTreaty } from '../../state/docket';
|
||||
import { useVat } from '../../state/kiln';
|
||||
import { useLeapStore } from '../Nav';
|
||||
import { useRecentsStore } from './Home';
|
||||
|
||||
export const AppInfo = () => {
|
||||
const addRecentApp = useRecentsStore((state) => state.addRecentApp);
|
||||
export const TreatyInfo = () => {
|
||||
const select = useLeapStore((state) => state.select);
|
||||
const { ship, host, desk } = useParams<{ ship: string; host: string; desk: string }>();
|
||||
const treaty = useTreaty(host, desk);
|
||||
const charges = useCharges();
|
||||
const charge = (charges || {})[desk];
|
||||
const installed = charge && chadIsRunning(charge.chad);
|
||||
const installing = charge && 'install' in charge.chad;
|
||||
const vat = useVat(desk);
|
||||
const charge = useCharge(desk);
|
||||
|
||||
useEffect(() => {
|
||||
select(
|
||||
@ -31,13 +21,6 @@ export const AppInfo = () => {
|
||||
);
|
||||
}, [treaty?.title]);
|
||||
|
||||
const installApp = async () => {
|
||||
await useDocketState.getState().installDocket(ship, desk);
|
||||
};
|
||||
const copyApp = () => {
|
||||
clipboardCopy(`web+urbitgraph://app/${ship}/${desk}`);
|
||||
};
|
||||
|
||||
if (!treaty) {
|
||||
// TODO: maybe replace spinner with skeletons
|
||||
return (
|
||||
@ -46,8 +29,11 @@ export const AppInfo = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <AppInfo className="dialog-inner-container" docket={vat ? charge : treaty} vat={vat} />;
|
||||
|
||||
/*
|
||||
return (
|
||||
|
||||
<div className="dialog-inner-container text-black">
|
||||
<DocketHeader docket={treaty}>
|
||||
<div className="col-span-2 md:col-span-1 flex items-center space-x-4">
|
||||
@ -100,4 +86,5 @@ export const AppInfo = () => {
|
||||
<TreatyMeta treaty={treaty} />
|
||||
</div>
|
||||
);
|
||||
*/
|
||||
};
|
@ -1,14 +1,13 @@
|
||||
import { map, omit } from 'lodash-es';
|
||||
import React, { FunctionComponent, useEffect } from 'react';
|
||||
import { Route, RouteComponentProps } from 'react-router-dom';
|
||||
import { KilnDiff } from '@urbit/api/hood';
|
||||
import { MenuState, Nav } from '../nav/Nav';
|
||||
import useDocketState, { useCharges } from '../state/docket';
|
||||
import { useKilnState } from '../state/kiln';
|
||||
import { RemoveApp } from '../tiles/RemoveApp';
|
||||
import { SuspendApp } from '../tiles/SuspendApp';
|
||||
import { Tile } from '../tiles/Tile';
|
||||
import api from '../state/api';
|
||||
import { TileInfo } from '../tiles/TileInfo';
|
||||
|
||||
type GridProps = RouteComponentProps<{
|
||||
menu?: MenuState;
|
||||
@ -25,22 +24,6 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
|
||||
fetchAllies();
|
||||
fetchVats();
|
||||
fetchLag();
|
||||
api
|
||||
.subscribe({
|
||||
app: 'hood',
|
||||
path: '/kiln/vats',
|
||||
event: (data: KilnDiff) => {
|
||||
console.log(data);
|
||||
},
|
||||
err: () => {},
|
||||
quit: () => {}
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log(e);
|
||||
})
|
||||
.then((r) => {
|
||||
console.log(r);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
@ -59,6 +42,9 @@ export const Grid: FunctionComponent<GridProps> = ({ match }) => {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<Route exact path="/app/:desk">
|
||||
<TileInfo />
|
||||
</Route>
|
||||
<Route exact path="/app/:desk/suspend">
|
||||
<SuspendApp />
|
||||
</Route>
|
||||
|
21
pkg/grid/src/tiles/TileInfo.tsx
Normal file
21
pkg/grid/src/tiles/TileInfo.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { Dialog, DialogContent } from '../components/Dialog';
|
||||
import { AppInfo } from '../components/AppInfo';
|
||||
import { useCharge } from '../state/docket';
|
||||
import { useVat } from '../state/kiln';
|
||||
|
||||
export const TileInfo = () => {
|
||||
const { desk } = useParams<{ desk: string }>();
|
||||
const { push } = useHistory();
|
||||
const charge = useCharge(desk);
|
||||
const vat = useVat(desk);
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && push('/')}>
|
||||
<DialogContent>
|
||||
<AppInfo vat={vat} docket={charge} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
Loading…
Reference in New Issue
Block a user