feat: add version to appshell
This commit is contained in:
parent
1d42d922bd
commit
45541a3cdd
13 changed files with 152 additions and 156 deletions
|
@ -25,6 +25,7 @@
|
|||
- Discord embeds (OG metadata)
|
||||
- Gallery viewer, and multiple file format support
|
||||
- Code highlighting
|
||||
- Fully customizable Discord webhook notifications
|
||||
- Easy setup instructions on [docs](https://zipl.vercel.app/) (One command install `docker-compose up -d`)
|
||||
|
||||
# Usage
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "zipline",
|
||||
"version": "3.5.0",
|
||||
"version": "3.4.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "REACT_EDITOR=code NODE_ENV=development tsx src/server",
|
||||
|
@ -15,7 +15,7 @@
|
|||
"docker:build-dev": "docker-compose --file docker-compose.dev.yml up --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@dicedtomato/mantine-data-grid": "0.0.20",
|
||||
"@dicedtomato/mantine-data-grid": "0.0.21",
|
||||
"@emotion/react": "^11.9.3",
|
||||
"@emotion/server": "^11.4.0",
|
||||
"@iarna/toml": "2.2.5",
|
||||
|
@ -75,4 +75,4 @@
|
|||
"url": "https://github.com/diced/zipline.git"
|
||||
},
|
||||
"packageManager": "yarn@3.2.1"
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { AppShell, Box, Burger, Button, Divider, Header, MediaQuery, Navbar, NavLink, Paper, Popover, ScrollArea, Select, Stack, Text, Title, UnstyledButton, useMantineTheme, Group, Image } from '@mantine/core';
|
||||
import { AppShell, Box, Burger, Button, Divider, Header, MediaQuery, Navbar, NavLink, Paper, Popover, ScrollArea, Select, Stack, Text, Title, UnstyledButton, useMantineTheme, Group, Image, Tooltip, Badge } from '@mantine/core';
|
||||
import { useClipboard } from '@mantine/hooks';
|
||||
import { useModals } from '@mantine/modals';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
|
@ -7,7 +7,7 @@ import { updateUser } from 'lib/redux/reducers/user';
|
|||
import { useStoreDispatch } from 'lib/redux/store';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { ActivityIcon, CheckIcon, CopyIcon, CrossIcon, DeleteIcon, FileIcon, HomeIcon, LinkIcon, LogoutIcon, PencilIcon, SettingsIcon, TagIcon, TypeIcon, UploadIcon, UserIcon } from './icons';
|
||||
import { friendlyThemeName, themes } from './Theming';
|
||||
|
||||
|
@ -111,10 +111,11 @@ const admin_items = [
|
|||
export default function Layout({ children, user, title }) {
|
||||
const [token, setToken] = useState(user?.token);
|
||||
const [systemTheme, setSystemTheme] = useState(user.systemTheme ?? 'system');
|
||||
const [avatar, setAvatar] = useState(user.avatar ?? null);
|
||||
const [version, setVersion] = useState<{ local: string, upstream: string }>(null);
|
||||
const [opened, setOpened] = useState(false); // navigation open
|
||||
const [open, setOpen] = useState(false); // manage acc dropdown
|
||||
|
||||
const avatar = user?.avatar ?? null;
|
||||
const router = useRouter();
|
||||
const dispatch = useStoreDispatch();
|
||||
const theme = useMantineTheme();
|
||||
|
@ -191,6 +192,15 @@ export default function Layout({ children, user, title }) {
|
|||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const data = await useFetch('/api/version');
|
||||
if (!data.error) {
|
||||
setVersion(data);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AppShell
|
||||
navbarOffsetBreakpoint='sm'
|
||||
|
@ -238,6 +248,27 @@ export default function Layout({ children, user, title }) {
|
|||
</NavLink>
|
||||
)}
|
||||
</Navbar.Section>
|
||||
{version ? (
|
||||
<Navbar.Section>
|
||||
<Tooltip
|
||||
label={
|
||||
version.local !== version.upstream
|
||||
? `You are running an outdated version of Zipline, refer to the docs on how to update to ${version.upstream}`
|
||||
: 'You are running the latest version of Zipline'
|
||||
}
|
||||
>
|
||||
<Badge
|
||||
m='md'
|
||||
radius='md'
|
||||
size='lg'
|
||||
variant='dot'
|
||||
color={version.local !== version.upstream ? 'red' : 'primary'}
|
||||
>
|
||||
{version.local}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</Navbar.Section>
|
||||
) : null}
|
||||
</Navbar>
|
||||
}
|
||||
header={
|
||||
|
|
|
@ -116,77 +116,75 @@ export default function Dashboard() {
|
|||
|
||||
<Title mt='md'>Files</Title>
|
||||
<MutedText size='md'>View your gallery <Link href='/dashboard/files'>here</Link>.</MutedText>
|
||||
<Box>
|
||||
<DataGrid
|
||||
data={images}
|
||||
loading={images.length ? false : true}
|
||||
withPagination={true}
|
||||
withColumnResizing={false}
|
||||
withColumnFilters={true}
|
||||
noEllipsis={true}
|
||||
withSorting={true}
|
||||
highlightOnHover={true}
|
||||
CopyIcon={CopyIcon}
|
||||
DeleteIcon={DeleteIcon}
|
||||
EnterIcon={EnterIcon}
|
||||
deleteImage={deleteImage}
|
||||
copyImage={copyImage}
|
||||
viewImage={viewImage}
|
||||
styles={{
|
||||
dataCell: {
|
||||
width: '100%',
|
||||
<DataGrid
|
||||
data={images}
|
||||
loading={images.length ? false : true}
|
||||
withPagination={true}
|
||||
withColumnResizing={false}
|
||||
withColumnFilters={true}
|
||||
noEllipsis={true}
|
||||
withSorting={true}
|
||||
highlightOnHover={true}
|
||||
CopyIcon={CopyIcon}
|
||||
DeleteIcon={DeleteIcon}
|
||||
EnterIcon={EnterIcon}
|
||||
deleteImage={deleteImage}
|
||||
copyImage={copyImage}
|
||||
viewImage={viewImage}
|
||||
styles={{
|
||||
dataCell: {
|
||||
width: '100%',
|
||||
},
|
||||
td: {
|
||||
':nth-child(1)': {
|
||||
minWidth: 170,
|
||||
},
|
||||
td: {
|
||||
':nth-child(1)': {
|
||||
minWidth: 170,
|
||||
},
|
||||
':nth-child(2)': {
|
||||
minWidth: 100,
|
||||
},
|
||||
':nth-child(2)': {
|
||||
minWidth: 100,
|
||||
},
|
||||
th: {
|
||||
':nth-child(1)': {
|
||||
minWidth: 170,
|
||||
padding: theme.spacing.lg,
|
||||
borderTopLeftRadius: theme.radius.sm,
|
||||
},
|
||||
':nth-child(2)': {
|
||||
minWidth: 100,
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
':nth-child(3)': {
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
':nth-child(4)': {
|
||||
padding: theme.spacing.lg,
|
||||
borderTopRightRadius: theme.radius.sm,
|
||||
},
|
||||
},
|
||||
th: {
|
||||
':nth-child(1)': {
|
||||
minWidth: 170,
|
||||
padding: theme.spacing.lg,
|
||||
borderTopLeftRadius: theme.radius.sm,
|
||||
},
|
||||
thead: {
|
||||
backgroundColor: theme.colors.dark[6],
|
||||
':nth-child(2)': {
|
||||
minWidth: 100,
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
}}
|
||||
empty={<></>}
|
||||
':nth-child(3)': {
|
||||
padding: theme.spacing.lg,
|
||||
},
|
||||
':nth-child(4)': {
|
||||
padding: theme.spacing.lg,
|
||||
borderTopRightRadius: theme.radius.sm,
|
||||
},
|
||||
},
|
||||
thead: {
|
||||
backgroundColor: theme.colors.dark[6],
|
||||
},
|
||||
}}
|
||||
empty={<></>}
|
||||
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'file',
|
||||
header: 'Name',
|
||||
filterFn: stringFilterFn,
|
||||
},
|
||||
{
|
||||
accessorKey: 'mimetype',
|
||||
header: 'Type',
|
||||
filterFn: stringFilterFn,
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: 'Date',
|
||||
filterFn: dateFilterFn,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
columns={[
|
||||
{
|
||||
accessorKey: 'file',
|
||||
header: 'Name',
|
||||
filterFn: stringFilterFn,
|
||||
},
|
||||
{
|
||||
accessorKey: 'mimetype',
|
||||
header: 'Type',
|
||||
filterFn: stringFilterFn,
|
||||
},
|
||||
{
|
||||
accessorKey: 'created_at',
|
||||
header: 'Date',
|
||||
filterFn: dateFilterFn,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,63 +1,32 @@
|
|||
export interface ConfigCore {
|
||||
// Whether to return http or https links
|
||||
https: boolean;
|
||||
|
||||
// Used for signing of cookies and other stuff
|
||||
secret: string;
|
||||
|
||||
// The host Zipline will run on
|
||||
host: string;
|
||||
|
||||
// The port Zipline will run on
|
||||
port: number;
|
||||
|
||||
// The PostgreSQL database url
|
||||
database_url: string;
|
||||
|
||||
// Whether or not to log stuff
|
||||
logger: boolean;
|
||||
|
||||
// The interval to store stats
|
||||
|
||||
stats_interval: number;
|
||||
invites_interval: number;
|
||||
}
|
||||
|
||||
export interface ConfigDatasource {
|
||||
// The type of datasource
|
||||
type: 'local' | 's3' | 'swift';
|
||||
|
||||
// The local datasource, the default
|
||||
local: ConfigLocalDatasource;
|
||||
|
||||
// The s3 datasource
|
||||
s3?: ConfigS3Datasource;
|
||||
// The Swift datasource
|
||||
swift?: ConfigSwiftDatasource;
|
||||
}
|
||||
|
||||
export interface ConfigLocalDatasource {
|
||||
// The directory to store files in
|
||||
directory: string;
|
||||
}
|
||||
|
||||
export interface ConfigS3Datasource {
|
||||
// The access key id for the s3 bucket
|
||||
access_key_id: string;
|
||||
|
||||
// The secret access key for the s3 bucket
|
||||
secret_access_key: string;
|
||||
|
||||
// Not required, but if using a non-aws S3 service you can specify the endpoint
|
||||
endpoint?: string;
|
||||
|
||||
// The S3 bucket to store files in
|
||||
bucket: string;
|
||||
|
||||
// If true Zipline will attempt to connect to the bucket via the url "https://s3.amazonaws.com/{bucket}/stuff"
|
||||
// If false Zipline will attempt to connect to the bucket via the url "http://{bucket}.s3.amazonaws.com/stuff"
|
||||
force_s3_path: boolean;
|
||||
|
||||
// Region
|
||||
// aws region, default will be us-east-1 (if using a non-aws S3 service this might work for you)
|
||||
region?: string;
|
||||
}
|
||||
|
||||
|
@ -72,44 +41,27 @@ export interface ConfigSwiftDatasource {
|
|||
}
|
||||
|
||||
export interface ConfigUploader {
|
||||
// The route uploads will be served on
|
||||
route: string;
|
||||
|
||||
// Length of random chars to generate for file names
|
||||
length: number;
|
||||
|
||||
// Admin file upload limit
|
||||
admin_limit: number;
|
||||
|
||||
// User file upload limit
|
||||
user_limit: number;
|
||||
|
||||
// Disabled extensions to block from uploading
|
||||
disabled_extensions: string[];
|
||||
}
|
||||
|
||||
export interface ConfigUrls {
|
||||
// The route urls will be served on
|
||||
route: string;
|
||||
|
||||
// Length of random chars to generate for urls
|
||||
length: number;
|
||||
}
|
||||
|
||||
// Ratelimiting for users/admins, setting them to 0 disables ratelimiting
|
||||
export interface ConfigRatelimit {
|
||||
// Ratelimit for users
|
||||
user: number;
|
||||
|
||||
// Ratelimit for admins
|
||||
admin: number;
|
||||
}
|
||||
|
||||
export interface ConfigWebsite {
|
||||
// Change the title from Zipline to something else
|
||||
title: string;
|
||||
// If zipline should show files per user in the stats page
|
||||
show_files_per_user: boolean;
|
||||
show_version: boolean;
|
||||
}
|
||||
|
||||
export interface ConfigDiscord {
|
||||
|
|
|
@ -49,6 +49,7 @@ export default function readConfig() {
|
|||
map('CORE_DATABASE_URL', 'string', 'core.database_url'),
|
||||
map('CORE_LOGGER', 'boolean', 'core.logger'),
|
||||
map('CORE_STATS_INTERVAL', 'number', 'core.stats_interval'),
|
||||
map('CORE_INVITES_INTERVAL', 'number', 'core.invites_interval'),
|
||||
|
||||
map('DATASOURCE_TYPE', 'string', 'datasource.type'),
|
||||
|
||||
|
@ -83,6 +84,7 @@ export default function readConfig() {
|
|||
|
||||
map('WEBSITE_TITLE', 'string', 'website.title'),
|
||||
map('WEBSITE_SHOW_FILES_PER_USER', 'boolean', 'website.show_files_per_user'),
|
||||
map('WEBSITE_SHOW_VERSION', 'boolean', 'website.show_version'),
|
||||
|
||||
map('DISCORD_URL', 'string', 'discord.url'),
|
||||
map('DISCORD_USERNAME', 'string', 'discord.username'),
|
||||
|
|
|
@ -23,6 +23,7 @@ const validator = object({
|
|||
database_url: string().required(),
|
||||
logger: boolean().default(false),
|
||||
stats_interval: number().default(1800),
|
||||
invites_interval: number().default(1800),
|
||||
}).required(),
|
||||
datasource: object({
|
||||
type: string().oneOf(['local', 's3', 'swift']).default('local'),
|
||||
|
@ -66,6 +67,7 @@ const validator = object({
|
|||
website: object({
|
||||
title: string().default('Zipline'),
|
||||
show_files_per_user: boolean().default(true),
|
||||
show_version: boolean().default(true),
|
||||
}),
|
||||
discord: object({
|
||||
url: string(),
|
||||
|
@ -110,8 +112,6 @@ export default function validate(config): Config {
|
|||
}
|
||||
}
|
||||
|
||||
console.log(validated);
|
||||
|
||||
return validated as unknown as Config;
|
||||
} catch (e) {
|
||||
if (process.env.ZIPLINE_DOCKER_BUILD) return null;
|
||||
|
|
|
@ -114,8 +114,6 @@ export async function sendShorten(user: User, url: Url, host: string) {
|
|||
}] : null,
|
||||
};
|
||||
|
||||
console.log(body);
|
||||
|
||||
const res = await fetch(config.discord.url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import { readFile } from 'fs/promises';
|
||||
import config from 'lib/config';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
const user = await req.user();
|
||||
if (!user) return res.forbid('not logged in');
|
||||
|
||||
if (!config.website.show_version) return res.bad('version hidden');
|
||||
|
||||
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
|
||||
|
||||
const re = await fetch('https://raw.githubusercontent.com/diced/zipline/trunk/package.json');
|
||||
|
|
|
@ -142,15 +142,7 @@ export const getServerSideProps: GetServerSideProps = async context => {
|
|||
if (!invite) return { notFound: true };
|
||||
if (invite.used) return { notFound: true };
|
||||
|
||||
if (invite.expires_at && invite.expires_at < new Date()) {
|
||||
await prisma.invite.delete({
|
||||
where: {
|
||||
code,
|
||||
},
|
||||
});
|
||||
|
||||
return { notFound: true };
|
||||
};
|
||||
if (invite.expires_at && invite.expires_at < new Date()) return { notFound: true };
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
|
|
@ -111,10 +111,20 @@ async function start() {
|
|||
});
|
||||
|
||||
http.listen(config.core.port, config.core.host ?? '0.0.0.0');
|
||||
|
||||
|
||||
logger.info(`started ${dev ? 'development' : 'production'} zipline@${version} server`);
|
||||
|
||||
stats(prisma);
|
||||
|
||||
setInterval(async () => {
|
||||
await prisma.invite.deleteMany({
|
||||
where: {
|
||||
used: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (config.core.logger) logger.info('invites cleaned');
|
||||
}, config.core.invites_interval * 1000);
|
||||
}
|
||||
|
||||
async function rawFile(
|
||||
|
@ -180,7 +190,7 @@ async function fileDb(
|
|||
if (image.expires_at && image.expires_at < new Date()) {
|
||||
await datasource.delete(image.file);
|
||||
await prisma.image.delete({ where: { id: image.id } });
|
||||
|
||||
|
||||
return nextServer.render404(req, res as ServerResponse);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,23 +5,29 @@ import { Datasource } from 'lib/datasources';
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
export async function migrations() {
|
||||
const migrate = new Migrate('./prisma/schema.prisma');
|
||||
await ensureDatabaseExists('apply', true, './prisma/schema.prisma');
|
||||
try {
|
||||
const migrate = new Migrate('./prisma/schema.prisma');
|
||||
await ensureDatabaseExists('apply', true, './prisma/schema.prisma');
|
||||
|
||||
const diagnose = await migrate.diagnoseMigrationHistory({
|
||||
optInToShadowDatabase: false,
|
||||
});
|
||||
const diagnose = await migrate.diagnoseMigrationHistory({
|
||||
optInToShadowDatabase: false,
|
||||
});
|
||||
|
||||
if (diagnose.history?.diagnostic === 'databaseIsBehind') {
|
||||
try {
|
||||
Logger.get('database').info('migrating database');
|
||||
await migrate.applyMigrations();
|
||||
} finally {
|
||||
if (diagnose.history?.diagnostic === 'databaseIsBehind') {
|
||||
try {
|
||||
Logger.get('database').info('migrating database');
|
||||
await migrate.applyMigrations();
|
||||
} finally {
|
||||
migrate.stop();
|
||||
Logger.get('database').info('finished migrating database');
|
||||
}
|
||||
} else {
|
||||
migrate.stop();
|
||||
Logger.get('database').info('finished migrating database');
|
||||
}
|
||||
} else {
|
||||
migrate.stop();
|
||||
} catch (error) {
|
||||
Logger.get('database').error('Failed to migrate database... exiting...');
|
||||
Logger.get('database').error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -301,9 +301,9 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@dicedtomato/mantine-data-grid@npm:0.0.20":
|
||||
version: 0.0.20
|
||||
resolution: "@dicedtomato/mantine-data-grid@npm:0.0.20"
|
||||
"@dicedtomato/mantine-data-grid@npm:0.0.21":
|
||||
version: 0.0.21
|
||||
resolution: "@dicedtomato/mantine-data-grid@npm:0.0.21"
|
||||
dependencies:
|
||||
"@emotion/react": ^11.9.3
|
||||
"@mantine/core": ^5.0.0
|
||||
|
@ -324,7 +324,7 @@ __metadata:
|
|||
peerDependenciesMeta:
|
||||
dayjs:
|
||||
optional: true
|
||||
checksum: 6d23048cc4d0dba093c8bbbaa70f7f7b8539834bcf11f070669b54a643b8def70c21a3a3ed7e807220e2c9928239c658b0a8f717a466cbfd4e8933576a2762a2
|
||||
checksum: bca215db4cc2fbf776a30c2fe9fbb2e95649d9adfd8b014c04ae6a8dff00a726c6042546d79144f11323e2895539cc5d83686d9e5c6bc7a2e9f1a48cb2831b87
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
@ -8867,7 +8867,7 @@ __metadata:
|
|||
version: 0.0.0-use.local
|
||||
resolution: "zipline@workspace:."
|
||||
dependencies:
|
||||
"@dicedtomato/mantine-data-grid": 0.0.20
|
||||
"@dicedtomato/mantine-data-grid": 0.0.21
|
||||
"@emotion/react": ^11.9.3
|
||||
"@emotion/server": ^11.4.0
|
||||
"@iarna/toml": 2.2.5
|
||||
|
|
Loading…
Reference in a new issue