refactor(studio): ConnectivityView module, re-export, stories
- Module connectivity-view: types, useConnectivityView, WebDAV, Webhooks, Skeleton, orchestrator ConnectivityView - Re-export from ConnectivityView.tsx - Stories: Default, Loading (Skeleton); decorator min-h-layout-page - Fix: text-[10px] -> text-xs (Mount Password hint) Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
3547ce1096
commit
e2f30ee28a
9 changed files with 353 additions and 192 deletions
24
apps/web/src/components/studio/ConnectivityView.stories.tsx
Normal file
24
apps/web/src/components/studio/ConnectivityView.stories.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { ConnectivityView, ConnectivityViewSkeleton } from './connectivity-view';
|
||||||
|
|
||||||
|
const meta: Meta<typeof ConnectivityView> = {
|
||||||
|
title: 'Components/Features/Studio/ConnectivityView',
|
||||||
|
component: ConnectivityView,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<div className="bg-kodo-background min-h-layout-page p-4">
|
||||||
|
<Story />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Default: Story = {};
|
||||||
|
|
||||||
|
export const Loading: Story = {
|
||||||
|
render: () => <ConnectivityViewSkeleton />,
|
||||||
|
};
|
||||||
|
|
@ -1,192 +1 @@
|
||||||
import React, { useState } from 'react';
|
export { ConnectivityView, ConnectivityViewSkeleton } from './connectivity-view';
|
||||||
import { Card } from '../ui/card';
|
|
||||||
import { Button } from '../ui/button';
|
|
||||||
import { Input } from '../ui/input';
|
|
||||||
import { Globe, Copy, Plus, Trash2, CheckCircle, Folder } from 'lucide-react';
|
|
||||||
import { useToast } from '../../components/feedback/ToastProvider';
|
|
||||||
|
|
||||||
export const ConnectivityView: React.FC = () => {
|
|
||||||
const { addToast } = useToast();
|
|
||||||
|
|
||||||
// WebDAV State
|
|
||||||
const [webdavPass, setWebdavPass] = useState('****');
|
|
||||||
|
|
||||||
// Webhooks State
|
|
||||||
const [webhooks, setWebhooks] = useState([
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
url: 'https://api.myapp.com/hooks/veza',
|
|
||||||
events: ['file.upload', 'file.delete'],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const [newHookUrl, setNewHookUrl] = useState('');
|
|
||||||
|
|
||||||
const generateWebdavPass = () => {
|
|
||||||
setWebdavPass(`wd-${Math.random().toString(36).substr(2, 10)}`);
|
|
||||||
addToast('New WebDAV password generated', 'success');
|
|
||||||
};
|
|
||||||
|
|
||||||
const addWebhook = () => {
|
|
||||||
if (!newHookUrl) return;
|
|
||||||
setWebhooks([
|
|
||||||
...webhooks,
|
|
||||||
{ id: Date.now().toString(), url: newHookUrl, events: ['file.upload'] },
|
|
||||||
]);
|
|
||||||
setNewHookUrl('');
|
|
||||||
addToast('Webhook endpoint added', 'success');
|
|
||||||
};
|
|
||||||
|
|
||||||
const copyToClipboard = (text: string) => {
|
|
||||||
navigator.clipboard.writeText(text);
|
|
||||||
addToast('Copied to clipboard');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full flex flex-col gap-8 animate-fadeIn">
|
|
||||||
{/* WebDAV Mount Section */}
|
|
||||||
<Card variant="default">
|
|
||||||
<div className="flex items-start justify-between mb-6">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-white flex items-center gap-2">
|
|
||||||
<Folder className="w-5 h-5 text-kodo-gold" /> Directory Mount
|
|
||||||
(WebDAV)
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-kodo-content-dim mt-1">
|
|
||||||
Access your Cloud Studio files directly from your OS file explorer
|
|
||||||
or DAW.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="bg-kodo-gold/10 text-kodo-gold px-4 py-1 rounded text-xs font-bold border border-kodo-gold/20">
|
|
||||||
PROTOCOL ACTIVE
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-kodo-ink p-6 rounded-xl border border-kodo-steel">
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
|
||||||
Server URL
|
|
||||||
</label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono truncate">
|
|
||||||
https://webdav.veza.io/u/cyber_producer
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="border border-kodo-steel"
|
|
||||||
onClick={() =>
|
|
||||||
copyToClipboard('https://webdav.veza.io/u/cyber_producer')
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Copy className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
|
||||||
Username
|
|
||||||
</label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono">
|
|
||||||
cyber_producer
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="border border-kodo-steel"
|
|
||||||
onClick={() => copyToClipboard('cyber_producer')}
|
|
||||||
>
|
|
||||||
<Copy className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="md:col-span-2">
|
|
||||||
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
|
||||||
Mount Password
|
|
||||||
</label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-white font-mono tracking-widest">
|
|
||||||
{webdavPass}
|
|
||||||
</div>
|
|
||||||
<Button variant="secondary" onClick={generateWebdavPass}>
|
|
||||||
Generate New
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-kodo-content-dim mt-2">
|
|
||||||
Use this specific password for mounting. Do not use your account
|
|
||||||
login password.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Webhooks Section */}
|
|
||||||
<Card variant="default">
|
|
||||||
<div className="flex items-start justify-between mb-6">
|
|
||||||
<div>
|
|
||||||
<h3 className="text-xl font-bold text-white flex items-center gap-2">
|
|
||||||
<Globe className="w-5 h-5 text-kodo-steel" /> Storage Webhooks
|
|
||||||
</h3>
|
|
||||||
<p className="text-sm text-kodo-content-dim mt-1">
|
|
||||||
Trigger actions in external apps when files change.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Input
|
|
||||||
placeholder="https://api.your-app.com/hook"
|
|
||||||
value={newHookUrl}
|
|
||||||
onChange={(e) => setNewHookUrl(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
icon={<Plus className="w-4 h-4" />}
|
|
||||||
onClick={addWebhook}
|
|
||||||
>
|
|
||||||
Add
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{webhooks.map((hook) => (
|
|
||||||
<div
|
|
||||||
key={hook.id}
|
|
||||||
className="flex items-center justify-between p-4 bg-kodo-ink rounded border border-kodo-steel"
|
|
||||||
>
|
|
||||||
<div className="flex-1 min-w-0 mr-4">
|
|
||||||
<div className="text-sm font-mono text-white truncate">
|
|
||||||
{hook.url}
|
|
||||||
</div>
|
|
||||||
<div className="text-xs text-kodo-content-dim mt-1 flex gap-2">
|
|
||||||
{hook.events.map((ev) => (
|
|
||||||
<span key={ev} className="bg-white/5 px-1 rounded">
|
|
||||||
{ev}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="text-xs text-kodo-lime flex items-center gap-1">
|
|
||||||
<CheckCircle className="w-3 h-3" /> Active
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="text-kodo-red hover:bg-kodo-red/10"
|
|
||||||
onClick={() =>
|
|
||||||
setWebhooks(webhooks.filter((h) => h.id !== hook.id))
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Trash2 className="w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useConnectivityView } from './useConnectivityView';
|
||||||
|
import { ConnectivityViewWebDAV } from './ConnectivityViewWebDAV';
|
||||||
|
import { ConnectivityViewWebhooks } from './ConnectivityViewWebhooks';
|
||||||
|
|
||||||
|
export function ConnectivityView() {
|
||||||
|
const {
|
||||||
|
webdavPass,
|
||||||
|
webhooks,
|
||||||
|
newHookUrl,
|
||||||
|
setNewHookUrl,
|
||||||
|
generateWebdavPass,
|
||||||
|
addWebhook,
|
||||||
|
removeWebhook,
|
||||||
|
copyToClipboard,
|
||||||
|
} = useConnectivityView();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col gap-8 animate-fadeIn">
|
||||||
|
<ConnectivityViewWebDAV
|
||||||
|
webdavPass={webdavPass}
|
||||||
|
onGeneratePass={generateWebdavPass}
|
||||||
|
onCopy={copyToClipboard}
|
||||||
|
/>
|
||||||
|
<ConnectivityViewWebhooks
|
||||||
|
webhooks={webhooks}
|
||||||
|
newHookUrl={newHookUrl}
|
||||||
|
onNewHookUrlChange={setNewHookUrl}
|
||||||
|
onAddWebhook={addWebhook}
|
||||||
|
onRemoveWebhook={removeWebhook}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
/**
|
||||||
|
* Skeleton for ConnectivityView — layout primitives, no arbitrary values.
|
||||||
|
*/
|
||||||
|
export function ConnectivityViewSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="h-full flex flex-col gap-8 animate-fadeIn">
|
||||||
|
<div className="rounded-xl border border-kodo-steel p-6 space-y-6">
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-6 w-48 rounded bg-muted animate-pulse" />
|
||||||
|
<div className="h-4 w-72 rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="h-6 w-28 rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-kodo-ink p-6 rounded-xl border border-kodo-steel">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-4 w-20 rounded bg-muted animate-pulse" />
|
||||||
|
<div className="h-11 w-full rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-4 w-20 rounded bg-muted animate-pulse" />
|
||||||
|
<div className="h-11 w-full rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2 space-y-2">
|
||||||
|
<div className="h-4 w-28 rounded bg-muted animate-pulse" />
|
||||||
|
<div className="h-11 w-full rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="rounded-xl border border-kodo-steel p-6 space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-6 w-40 rounded bg-muted animate-pulse" />
|
||||||
|
<div className="h-4 w-56 rounded bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="h-11 flex-1 rounded-lg bg-muted animate-pulse" />
|
||||||
|
<div className="h-11 w-20 rounded-lg bg-muted animate-pulse" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="h-16 w-full rounded border border-kodo-steel bg-kodo-ink animate-pulse" />
|
||||||
|
<div className="h-16 w-full rounded border border-kodo-steel bg-kodo-ink animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Folder, Copy } from 'lucide-react';
|
||||||
|
import { DEFAULT_WEBDAV_URL, DEFAULT_WEBDAV_USER } from './types';
|
||||||
|
|
||||||
|
interface ConnectivityViewWebDAVProps {
|
||||||
|
webdavPass: string;
|
||||||
|
onGeneratePass: () => void;
|
||||||
|
onCopy: (text: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConnectivityViewWebDAV({
|
||||||
|
webdavPass,
|
||||||
|
onGeneratePass,
|
||||||
|
onCopy,
|
||||||
|
}: ConnectivityViewWebDAVProps) {
|
||||||
|
return (
|
||||||
|
<Card variant="default">
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white flex items-center gap-2">
|
||||||
|
<Folder className="w-5 h-5 text-kodo-gold" /> Directory Mount (WebDAV)
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-kodo-content-dim mt-1">
|
||||||
|
Access your Cloud Studio files directly from your OS file explorer or DAW.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-kodo-gold/10 text-kodo-gold px-4 py-1 rounded text-xs font-bold border border-kodo-gold/20">
|
||||||
|
PROTOCOL ACTIVE
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 bg-kodo-ink p-6 rounded-xl border border-kodo-steel">
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
||||||
|
Server URL
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono truncate">
|
||||||
|
{DEFAULT_WEBDAV_URL}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="border border-kodo-steel"
|
||||||
|
onClick={() => onCopy(DEFAULT_WEBDAV_URL)}
|
||||||
|
aria-label="Copy Server URL"
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono">
|
||||||
|
{DEFAULT_WEBDAV_USER}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="border border-kodo-steel"
|
||||||
|
onClick={() => onCopy(DEFAULT_WEBDAV_USER)}
|
||||||
|
aria-label="Copy Username"
|
||||||
|
>
|
||||||
|
<Copy className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
||||||
|
Mount Password
|
||||||
|
</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex-1 bg-kodo-graphite border border-kodo-steel rounded px-4 py-2 text-sm text-white font-mono tracking-widest">
|
||||||
|
{webdavPass}
|
||||||
|
</div>
|
||||||
|
<Button variant="secondary" onClick={onGeneratePass}>
|
||||||
|
Generate New
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-kodo-content-dim mt-2">
|
||||||
|
Use this specific password for mounting. Do not use your account login password.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { Card } from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Input } from '@/components/ui/input';
|
||||||
|
import { Globe, Plus, Trash2, CheckCircle } from 'lucide-react';
|
||||||
|
import type { WebhookItem } from './types';
|
||||||
|
|
||||||
|
interface ConnectivityViewWebhooksProps {
|
||||||
|
webhooks: WebhookItem[];
|
||||||
|
newHookUrl: string;
|
||||||
|
onNewHookUrlChange: (value: string) => void;
|
||||||
|
onAddWebhook: () => void;
|
||||||
|
onRemoveWebhook: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ConnectivityViewWebhooks({
|
||||||
|
webhooks,
|
||||||
|
newHookUrl,
|
||||||
|
onNewHookUrlChange,
|
||||||
|
onAddWebhook,
|
||||||
|
onRemoveWebhook,
|
||||||
|
}: ConnectivityViewWebhooksProps) {
|
||||||
|
return (
|
||||||
|
<Card variant="default">
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold text-white flex items-center gap-2">
|
||||||
|
<Globe className="w-5 h-5 text-kodo-steel" /> Storage Webhooks
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-kodo-content-dim mt-1">
|
||||||
|
Trigger actions in external apps when files change.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Input
|
||||||
|
placeholder="https://api.your-app.com/hook"
|
||||||
|
value={newHookUrl}
|
||||||
|
onChange={(e) => onNewHookUrlChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Button variant="primary" icon={<Plus className="w-4 h-4" />} onClick={onAddWebhook}>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{webhooks.map((hook) => (
|
||||||
|
<div
|
||||||
|
key={hook.id}
|
||||||
|
className="flex items-center justify-between p-4 bg-kodo-ink rounded border border-kodo-steel"
|
||||||
|
>
|
||||||
|
<div className="flex-1 min-w-0 mr-4">
|
||||||
|
<div className="text-sm font-mono text-white truncate">{hook.url}</div>
|
||||||
|
<div className="text-xs text-kodo-content-dim mt-1 flex gap-2">
|
||||||
|
{hook.events.map((ev) => (
|
||||||
|
<span key={ev} className="bg-white/5 px-1 rounded">
|
||||||
|
{ev}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="text-xs text-kodo-lime flex items-center gap-1">
|
||||||
|
<CheckCircle className="w-3 h-3" /> Active
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="text-kodo-red hover:bg-kodo-red/10"
|
||||||
|
onClick={() => onRemoveWebhook(hook.id)}
|
||||||
|
aria-label="Remove webhook"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
export { ConnectivityView } from './ConnectivityView';
|
||||||
|
export { ConnectivityViewSkeleton } from './ConnectivityViewSkeleton';
|
||||||
|
export { ConnectivityViewWebDAV } from './ConnectivityViewWebDAV';
|
||||||
|
export { ConnectivityViewWebhooks } from './ConnectivityViewWebhooks';
|
||||||
|
export { useConnectivityView } from './useConnectivityView';
|
||||||
|
export { DEFAULT_WEBDAV_URL, DEFAULT_WEBDAV_USER } from './types';
|
||||||
|
export type { WebhookItem } from './types';
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
export interface WebhookItem {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
events: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_WEBDAV_URL = 'https://webdav.veza.io/u/cyber_producer';
|
||||||
|
export const DEFAULT_WEBDAV_USER = 'cyber_producer';
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { useState, useCallback } from 'react';
|
||||||
|
import { useToast } from '@/components/feedback/ToastProvider';
|
||||||
|
import type { WebhookItem } from './types';
|
||||||
|
|
||||||
|
export function useConnectivityView() {
|
||||||
|
const { addToast } = useToast();
|
||||||
|
const [webdavPass, setWebdavPass] = useState('****');
|
||||||
|
const [webhooks, setWebhooks] = useState<WebhookItem[]>([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
url: 'https://api.myapp.com/hooks/veza',
|
||||||
|
events: ['file.upload', 'file.delete'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const [newHookUrl, setNewHookUrl] = useState('');
|
||||||
|
|
||||||
|
const generateWebdavPass = useCallback(() => {
|
||||||
|
setWebdavPass(`wd-${Math.random().toString(36).substring(2, 12)}`);
|
||||||
|
addToast('New WebDAV password generated', 'success');
|
||||||
|
}, [addToast]);
|
||||||
|
|
||||||
|
const addWebhook = useCallback(() => {
|
||||||
|
if (!newHookUrl.trim()) return;
|
||||||
|
setWebhooks((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
id: Date.now().toString(),
|
||||||
|
url: newHookUrl.trim(),
|
||||||
|
events: ['file.upload'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
setNewHookUrl('');
|
||||||
|
addToast('Webhook endpoint added', 'success');
|
||||||
|
}, [newHookUrl, addToast]);
|
||||||
|
|
||||||
|
const removeWebhook = useCallback((id: string) => {
|
||||||
|
setWebhooks((prev) => prev.filter((h) => h.id !== id));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const copyToClipboard = useCallback(
|
||||||
|
(text: string) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
addToast('Copied to clipboard');
|
||||||
|
},
|
||||||
|
[addToast],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
webdavPass,
|
||||||
|
webhooks,
|
||||||
|
newHookUrl,
|
||||||
|
setNewHookUrl,
|
||||||
|
generateWebdavPass,
|
||||||
|
addWebhook,
|
||||||
|
removeWebhook,
|
||||||
|
copyToClipboard,
|
||||||
|
};
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue