refactor: remove the ability to update tipi through the ui
This commit is contained in:
parent
186951cd69
commit
2c0126db3f
12 changed files with 3 additions and 226 deletions
|
@ -52,29 +52,10 @@ describe('Test: StatusProvider', () => {
|
|||
unmount();
|
||||
});
|
||||
|
||||
it('should render StatusScreen when system is UPDATING', async () => {
|
||||
const { result, unmount } = renderHook(() => useSystemStore());
|
||||
act(() => {
|
||||
result.current.setStatus('UPDATING');
|
||||
});
|
||||
|
||||
render(
|
||||
<StatusProvider>
|
||||
<div>system running</div>
|
||||
</StatusProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
it('should reload the page when system is RUNNING after being something else than RUNNING', async () => {
|
||||
const { result, unmount } = renderHook(() => useSystemStore());
|
||||
act(() => {
|
||||
result.current.setStatus('UPDATING');
|
||||
result.current.setStatus('RESTARTING');
|
||||
});
|
||||
|
||||
render(
|
||||
|
@ -84,7 +65,7 @@ describe('Test: StatusProvider', () => {
|
|||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
|
||||
expect(screen.getByText('Your system is restarting...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
act(() => {
|
||||
|
|
|
@ -23,9 +23,6 @@ export const StatusProvider: React.FC<IProps> = ({ children }) => {
|
|||
if (status === 'RESTARTING') {
|
||||
s.current = 'RESTARTING';
|
||||
}
|
||||
if (status === 'UPDATING') {
|
||||
s.current = 'UPDATING';
|
||||
}
|
||||
}, [status, s, setPollStatus]);
|
||||
|
||||
if (s.current === 'LOADING') {
|
||||
|
@ -36,9 +33,5 @@ export const StatusProvider: React.FC<IProps> = ({ children }) => {
|
|||
return <StatusScreen title="Your system is restarting..." subtitle="Please do not refresh this page" />;
|
||||
}
|
||||
|
||||
if (s.current === 'UPDATING') {
|
||||
return <StatusScreen title="Your system is updating..." subtitle="Please do not refresh this page" />;
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"domain-already-in-use": "Domain {domain} is already in use by app {id}",
|
||||
"could-not-get-latest-version": "Could not get latest version",
|
||||
"current-version-is-latest": "Current version is already up to date",
|
||||
"major-version-update": "The major version has changed. Please update manually (instructions on GitHub)",
|
||||
"major-version-update": "The major version has changed. Please update manually (instructions in release notes)",
|
||||
"demo-mode-limit": "Only 6 apps can be installed in the demo mode. Please uninstall an other app to install a new one."
|
||||
},
|
||||
"success": {}
|
||||
|
@ -229,7 +229,6 @@
|
|||
"maintenance-title": "Maintenance",
|
||||
"maintenance-subtitle": "Common actions to perform on your instance",
|
||||
"restart": "Restart",
|
||||
"update": "Update to {version}",
|
||||
"already-latest": "Already up to date"
|
||||
},
|
||||
"settings": {
|
||||
|
|
|
@ -9,12 +9,6 @@ export const handlers = [
|
|||
type: 'query',
|
||||
response: { current: '1.0.0', latest: '1.0.0', body: 'hello' },
|
||||
}),
|
||||
getTRPCMock({
|
||||
path: ['system', 'update'],
|
||||
type: 'mutation',
|
||||
response: true,
|
||||
delay: 100,
|
||||
}),
|
||||
getTRPCMock({
|
||||
path: ['system', 'restart'],
|
||||
type: 'mutation',
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Button } from '../../../../components/ui/Button';
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from '../../../../components/ui/Dialog';
|
||||
|
||||
interface IProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export const UpdateModal: React.FC<IProps> = ({ isOpen, onClose, onConfirm, loading }) => (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent size="sm">
|
||||
<DialogHeader>
|
||||
<h5 className="modal-title">Update Tipi</h5>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
<div className="text-muted">Would you like to update Tipi to the latest version?</div>
|
||||
</DialogDescription>
|
||||
<DialogFooter>
|
||||
<Button onClick={onConfirm} className="btn-success" loading={loading}>
|
||||
Update
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
|
@ -1 +0,0 @@
|
|||
export { UpdateModal } from './UpdateModal';
|
|
@ -14,58 +14,6 @@ describe('Test: GeneralActions', () => {
|
|||
expect(screen.getByText('Actions')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show toast if update mutation fails', async () => {
|
||||
// arrange
|
||||
server.use(getTRPCMock({ path: ['system', 'getVersion'], response: { current: '1.0.0', latest: '2.0.0', body: '' } }));
|
||||
server.use(getTRPCMockError({ path: ['system', 'update'], type: 'mutation', status: 500, message: 'Something went wrong' }));
|
||||
render(<GeneralActions />);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
|
||||
});
|
||||
const updateButton = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButton);
|
||||
|
||||
// act
|
||||
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButtonModal);
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Something went wrong/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should set poll status to true if update mutation succeeds', async () => {
|
||||
// arrange
|
||||
server.use(getTRPCMock({ path: ['system', 'getVersion'], response: { current: '1.0.0', latest: '2.0.0', body: '' } }));
|
||||
server.use(getTRPCMock({ path: ['system', 'update'], type: 'mutation', response: true }));
|
||||
const { result } = renderHook(() => useSystemStore());
|
||||
result.current.setStatus('RUNNING');
|
||||
|
||||
render(
|
||||
<StatusProvider>
|
||||
<GeneralActions />
|
||||
</StatusProvider>,
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Update to 2.0.0')).toBeInTheDocument();
|
||||
});
|
||||
const updateButton = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButton);
|
||||
|
||||
// act
|
||||
const updateButtonModal = screen.getByRole('button', { name: /Update/i });
|
||||
fireEvent.click(updateButtonModal);
|
||||
|
||||
result.current.setStatus('UPDATING');
|
||||
|
||||
// assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Your system is updating...')).toBeInTheDocument();
|
||||
});
|
||||
expect(result.current.pollStatus).toBe(true);
|
||||
});
|
||||
|
||||
it('should show toast if restart mutation fails', async () => {
|
||||
// arrange
|
||||
server.use(getTRPCMockError({ path: ['system', 'restart'], type: 'mutation', status: 500, message: 'Something went badly' }));
|
||||
|
|
|
@ -8,7 +8,6 @@ import { MessageKey } from '@/server/utils/errors';
|
|||
import { Button } from '../../../../components/ui/Button';
|
||||
import { useDisclosure } from '../../../../hooks/useDisclosure';
|
||||
import { RestartModal } from '../../components/RestartModal';
|
||||
import { UpdateModal } from '../../components/UpdateModal/UpdateModal';
|
||||
import { trpc } from '../../../../utils/trpc';
|
||||
import { useSystemStore } from '../../../../state/systemStore';
|
||||
|
||||
|
@ -19,25 +18,10 @@ export const GeneralActions = () => {
|
|||
const [loading, setLoading] = React.useState(false);
|
||||
const { setPollStatus } = useSystemStore();
|
||||
const restartDisclosure = useDisclosure();
|
||||
const updateDisclosure = useDisclosure();
|
||||
|
||||
const defaultVersion = '0.0.0';
|
||||
const isLatest = semver.gte(versionQuery.data?.current || defaultVersion, versionQuery.data?.latest || defaultVersion);
|
||||
|
||||
const update = trpc.system.update.useMutation({
|
||||
onMutate: () => {
|
||||
setLoading(true);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
setPollStatus(true);
|
||||
},
|
||||
onError: (e) => {
|
||||
updateDisclosure.close();
|
||||
setLoading(false);
|
||||
toast.error(t(e.data?.tError.message as MessageKey, { ...e.data?.tError?.variables }));
|
||||
},
|
||||
});
|
||||
|
||||
const restart = trpc.system.restart.useMutation({
|
||||
onMutate: () => {
|
||||
setLoading(true);
|
||||
|
@ -71,9 +55,6 @@ export const GeneralActions = () => {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Button onClick={updateDisclosure.open} className="mt-3 mr-2 btn-success">
|
||||
{t('settings.actions.update', { version: versionQuery.data?.latest })}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -92,7 +73,6 @@ export const GeneralActions = () => {
|
|||
</div>
|
||||
</div>
|
||||
<RestartModal isOpen={restartDisclosure.isOpen} onClose={restartDisclosure.close} onConfirm={() => restart.mutate()} loading={loading} />
|
||||
<UpdateModal isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.close} onConfirm={() => update.mutate()} loading={loading} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ import { create } from 'zustand';
|
|||
const SYSTEM_STATUS = {
|
||||
RUNNING: 'RUNNING',
|
||||
RESTARTING: 'RESTARTING',
|
||||
UPDATING: 'UPDATING',
|
||||
LOADING: 'LOADING',
|
||||
} as const;
|
||||
export type SystemStatus = (typeof SYSTEM_STATUS)[keyof typeof SYSTEM_STATUS];
|
||||
|
|
|
@ -12,7 +12,6 @@ export const systemRouter = router({
|
|||
systemInfo: protectedProcedure.query(SystemServiceClass.systemInfo),
|
||||
getVersion: publicProcedure.query(SystemService.getVersion),
|
||||
restart: protectedProcedure.mutation(SystemService.restart),
|
||||
update: protectedProcedure.mutation(SystemService.update),
|
||||
updateSettings: protectedProcedure.input(settingsSchema).mutation(({ input }) => TipiConfig.setSettings(input)),
|
||||
getSettings: protectedProcedure.query(TipiConfig.getSettings),
|
||||
});
|
||||
|
|
|
@ -160,59 +160,3 @@ describe('Test: restart', () => {
|
|||
await expect(SystemService.restart()).rejects.toThrow('server-messages.errors.not-allowed-in-demo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: update', () => {
|
||||
it('Should return true', async () => {
|
||||
// Arrange
|
||||
EventDispatcher.dispatchEventAsync = jest.fn().mockResolvedValueOnce({ success: true });
|
||||
setConfig('version', '0.0.1');
|
||||
await cache.set('latestVersion', '0.0.2');
|
||||
|
||||
// Act
|
||||
const update = await SystemService.update();
|
||||
|
||||
// Assert
|
||||
expect(update).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Should throw an error if latest version is not set', async () => {
|
||||
// Arrange
|
||||
await cache.del('latestVersion');
|
||||
server.use(
|
||||
rest.get('https://api.github.com/repos/meienberger/runtipi/releases/latest', (_, res, ctx) => {
|
||||
return res(ctx.json({ name: null }));
|
||||
}),
|
||||
);
|
||||
setConfig('version', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.could-not-get-latest-version');
|
||||
});
|
||||
|
||||
it('Should throw if current version is higher than latest', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.2');
|
||||
await cache.set('latestVersion', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.current-version-is-latest');
|
||||
});
|
||||
|
||||
it('Should throw if current version is equal to latest', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.1');
|
||||
await cache.set('latestVersion', '0.0.1');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.current-version-is-latest');
|
||||
});
|
||||
|
||||
it('Should throw an error if there is a major version difference', async () => {
|
||||
// Arrange
|
||||
setConfig('version', '0.0.1');
|
||||
await cache.set('latestVersion', '1.0.0');
|
||||
|
||||
// Act & Assert
|
||||
await expect(SystemService.update()).rejects.toThrow('server-messages.errors.major-version-update');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import semver from 'semver';
|
||||
import { z } from 'zod';
|
||||
import axios from 'redaxios';
|
||||
import { TranslatedError } from '@/server/utils/errors';
|
||||
|
@ -84,36 +83,6 @@ export class SystemServiceClass {
|
|||
return info.data;
|
||||
};
|
||||
|
||||
public update = async (): Promise<boolean> => {
|
||||
const { current, latest } = await this.getVersion();
|
||||
|
||||
if (TipiConfig.getConfig().NODE_ENV === 'development') {
|
||||
throw new TranslatedError('server-messages.errors.not-allowed-in-dev');
|
||||
}
|
||||
|
||||
if (!latest) {
|
||||
throw new TranslatedError('server-messages.errors.could-not-get-latest-version');
|
||||
}
|
||||
|
||||
if (semver.gt(current, latest)) {
|
||||
throw new TranslatedError('server-messages.errors.current-version-is-latest');
|
||||
}
|
||||
|
||||
if (semver.eq(current, latest)) {
|
||||
throw new TranslatedError('server-messages.errors.current-version-is-latest');
|
||||
}
|
||||
|
||||
if (semver.major(current) !== semver.major(latest)) {
|
||||
throw new TranslatedError('server-messages.errors.major-version-update');
|
||||
}
|
||||
|
||||
TipiConfig.setConfig('status', 'UPDATING');
|
||||
|
||||
this.dispatcher.dispatchEvent({ type: 'system', command: 'update', version: latest });
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
public restart = async (): Promise<boolean> => {
|
||||
if (TipiConfig.getConfig().NODE_ENV === 'development') {
|
||||
throw new TranslatedError('server-messages.errors.not-allowed-in-dev');
|
||||
|
|
Loading…
Add table
Reference in a new issue