mirror of https://github.com/BRAVO68WEB/shx.git
Merge pull request #109 from BRAVO68WEB/feature/api-integration
Feature/api integration
This commit is contained in:
commit
f65cac9912
|
@ -27,11 +27,11 @@ class ApiSdk {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
const { cookies } = await import('next/headers');
|
const { cookies } = await import('next/headers');
|
||||||
config.headers['x-shx-api-key'] = cookies().get('apiKey')?.value;
|
config.headers['x-shx-api-key'] = cookies().get('apiKey')?.value;
|
||||||
config.baseURL = cookies().get('instanceUrl')?.value;
|
|
||||||
} else {
|
} else {
|
||||||
config.headers['x-shx-api-key'] = Cookies.get().apiKey ?? '';
|
config.headers['x-shx-api-key'] = Cookies.get().apiKey ?? '';
|
||||||
config.baseURL = Cookies.get('instanceUrl') ?? '';
|
|
||||||
}
|
}
|
||||||
|
config.baseURL = process.env.NEXT_PUBLIC_INSTANCE_URL
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,24 +6,27 @@ export class Notes {
|
||||||
constructor(axios: Axios) {
|
constructor(axios: Axios) {
|
||||||
this.axios = axios;
|
this.axios = axios;
|
||||||
}
|
}
|
||||||
async getAllNotes(search?:string) {
|
async getAllNotes(search?: string) {
|
||||||
const res = await this.axios.get('/gist',{params:{search}});
|
const res = await this.axios.get('/gist', { params: { search } });
|
||||||
const data = res.data.data as INote[];
|
const data = res.data.data as INote[];
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
async getSingleNote(id: string, passkey = '') {
|
||||||
|
const res = await this.axios.get(`/gist/${id}`, {
|
||||||
|
params: passkey ? { passkey } : {},
|
||||||
|
});
|
||||||
|
const data = res.data.data as INote;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
async uploadSingleNote(data: AddNoteType) {
|
async uploadSingleNote(data: AddNoteType) {
|
||||||
console.log("uploading")
|
console.log('uploading');
|
||||||
if(!data.passkey){
|
if (!data.passkey) {
|
||||||
delete data.passkey
|
delete data.passkey;
|
||||||
}
|
}
|
||||||
const res = await this.axios.post('/gist', data);
|
const res = await this.axios.post('/gist', data);
|
||||||
return res.data.data as INote;
|
return res.data.data as INote;
|
||||||
}
|
}
|
||||||
async deleteSingleNote({
|
async deleteSingleNote({ noteID }: { noteID: string }) {
|
||||||
noteID,
|
|
||||||
}: {
|
|
||||||
noteID: string;
|
|
||||||
}) {
|
|
||||||
const res = await this.axios.delete(`/gist/${noteID}`);
|
const res = await this.axios.delete(`/gist/${noteID}`);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,12 @@ export class Settings {
|
||||||
const res = await this.axios.post('/settings', {key, value});
|
const res = await this.axios.post('/settings', {key, value});
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
async getInstanceInfo(){
|
||||||
|
const res = await this.axios.get('/info/sys');
|
||||||
|
delete res.data.data.cpuUsage
|
||||||
|
delete res.data.data.memoryUsage
|
||||||
|
return res.data.data as ISysSettings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Settings;
|
export default Settings;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
function ErrorPage() {
|
||||||
|
return (
|
||||||
|
<div>ErrorPage</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorPage
|
|
@ -1,23 +1,30 @@
|
||||||
import { sidebarGroups } from '@/lib/sidebar';
|
import { sidebarGroups } from '@/lib/sidebar';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import React from 'react';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
interface DashboardLayoutProps {
|
interface DashboardLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Layout = ({ children }: DashboardLayoutProps) => {
|
const Layout = ({ children }: DashboardLayoutProps) => {
|
||||||
|
const cookieList = cookies();
|
||||||
|
const apiKey = cookieList.has('apiKey');
|
||||||
|
const masterKey = cookieList.has('masterKey');
|
||||||
|
if (!apiKey || !masterKey) redirect('/');
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<div className="sidebar w-full max-w-xs h-screen bg-black flex flex-col overflow-x-hidden overflow-y-auto">
|
<div className="sidebar w-full max-w-xs h-screen bg-black flex flex-col overflow-x-hidden overflow-y-auto">
|
||||||
{sidebarGroups.map((sidebarGrp, index) => {
|
{sidebarGroups.map((sidebarGrp, index) => {
|
||||||
return (
|
return (
|
||||||
<div key={index} className="menu-group p-4">
|
<div key={index} className="menu-group p-4">
|
||||||
<p className="title text-lg text-primary mb-2">{sidebarGrp.name}</p>
|
<p className="title text-lg text-primary mb-2">
|
||||||
|
{sidebarGrp.name}
|
||||||
|
</p>
|
||||||
{sidebarGrp.items.map((item, index) => {
|
{sidebarGrp.items.map((item, index) => {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
key={index}
|
key={index}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className="p-2.5 block text-sm hover:bg-gray-900 w-full rounded-md"
|
className="p-2.5 block text-sm hover:bg-gray-900 w-full rounded-md"
|
||||||
>
|
>
|
||||||
|
@ -29,7 +36,7 @@ const Layout = ({ children }: DashboardLayoutProps) => {
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<main className='w-full h-full overflow-y-auto p-5'>{children}</main>
|
<main className="w-full h-full overflow-y-auto p-5">{children}</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
import Markdown from '@/components/Markdown';
|
||||||
|
import axios from 'axios';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = async () => {
|
||||||
|
const { data } = await axios.get(
|
||||||
|
'https://raw.githubusercontent.com/BRAVO68WEB/shx/dev/README.md'
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-5xl">Dasbhboard</h1>
|
<Markdown markdown={data} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,37 +1,94 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
|
import axios from 'axios';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
import { Download } from 'lucide-react';
|
import { Download } from 'lucide-react';
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
const configs = [
|
const configs = [
|
||||||
{
|
{
|
||||||
name: 'Lorem Ipsum Config',
|
name: 'Image Config',
|
||||||
|
filename: 'image.sxcu',
|
||||||
|
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/image.sxcu?apikey=`,
|
||||||
id: '1',
|
id: '1',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Lorem Cnofig',
|
name: 'Gist Config',
|
||||||
id: '12',
|
filename: 'gist.sxcu',
|
||||||
|
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/gist.sxcu?apikey=`,
|
||||||
|
id: '2',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Lorem Config Config',
|
name: 'URL Config',
|
||||||
id: '13',
|
filename: 'url.sxcu',
|
||||||
|
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/url.sxcu?apikey=`,
|
||||||
|
id: '3',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Lorem ',
|
name: 'File Config',
|
||||||
id: '14',
|
filename: 'file.sxcu',
|
||||||
|
url: `${process.env.NEXT_PUBLIC_INSTANCE_URL}/config/file.sxcu?apikey=`,
|
||||||
|
id: '4',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const downloadFile = ({
|
||||||
|
data,
|
||||||
|
fileName,
|
||||||
|
fileType,
|
||||||
|
}: {
|
||||||
|
data: string;
|
||||||
|
fileName: string;
|
||||||
|
fileType: string;
|
||||||
|
}) => {
|
||||||
|
// Create a blob with the data we want to download as a file
|
||||||
|
const blob = new Blob([data], { type: fileType });
|
||||||
|
// Create an anchor element and dispatch a click event on it
|
||||||
|
// to trigger a download
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.download = fileName;
|
||||||
|
a.href = window.URL.createObjectURL(blob);
|
||||||
|
const clickEvt = new MouseEvent('click', {
|
||||||
|
view: window,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
a.dispatchEvent(clickEvt);
|
||||||
|
a.remove();
|
||||||
|
};
|
||||||
function Page() {
|
function Page() {
|
||||||
|
const [apiKey, setApiKey] = useState('');
|
||||||
|
useEffect(() => {
|
||||||
|
setApiKey(Cookies.get('apiKey') ?? '');
|
||||||
|
},[]);
|
||||||
|
|
||||||
|
async function onDownloadFile(url: string, filename: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await axios.get(url);
|
||||||
|
console.log(data);
|
||||||
|
downloadFile({
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
fileName: filename,
|
||||||
|
fileType: 'text/json',
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
toast.error('Error downloading file');
|
||||||
|
}
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{configs.map(({ name, id }) => {
|
{configs.map(({ name, id, url, filename }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
className="w-full my-3 p-4 flex items-center justify-between bg-gray-900 rounded"
|
className="w-full my-3 p-4 flex items-center justify-between bg-gray-900 rounded"
|
||||||
>
|
>
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
<Button size={'icon'} variant={'transparent'}>
|
<Button
|
||||||
|
size={'icon'}
|
||||||
|
onClick={() => onDownloadFile(`${url}${apiKey}`, filename)}
|
||||||
|
>
|
||||||
<Download />
|
<Download />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
|
import api from '@/api';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
function Page() {
|
async function Page() {
|
||||||
const urls = [
|
const res = await api.settings.getInstanceInfo();
|
||||||
{
|
const data = Object.entries(res);
|
||||||
id: 'afdas',
|
|
||||||
originalURL: 'https://www.google.com',
|
|
||||||
shortenedURL: 'https://www.google.com',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -29,19 +25,13 @@ function Page() {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y p-2">
|
<tbody className="divide-y p-2">
|
||||||
{urls.map(({ originalURL, shortenedURL, id }) => (
|
{data.map((val, id) => (
|
||||||
<tr className="bg-gray-900 rounded" key={id}>
|
<tr className="bg-gray-900 rounded" key={id}>
|
||||||
<td className="whitespace-nowrap py-5 pl-4 pr-20 text-sm font-medium text-white">
|
<td className="whitespace-nowrap py-5 pl-4 pr-20 text-sm font-medium text-white">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">{val[0]}</div>
|
||||||
{originalURL}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">{val[1]}</div>
|
||||||
{shortenedURL}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import React, { ChangeEventHandler, useEffect, useState } from 'react';
|
import React, { ChangeEventHandler, useEffect, useState } from 'react';
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import Input from '@/components/ui/Input';
|
|
||||||
import TagInput from '@/components/TagInput';
|
import TagInput from '@/components/TagInput';
|
||||||
import api from '@/api';
|
import api from '@/api';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
@ -112,21 +111,16 @@ function Page() {
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center w-full gap-4">
|
|
||||||
<Input
|
|
||||||
id="instance-url"
|
|
||||||
withLabel
|
|
||||||
label="Instance URL"
|
|
||||||
type="text"
|
|
||||||
className="w-full"
|
|
||||||
/>
|
|
||||||
<Button className="w-20 h-min ">Save</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<TagInput
|
<TagInput
|
||||||
tags={settings.imageExtensions}
|
tags={settings.imageExtensions}
|
||||||
onAddTags={addImageExt}
|
onAddTags={addImageExt}
|
||||||
placeholder="Image Extensions"
|
placeholder="Image Extensions"
|
||||||
|
onChange={value =>
|
||||||
|
setSettings(old => {
|
||||||
|
return { ...old, imageExtensions: value };
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
@ -143,6 +137,11 @@ function Page() {
|
||||||
tags={settings.fileExtensions}
|
tags={settings.fileExtensions}
|
||||||
onAddTags={addFileExt}
|
onAddTags={addFileExt}
|
||||||
placeholder="File Extensions"
|
placeholder="File Extensions"
|
||||||
|
onChange={value =>
|
||||||
|
setSettings(old => {
|
||||||
|
return { ...old, fileExtensions: value };
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
|
* {
|
||||||
*{
|
margin: 0;
|
||||||
margin:0;
|
padding: 0;
|
||||||
padding:0;
|
box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:root{
|
:root {
|
||||||
--primary:#3A86FF;
|
--primary: #3a86ff;
|
||||||
--secondary:#8338EC;
|
--secondary: #8338ec;
|
||||||
}
|
}
|
||||||
|
|
||||||
body{
|
body {
|
||||||
color:white
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-body p:nth-child(3) {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.markdown-body p:nth-child(4) {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.markdown-body p:nth-child(5) {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
import './globals.css'
|
import React from 'react';
|
||||||
import { Source_Code_Pro } from 'next/font/google'
|
import './globals.css';
|
||||||
|
import { Source_Code_Pro } from 'next/font/google';
|
||||||
|
import Providers from '@/components/Providers';
|
||||||
|
|
||||||
const inter = Source_Code_Pro({subsets:['latin']})
|
const inter = Source_Code_Pro({ subsets: ['latin'] });
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
title: 'Create Next App',
|
title: 'Create Next App',
|
||||||
description: 'Generated by create next app',
|
description: 'Generated by create next app',
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
return (
|
||||||
return (
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={`${inter.className} bg-black text-white min-h-screen`}>{children}</body>
|
<body className={`${inter.className} bg-black text-white min-h-screen`}>
|
||||||
|
<Providers>{children}</Providers>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import api from '@/api';
|
||||||
|
import Button from '@/components/ui/Button';
|
||||||
|
import Input from '@/components/ui/Input';
|
||||||
|
import { Lock } from 'lucide-react';
|
||||||
|
import React, { FormEventHandler, useState } from 'react';
|
||||||
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
|
interface NoteContentProps {
|
||||||
|
data: INote | null;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoteContent({ data, id }: NoteContentProps) {
|
||||||
|
const [note, setNote] = useState(data);
|
||||||
|
const [input, setInput] = useState('');
|
||||||
|
const onSubmit: FormEventHandler = async evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
try {
|
||||||
|
const data = await api.notes.getSingleNote(id, input);
|
||||||
|
setNote(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast.error('Wrong passkey');
|
||||||
|
setNote(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="w-1/2 p-10 border-primary border max-h-full overflow-y-auto">
|
||||||
|
{note?.content ?? (
|
||||||
|
<form
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
className="flex items-center justify-between gap-4 w-full"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
withLabel={true}
|
||||||
|
placeholder="Enter passkey"
|
||||||
|
label="Enter passkey"
|
||||||
|
className="w-full"
|
||||||
|
onChange={evt => setInput(evt.target.value)}
|
||||||
|
value={input}
|
||||||
|
/>
|
||||||
|
<Button size={'icon'}>
|
||||||
|
<Lock />
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NoteContent;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
function Loading() {
|
||||||
|
return (
|
||||||
|
<div>Loading</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Loading
|
|
@ -0,0 +1,25 @@
|
||||||
|
import api from '@/api';
|
||||||
|
import React from 'react';
|
||||||
|
import NoteContent from './NoteContent';
|
||||||
|
|
||||||
|
interface NotePageProps {
|
||||||
|
params: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function Page({ params: { id } }: NotePageProps) {
|
||||||
|
let data: INote | null = null;
|
||||||
|
try {
|
||||||
|
data = await api.notes.getSingleNote(id);
|
||||||
|
} catch {
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="w-full h-screen p-20 flex justify-center items-center">
|
||||||
|
<NoteContent data={data} id={id} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Page;
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
import api from '@/api';
|
import api from '@/api';
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
|
import { parseDate } from '@/lib/utils';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
|
|
||||||
|
@ -11,9 +14,16 @@ interface ApiKeyListProps {
|
||||||
|
|
||||||
function ApiKeyList({ data }: ApiKeyListProps) {
|
function ApiKeyList({ data }: ApiKeyListProps) {
|
||||||
const [apiKeys, setApiKeys] = useState(data);
|
const [apiKeys, setApiKeys] = useState(data);
|
||||||
const onDisableApiKey = async (id: string) => {
|
const router = useRouter();
|
||||||
|
const onDisableApiKey = async (id: string, key: string) => {
|
||||||
try {
|
try {
|
||||||
|
const apiKey = Cookies.get().apiKey as string;
|
||||||
await api.apiKeys.disableApiKey(id);
|
await api.apiKeys.disableApiKey(id);
|
||||||
|
if (apiKey.endsWith(key.substring(9))) {
|
||||||
|
Cookies.remove('apiKey');
|
||||||
|
Cookies.remove('masterKey');
|
||||||
|
router.replace('/');
|
||||||
|
}
|
||||||
setApiKeys(old => old.filter(key => key.keyID !== id));
|
setApiKeys(old => old.filter(key => key.keyID !== id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error Deleting api key');
|
console.error('Error Deleting api key');
|
||||||
|
@ -37,10 +47,13 @@ function ApiKeyList({ data }: ApiKeyListProps) {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="divide-y p-2">
|
<tbody className="divide-y p-2">
|
||||||
{apiKeys.map(({ key, keyID }) => (
|
{apiKeys.map(({ key, keyID, last_used }) => (
|
||||||
<tr className="bg-gray-900 rounded" key={keyID}>
|
<tr className="bg-gray-900 rounded" key={keyID}>
|
||||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||||
<p className="text-xl">{key}</p>
|
<p className="text-xl">{key}</p>
|
||||||
|
<p className="text-xs text-gray-400">
|
||||||
|
Last Used: {parseDate(last_used)}
|
||||||
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
|
<td className="relative whitespace-nowrap py-4 px-4 text-right text-sm font-medium icons flex center items-center gap-3">
|
||||||
<Button
|
<Button
|
||||||
|
@ -49,7 +62,7 @@ function ApiKeyList({ data }: ApiKeyListProps) {
|
||||||
aria-label="Disable Api Key"
|
aria-label="Disable Api Key"
|
||||||
title="Disable Api Key"
|
title="Disable Api Key"
|
||||||
className="rounded-full p-2 bg-red-100 text-red-600"
|
className="rounded-full p-2 bg-red-100 text-red-600"
|
||||||
onClick={() => onDisableApiKey(keyID)}
|
onClick={() => onDisableApiKey(keyID, key)}
|
||||||
>
|
>
|
||||||
Disable
|
Disable
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import { Edit, Trash2 } from 'lucide-react';
|
import { ArrowUpRight, Copy, Trash2 } from 'lucide-react';
|
||||||
import React from 'react';
|
import Link from 'next/link';
|
||||||
|
|
||||||
interface NotesLitsProps {
|
interface NotesLitsProps {
|
||||||
data: INote[];
|
data: INote[];
|
||||||
|
@ -8,6 +8,11 @@ interface NotesLitsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotesList({ data, onDeleteNote }: NotesLitsProps) {
|
function NotesList({ data, onDeleteNote }: NotesLitsProps) {
|
||||||
|
function copyNoteLink(id: string) {
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
`${window.location.origin.toString()}/notes/${id}`
|
||||||
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-1">
|
<div className="flex flex-col w-full gap-1">
|
||||||
{data.map(note => {
|
{data.map(note => {
|
||||||
|
@ -30,12 +35,23 @@ function NotesList({ data, onDeleteNote }: NotesLitsProps) {
|
||||||
<Button
|
<Button
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
size={'icon'}
|
size={'icon'}
|
||||||
aria-label="Edit Note"
|
aria-label="Copy Note"
|
||||||
title="Edit Note"
|
title="Copy Note"
|
||||||
className="rounded-full hover:bg-black"
|
className="rounded-full hover:bg-black"
|
||||||
|
onClick={() => copyNoteLink(note.gist_url_key)}
|
||||||
>
|
>
|
||||||
<Edit className="h-5 w-5 " />
|
<Copy className="h-5 w-5 " />
|
||||||
</Button>
|
</Button>
|
||||||
|
<Link
|
||||||
|
href={`/notes/${note.gist_url_key}`}
|
||||||
|
target="_blank"
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
aria-label="Go to Note"
|
||||||
|
title="Go to Note"
|
||||||
|
className="rounded-full hover:bg-black p-3 h-min w-min"
|
||||||
|
>
|
||||||
|
<ArrowUpRight className="h-5 w-5 " />
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import { Trash, Edit2, ArrowUpRight, X } from 'lucide-react';
|
import { Trash, Edit2, ArrowUpRight, X } from 'lucide-react';
|
||||||
import URLControls from './URLControls';
|
import URLControls from './URLControls';
|
||||||
|
@ -9,7 +9,6 @@ import Input from '@/components/ui/Input';
|
||||||
import api from '@/api';
|
import api from '@/api';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
|
|
||||||
interface ShortenUrlListProps {
|
interface ShortenUrlListProps {
|
||||||
data: IUrl[];
|
data: IUrl[];
|
||||||
|
@ -27,7 +26,7 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
||||||
state: false,
|
state: false,
|
||||||
});
|
});
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [instanceUrl, setInstanceURL] = useState('');
|
|
||||||
const onAddURL = async (url: string) => {
|
const onAddURL = async (url: string) => {
|
||||||
try {
|
try {
|
||||||
await api.url.uploadUrl(url);
|
await api.url.uploadUrl(url);
|
||||||
|
@ -59,9 +58,6 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setInstanceURL(Cookies.get('instanceUrl') ?? '');
|
|
||||||
}, []);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<URLControls onAddURL={onAddURL} />
|
<URLControls onAddURL={onAddURL} />
|
||||||
|
@ -103,10 +99,10 @@ function ShortenUrlList({ data }: ShortenUrlListProps) {
|
||||||
</td>
|
</td>
|
||||||
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
<td className="whitespace-nowrap pl-4 text-sm font-medium text-white">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<p className="w-80 truncate">{`${instanceUrl}/${short_key}`}</p>
|
<p className="w-80 truncate">{`${process.env.NEXT_PUBLIC_INSTANCE_URL}/${short_key}`}</p>
|
||||||
<a
|
<a
|
||||||
referrerPolicy="no-referrer"
|
referrerPolicy="no-referrer"
|
||||||
href={`${instanceUrl}/${short_key}`}
|
href={`${process.env.NEXT_PUBLIC_INSTANCE_URL}/${short_key}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="p-2 bg-white bg-opacity-10 rounded cursor-pointer"
|
className="p-2 bg-white bg-opacity-10 rounded cursor-pointer"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import { ArrowUpRight, Trash } from 'lucide-react';
|
import { ArrowUpRight, Trash } from 'lucide-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn, parseDate } from '@/lib/utils';
|
||||||
import { UploadsListComponentProps } from '@/types/list';
|
import { UploadsListComponentProps } from '@/types/list';
|
||||||
|
|
||||||
export default function LinearList({
|
export default function LinearList({
|
||||||
|
@ -10,9 +10,6 @@ export default function LinearList({
|
||||||
onDelete,
|
onDelete,
|
||||||
}: UploadsListComponentProps) {
|
}: UploadsListComponentProps) {
|
||||||
// parse a date from gmt format to iso format
|
// parse a date from gmt format to iso format
|
||||||
const parseDate = (date: string) => {
|
|
||||||
return new Date(date).toISOString().split('T')[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full gap-2 ">
|
<div className="flex flex-col w-full gap-2 ">
|
||||||
|
|
|
@ -18,9 +18,8 @@ const LoginForm = () => {
|
||||||
resolver: zodResolver(LoginSchema),
|
resolver: zodResolver(LoginSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const onSubmit = async ({ masterkey, instanceUrl }: LoginType) => {
|
const onSubmit = async ({ masterkey }: LoginType) => {
|
||||||
Cookies.set('masterKey', masterkey);
|
Cookies.set('masterKey', masterkey);
|
||||||
Cookies.set('instanceUrl', instanceUrl);
|
|
||||||
try {
|
try {
|
||||||
const res = await api.apiKeys.createKey();
|
const res = await api.apiKeys.createKey();
|
||||||
if (res) {
|
if (res) {
|
||||||
|
@ -28,14 +27,12 @@ const LoginForm = () => {
|
||||||
router.push('/dashboard');
|
router.push('/dashboard');
|
||||||
} else {
|
} else {
|
||||||
Cookies.remove('masterKey');
|
Cookies.remove('masterKey');
|
||||||
Cookies.remove('instanceUrl');
|
|
||||||
Cookies.remove('apiKey');
|
Cookies.remove('apiKey');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast.error('error');
|
toast.error('error');
|
||||||
Cookies.remove('masterKey');
|
Cookies.remove('masterKey');
|
||||||
Cookies.remove('instanceUrl');
|
|
||||||
Cookies.remove('apiKey');
|
Cookies.remove('apiKey');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -53,14 +50,6 @@ const LoginForm = () => {
|
||||||
type="text"
|
type="text"
|
||||||
id="masterKey"
|
id="masterKey"
|
||||||
/>
|
/>
|
||||||
<Input
|
|
||||||
{...register('instanceUrl')}
|
|
||||||
label="Instance Url"
|
|
||||||
withLabel={true}
|
|
||||||
placeholder="Instance Url"
|
|
||||||
type="text"
|
|
||||||
id="instanceUrl"
|
|
||||||
/>
|
|
||||||
<Button type="submit" className="mt-8">
|
<Button type="submit" className="mt-8">
|
||||||
Sign in
|
Sign in
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import showdown from 'showdown';
|
||||||
|
import 'github-markdown-css';
|
||||||
|
|
||||||
|
const converter = new showdown.Converter();
|
||||||
|
|
||||||
|
interface MarkdownProps {
|
||||||
|
markdown: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Markdown({ markdown }: MarkdownProps) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current?.innerHTML) ref.current.innerHTML = converter.makeHtml(markdown);
|
||||||
|
}, [markdown]);
|
||||||
|
return (
|
||||||
|
<div className="markdown-body w-full p-10" ref={ref}>
|
||||||
|
Markdown
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Markdown;
|
|
@ -0,0 +1,13 @@
|
||||||
|
'use client';
|
||||||
|
import { Toaster } from 'react-hot-toast';
|
||||||
|
|
||||||
|
function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Toaster position={'top-right'} reverseOrder={false} />
|
||||||
|
{children}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Providers;
|
|
@ -5,10 +5,11 @@ import Button from './ui/Button';
|
||||||
interface TagInput {
|
interface TagInput {
|
||||||
tags: string[];
|
tags: string[];
|
||||||
onAddTags: (value: string) => void;
|
onAddTags: (value: string) => void;
|
||||||
|
onChange: (value: string[]) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TagInput({ tags, placeholder, onAddTags }: TagInput) {
|
function TagInput({ tags, placeholder, onAddTags, onChange }: TagInput) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const onSubmit: FormEventHandler<HTMLFormElement> = evt => {
|
const onSubmit: FormEventHandler<HTMLFormElement> = evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
|
@ -17,10 +18,17 @@ function TagInput({ tags, placeholder, onAddTags }: TagInput) {
|
||||||
inputRef.current.value = '';
|
inputRef.current.value = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const onDelete = (i: number) => {
|
||||||
|
onChange(tags.filter((_tag, index) => i !== index));
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-wrap gap-2">
|
<div className="w-full flex flex-wrap gap-2">
|
||||||
{tags.map((tag, index) => (
|
{tags.map((tag, index) => (
|
||||||
<Button key={index} className="rounded w-min m-0 items-center">
|
<Button
|
||||||
|
onClick={() => onDelete(index)}
|
||||||
|
key={index}
|
||||||
|
className="rounded w-min m-0 items-center"
|
||||||
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -32,12 +32,12 @@ export const sidebarGroups: SidebarGroup[] = [
|
||||||
href: '/dashboard/utilities/instance-info',
|
href: '/dashboard/utilities/instance-info',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Uploads',
|
name: 'Settings',
|
||||||
href: '/dashboard/utilities/settings',
|
href: '/dashboard/utilities/settings',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Notes',
|
name: 'Download Config',
|
||||||
href: '/dashboard/utilites/download-config',
|
href: '/dashboard/utilities/download-config',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,3 +4,7 @@ import { twMerge } from 'tailwind-merge';
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const parseDate = (date: string) => {
|
||||||
|
return new Date(date).toISOString().split('T')[0];
|
||||||
|
};
|
|
@ -2,7 +2,6 @@ import { z } from 'zod';
|
||||||
|
|
||||||
export const LoginSchema = z.object({
|
export const LoginSchema = z.object({
|
||||||
masterkey: z.string().max(256),
|
masterkey: z.string().max(256),
|
||||||
instanceUrl: z.string().url(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type LoginType = z.infer<typeof LoginSchema>;
|
export type LoginType = z.infer<typeof LoginSchema>;
|
||||||
|
|
|
@ -23,15 +23,19 @@
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
"eslint": "8.41.0",
|
"eslint": "8.41.0",
|
||||||
"eslint-config-next": "13.4.5",
|
"eslint-config-next": "13.4.5",
|
||||||
|
"github-markdown-css": "^5.2.0",
|
||||||
|
"install": "^0.13.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.221.0",
|
"lucide-react": "^0.221.0",
|
||||||
"next": "^13.4.7",
|
"next": "^13.4.7",
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
|
"npm": "^9.7.2",
|
||||||
"postcss": "8.4.24",
|
"postcss": "8.4.24",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.44.3",
|
"react-hook-form": "^7.44.3",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
|
"showdown": "^2.1.0",
|
||||||
"tailwind-merge": "^1.12.0",
|
"tailwind-merge": "^1.12.0",
|
||||||
"tailwindcss": "3.3.2",
|
"tailwindcss": "3.3.2",
|
||||||
"typescript": "5.0.4",
|
"typescript": "5.0.4",
|
||||||
|
@ -46,6 +50,7 @@
|
||||||
"@storybook/react": "^7.0.17",
|
"@storybook/react": "^7.0.17",
|
||||||
"@storybook/testing-library": "^0.0.14-next.2",
|
"@storybook/testing-library": "^0.0.14-next.2",
|
||||||
"@types/js-cookie": "^3.0.3",
|
"@types/js-cookie": "^3.0.3",
|
||||||
|
"@types/showdown": "^2.0.1",
|
||||||
"eslint-plugin-storybook": "^0.6.12",
|
"eslint-plugin-storybook": "^0.6.12",
|
||||||
"storybook": "^7.0.17"
|
"storybook": "^7.0.17"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
interface IApiKey {
|
interface IApiKey {
|
||||||
key: string;
|
key: string;
|
||||||
keyID: string;
|
keyID: string;
|
||||||
|
last_used: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,4 +3,13 @@ interface ISettings {
|
||||||
language: string;
|
language: string;
|
||||||
imageExtensions: string[];
|
imageExtensions: string[];
|
||||||
fileExtensions: string[];
|
fileExtensions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ISysSettings {
|
||||||
|
platform: string;
|
||||||
|
arch: string;
|
||||||
|
nodeVersion: string;
|
||||||
|
uptime: string;
|
||||||
|
kernelVersion: string;
|
||||||
|
hostname: string;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue