Added libs
This commit is contained in:
126
noSys/vue/api/noSysApi.js
Normal file
126
noSys/vue/api/noSysApi.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import { useNoSysStore } from '../stores/noSysStore'
|
||||
|
||||
const noSysApiUrl = "/api/noSys";
|
||||
const noSysStore = useNoSysStore()
|
||||
|
||||
export const noSysApi = {
|
||||
|
||||
async getUsers(){
|
||||
let users = {}
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+'/users')
|
||||
if (!response.ok) throw new Error("Error fetching users:")
|
||||
users = await response.json()
|
||||
return users;
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getUser(userId){
|
||||
let user = {}
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+'/users/'+userId)
|
||||
if (!response.ok) throw new Error("Error fetching users:")
|
||||
user = await response.json()
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async getConfigs(){
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+'/configs')
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching configs: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const result = await response.json()
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching configs:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async connectAddress(user, ip, port){
|
||||
const data = {"user": user, "address": {"ip": ip,"port": port}};
|
||||
const requestOptions = {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json',},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+"/peers", requestOptions);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching users: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async listPeers(){
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+'/peers')
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching peers: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const result = await response.json()
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching peers:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async disconnectPeer(connectionId){
|
||||
const data = {};
|
||||
const requestOptions = {
|
||||
method: 'DELETE',
|
||||
headers: {'Content-Type': 'application/json',},
|
||||
body: JSON.stringify(data),
|
||||
};
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+"/peers/"+connectionId, requestOptions);
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching users: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error fetching users:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async listNetworks(){
|
||||
try {
|
||||
const response = await fetch(noSysApiUrl+'/networks')
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Error fetching networks: ${response.status} - ${errorText}`);
|
||||
}
|
||||
const result = await response.json()
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("Error fetching networks:", error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
}
|
||||
15
noSys/vue/api/socketEvents.js
Normal file
15
noSys/vue/api/socketEvents.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useNoSysStore } from '../stores/noSysStore'
|
||||
|
||||
const noSysStore = useNoSysStore()
|
||||
|
||||
export default function registerSocketEvents(socket) {
|
||||
socket.on('connect', (data) => {
|
||||
console.log('Connected noSys')
|
||||
}),
|
||||
|
||||
socket.on('newUser', (data) => {
|
||||
noSysStore.addUser({publicKey:data})
|
||||
console.log('New user', data)
|
||||
})
|
||||
|
||||
}
|
||||
282
noSys/vue/components/ConnectionsTab.vue
Normal file
282
noSys/vue/components/ConnectionsTab.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<script setup>
|
||||
import Button from '@/components/buttons/Button.vue';
|
||||
import InputText from '@/components/inputs/InputText.vue';
|
||||
import Label from '@/components/labels/Label.vue';
|
||||
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue';
|
||||
import { noSysApi } from '../api/noSysApi';
|
||||
import InputComboBox from '@/components/inputs/InputComboBox.vue';
|
||||
import { useNoSysStore } from '../stores/noSysStore'
|
||||
import { getSocket } from '@/plugins/socketioManager'
|
||||
import Card from '@/components/cards/Card.vue';
|
||||
import { ArrowPathIcon, EllipsisHorizontalIcon, GlobeAltIcon, LinkIcon, MapIcon, WifiIcon, XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import { TabPanel} from '@headlessui/vue'
|
||||
|
||||
var socket = null
|
||||
const noSysStore = useNoSysStore()
|
||||
|
||||
const ip = ref("n0sys.duckdns.org")
|
||||
const port = ref("30331")
|
||||
const user = ref()
|
||||
const peers = ref([])
|
||||
|
||||
async function listUsers(){
|
||||
noSysStore.users.value = await noSysApi.getUsers()
|
||||
}
|
||||
|
||||
async function connect(){
|
||||
const response = await noSysApi.connectAddress(user.value.user, ip.value, port.value)
|
||||
}
|
||||
|
||||
async function reconnect(connectionId){
|
||||
// const response = await noSysApi.connectAddress(user.value.user, ip.value, port.value)
|
||||
}
|
||||
|
||||
async function disconnect(connectionId){
|
||||
const response = await noSysApi.disconnectPeer(connectionId)
|
||||
}
|
||||
|
||||
async function listPeers(){
|
||||
peers.value = await noSysApi.listPeers()
|
||||
}
|
||||
|
||||
onActivated(async () => {
|
||||
socket = await getSocket("noSys_noSys")
|
||||
socket.on('tempEvent', onTemporaryEvent)
|
||||
|
||||
listUsers()
|
||||
listPeers()
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
socket.off('tempEvent')
|
||||
});
|
||||
|
||||
function onTemporaryEvent(data) {
|
||||
console.log('Received temporary event in HomeView:', data)
|
||||
}
|
||||
|
||||
// function sendMessage(){
|
||||
// messageElement = document.getElementById("message")
|
||||
// message = messageElement.value;
|
||||
// if (connectionClicked && message){
|
||||
// const data = {"message":{"action":"test", "data":message},"toModule": {"package": "noSys","module": "noSys"},"encrypted": true};
|
||||
// const requestOptions = {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json',
|
||||
// },
|
||||
// body: JSON.stringify(data),
|
||||
// };
|
||||
|
||||
// fetch(noSysApiUrl+"/peers/"+connectionClicked, requestOptions)
|
||||
// .then(response => {
|
||||
// if (!response.ok) {
|
||||
// throw new Error('Network response was not ok');
|
||||
// }
|
||||
// return response.json();
|
||||
// })
|
||||
// .then(data => {
|
||||
// console.log(data);
|
||||
// addMessage(connectionClicked, message, false)
|
||||
// messageElement.value = ""
|
||||
// })
|
||||
// .catch(error => {
|
||||
// console.error
|
||||
|
||||
// ('Error:', error);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabPanel>
|
||||
<div class="container mx-auto px-6 py-1 space-y-8">
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<Card class="border-yellow-400/20 p-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-12 h-12 bg-green-500/20 rounded-lg flex items-center justify-center">
|
||||
<WifiIcon class="text-green-400 w-6 h-6"/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-white">
|
||||
{{peers.filter((p) => p.status === "CONNECTED").length}}
|
||||
</p>
|
||||
<p class="text-gray-400 text-sm">Connected</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class=" border-yellow-400/20 p-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-12 h-12 bg-yellow-500/20 rounded-lg flex items-center justify-center">
|
||||
<EllipsisHorizontalIcon class="text-yellow-400 h-6 w-6" size={24} />
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-white">
|
||||
{{peers.filter((p) => p.status === "CONNECTING").length}}
|
||||
</p>
|
||||
<p class="text-gray-400 text-sm">Connecting</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class=" border-yellow-400/20 p-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-12 h-12 bg-red-500/20 rounded-lg flex items-center justify-center">
|
||||
<XMarkIcon class="text-red-400 h-6 w-6"/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-white">
|
||||
{{peers.filter((p) => p.status === "ERROR").length}}
|
||||
</p>
|
||||
<p class="text-gray-400 text-sm">Failed</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class=" border-yellow-400/20 p-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-12 h-12 bg-blue-500/20 rounded-lg flex items-center justify-center">
|
||||
<LinkIcon class="text-blue-400 h-6 w-6"/>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-white">{{peers.length}}</p>
|
||||
<p class="text-gray-400 text-sm">Total</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card class="bg-black border-yellow-400/20 p-8">
|
||||
<div class="flex items-center space-x-3 mb-6">
|
||||
<LinkIcon class="text-yellow-400 h-6 w-6" />
|
||||
<h2 class="text-2xl font-bold text-yellow-400">Connect to Peer</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-4 gap-6">
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">IP Address</Label>
|
||||
<InputText id="ip" type="text" v-model="ip" placeholder="" class="border-yellow-400/30 w-full"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">Port</Label>
|
||||
<InputText id="port" type="text" v-model="port" placeholder="" class="border-yellow-400/30 w-full"/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">User</Label>
|
||||
<InputComboBox v-model="user" :items="noSysStore.users.value" labelKey="user" keyField="user" class="border-yellow-400/30"></InputComboBox>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<Button @click="connect()" class="w-full bg-yellow-400 text-black hover:bg-yellow-300 font-bold">Connect</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class=" border-yellow-400/20 p-8">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<GlobeAltIcon class="text-yellow-400 h-6 w-6" />
|
||||
<h2 class="text-2xl font-bold text-yellow-400">Active Connections</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="peers.length === 0" class="text-center py-12">
|
||||
<WifiIcon class="text-gray-600 mx-auto mb-4 h-7 w-7" />
|
||||
<p class="text-gray-400 text-lg">No connections established</p>
|
||||
<p class="text-gray-500 text-sm">Connect to peers to start building the decentralized network</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div v-for="peer in peers" :key="peer.id" class="bg-black border border-yellow-400/20 rounded-lg p-6">
|
||||
<div class="grid lg:grid-cols-7 gap-4 items-center">
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Connection ID</Label>
|
||||
<p class="text-white font-mono text-sm">{{ peer.id }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Address</Label>
|
||||
<p class="text-white text-sm">
|
||||
{{peer.address}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Bind Address</Label>
|
||||
<p class="text-white text-sm">
|
||||
{{peer.bindAddress}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Status</Label>
|
||||
<div class="flex items-center space-x-2"
|
||||
:class="{
|
||||
'text-green-400': peer.status === 'CONNECTED',
|
||||
'text-blue-400': peer.status === 'CONNECTING',
|
||||
'text-yellow-400': peer.status === 'HANDSHAKING',
|
||||
'text-red-400': peer.status === 'DISCONNECTED',
|
||||
'text-gray-300': !['CONNECTED', 'CONNECTING', 'HANDSHAKING', 'DISCONNECTED'].includes(peer.status)
|
||||
}">
|
||||
{{peer.status}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Users</Label>
|
||||
<p class="text-sm">
|
||||
<span class="text-yellow-400">{{peer.user}}</span>
|
||||
<!-- TODO MyUser, PeerUser/ProofOfWork -->
|
||||
<span class="text-gray-400"> ↔ </span>
|
||||
<span class="text-blue-400">{{peer.user}}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Last Activity</Label>
|
||||
<!-- TODO Last Activity -->
|
||||
<p class="text-white text-sm">{{ new Date().toLocaleTimeString() }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button v-if="peer.status === 'CONNECTED'"
|
||||
@click="disconnect(peer.id)"
|
||||
class="bg-red-500 hover:bg-red-600 text-white"
|
||||
>
|
||||
<XMarkIcon class="mr-1 h-4 w-4" />
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Button v-if="peer.status === 'DISCONNECTED'"
|
||||
@click="reconnect(peer.id)"
|
||||
class="bg-green-500 hover:bg-green-600 text-white"
|
||||
>
|
||||
<ArrowPathIcon class="mr-1 h-4 w-4" />
|
||||
Reconnect
|
||||
</Button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-yellow-400/10">
|
||||
<div class="flex justify-between text-xs text-gray-400">
|
||||
<!-- Connected At -->
|
||||
<span>Connected: {{new Date(peer.connectedAt).toLocaleString()}}</span>
|
||||
<span>
|
||||
Duration: {{Math.floor((Date.now() - new Date(peer.connectedAt).getTime()) / 60000)}}{" "}
|
||||
minutes
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
</div>
|
||||
</TabPanel>
|
||||
</template>
|
||||
183
noSys/vue/components/NetworksTabs.vue
Normal file
183
noSys/vue/components/NetworksTabs.vue
Normal file
@@ -0,0 +1,183 @@
|
||||
<script setup>
|
||||
import Button from '@/components/buttons/Button.vue';
|
||||
import Card from '@/components/cards/Card.vue';
|
||||
import InputComboBox from '@/components/inputs/InputComboBox.vue';
|
||||
import InputText from '@/components/inputs/InputText.vue';
|
||||
import Label from '@/components/labels/Label.vue';
|
||||
import { TabPanel} from '@headlessui/vue'
|
||||
import { CogIcon, MagnifyingGlassIcon, PlusIcon, TrashIcon } from '@heroicons/vue/24/solid';
|
||||
import { noSysApi } from '../api/noSysApi';
|
||||
import { useNoSysStore } from '../stores/noSysStore';
|
||||
import { onActivated, onDeactivated } from 'vue';
|
||||
|
||||
const noSysStore = useNoSysStore()
|
||||
|
||||
async function listNetworks(){
|
||||
noSysStore.networks.value = await noSysApi.listNetworks()
|
||||
}
|
||||
|
||||
onActivated(()=>{
|
||||
listNetworks()
|
||||
})
|
||||
|
||||
onDeactivated(()=>{
|
||||
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<TabPanel>
|
||||
<Card class="border-yellow-400/20 p-8 mb-8">
|
||||
<div class="flex items-center space-x-3 mb-6">
|
||||
<PlusIcon class="text-yellow-400 w-6 h-6"/>
|
||||
<h2 class="text-2xl font-bold text-yellow-400">Add Network</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">Network ID</Label>
|
||||
<InputText
|
||||
class="bg-black border-yellow-400/30 text-white"
|
||||
placeholder="ID"
|
||||
/>
|
||||
</div>
|
||||
<Button class="mt-6 bg-yellow-400 text-black hover:bg-yellow-300 font-bold w-full">
|
||||
<MagnifyingGlassIcon class="w-4 h-4 mr-2 text-black" />
|
||||
<p class="text-black">Search Network</p>
|
||||
</Button>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">Network Name</Label>
|
||||
<InputText
|
||||
class="bg-black border-yellow-400/30 text-white"
|
||||
placeholder="Name"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-300 mb-2 block">Network Description</Label>
|
||||
<InputText
|
||||
class="bg-black border-yellow-400/30 text-white"
|
||||
placeholder="Description"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<Label class="text-gray-300 mb-2 block">Modules</Label>
|
||||
<InputComboBox
|
||||
class="bg-black border-yellow-400/30 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<Label class="text-gray-300 mb-2 block">Network Type</Label>
|
||||
<InputComboBox
|
||||
class="bg-black border-yellow-400/30 text-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-x-3">
|
||||
<Button class="mt-6 bg-yellow-400 text-black hover:bg-yellow-300 font-bold w-full">
|
||||
<PlusIcon class="w-4 h-4 mr-2 text-black" />
|
||||
<p class="text-black">Add Network</p>
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card class="border-yellow-400/20 p-8">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- <Network class="text-yellow-400" size={24} /> -->
|
||||
<h2 class="text-2xl font-bold text-yellow-400">Networks</h2>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400">{networks.length} networks</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="!noSysStore.networks.value" class="text-center py-12">
|
||||
<Network size={48} class="text-gray-600 mx-auto mb-4" />
|
||||
<p class="text-gray-400 text-lg">No networks created</p>
|
||||
<p class="text-gray-500 text-sm">Create a network to start connecting with peers</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-4">
|
||||
<div v-for="network in noSysStore.networks.value" key={network.id} class="bg-black border border-yellow-400/20 rounded-lg p-6">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-3 mb-3">
|
||||
<h3 class="text-xl font-bold text-white">{{network.data.name}}</h3>
|
||||
<span class="bg-yellow-400/20 text-yellow-400 px-2 py-1 rounded text-sm">
|
||||
{{network.data.id}}
|
||||
</span>
|
||||
<span
|
||||
class="px-2 py-1 rounded text-sm"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid md:grid-cols-4 gap-4 mb-4">
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Total Connections</Label>
|
||||
<p class="text-2xl font-bold text-green-400">{{network.state.peers.length}}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Status</Label>
|
||||
<p
|
||||
class=""
|
||||
>
|
||||
{{network.state.status}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Auto Fill</Label>
|
||||
<p class="">
|
||||
{{network.state.managed}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label class="text-gray-400 text-xs">Users</Label>
|
||||
<p class="">
|
||||
{{network.state.users.length}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<Label class="text-gray-400 text-xs">Modules</Label>
|
||||
<div class="flex flex-row gap-2">
|
||||
<div v-for="module in network.data.modules" class="flex flex-wrap mt-1">
|
||||
<span class="bg-blue-500/20 text-blue-400 px-2 py-1 rounded text-sm">
|
||||
{{module}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex space-x-2">
|
||||
<Button
|
||||
class="border-yellow-400 text-yellow-400 hover:bg-yellow-400 hover:text-black bg-transparent"
|
||||
>
|
||||
<CogIcon class="h-4 w-4 mr-1" />
|
||||
Configure
|
||||
</Button>
|
||||
<Button
|
||||
class="bg-red-500 hover:bg-red-600 text-white"
|
||||
>
|
||||
<TrashIcon class="h-4 w-4mr-1" />
|
||||
Quit
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</TabPanel>
|
||||
</template>
|
||||
7
noSys/vue/router.js
Normal file
7
noSys/vue/router.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import HomeView from "./views/HomeView.vue";
|
||||
|
||||
const routes = [
|
||||
{path: '/', name:'noSys', component: HomeView},
|
||||
]
|
||||
|
||||
export {routes};
|
||||
57
noSys/vue/stores/noSysStore.js
Normal file
57
noSys/vue/stores/noSysStore.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useNoSysStore = defineStore('noSysStore', {
|
||||
state: () => ({
|
||||
users: [], // [{ publicKey }]
|
||||
connections: [], // [{ publicKey, connectedAt, network }]
|
||||
networks: [],
|
||||
}),
|
||||
|
||||
getters: {
|
||||
getUserByKey: (state) => (key) => {
|
||||
return state.users.find(u => u.publicKey === key)
|
||||
},
|
||||
getConnectionsByNetwork: (state) => (network) => {
|
||||
return state.connections.filter(c => c.network === network)
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
addUser(user) {
|
||||
if (!this.users.find(u => u.publicKey === user.publicKey)) {
|
||||
this.users.push(user)
|
||||
}
|
||||
},
|
||||
|
||||
removeUser(publicKey) {
|
||||
this.users = this.users.filter(u => u.publicKey !== publicKey)
|
||||
},
|
||||
|
||||
updateUser(updated) {
|
||||
const index = this.users.findIndex(u => u.publicKey === updated.publicKey)
|
||||
if (index !== -1) this.users[index] = { ...this.users[index], ...updated }
|
||||
},
|
||||
|
||||
addConnection(conn) {
|
||||
const exists = this.connections.find(
|
||||
c => c.publicKey === conn.publicKey && c.network === conn.network
|
||||
)
|
||||
if (!exists) {
|
||||
this.connections.push({
|
||||
...conn,
|
||||
connectedAt: conn.connectedAt || new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
removeConnection(publicKey, network = null) {
|
||||
this.connections = this.connections.filter(
|
||||
c => c.publicKey !== publicKey || (network && c.network !== network)
|
||||
)
|
||||
},
|
||||
|
||||
clearConnections() {
|
||||
this.connections = []
|
||||
}
|
||||
}
|
||||
})
|
||||
59
noSys/vue/views/HomeView.vue
Normal file
59
noSys/vue/views/HomeView.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<script setup>
|
||||
import Button from '@/components/buttons/Button.vue';
|
||||
import InputText from '@/components/inputs/InputText.vue';
|
||||
import Label from '@/components/labels/Label.vue';
|
||||
import { ref, onMounted, onUnmounted, onActivated, onDeactivated } from 'vue';
|
||||
import { noSysApi } from '../api/noSysApi';
|
||||
import InputComboBox from '@/components/inputs/InputComboBox.vue';
|
||||
import { useNoSysStore } from '../stores/noSysStore'
|
||||
import { getSocket } from '@/plugins/socketioManager'
|
||||
import Card from '@/components/cards/Card.vue';
|
||||
import { ArrowPathIcon, EllipsisHorizontalIcon, GlobeAltIcon, LinkIcon, MapIcon, WifiIcon, XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import ConnectionsTab from '../components/ConnectionsTab.vue';
|
||||
import NetworksTabs from '../components/NetworksTabs.vue';
|
||||
import { TabGroup, TabList, Tab, TabPanels} from '@headlessui/vue'
|
||||
|
||||
|
||||
const tabItems = [
|
||||
{label:"Connections", icon:LinkIcon, tabComponent:ConnectionsTab},
|
||||
{label:"Networks", icon:MapIcon, tabComponent:NetworksTabs},
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-black text-yellow-400">
|
||||
<header class="border-b border-yellow-400/20 py-6">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-3xl font-black">
|
||||
<span class="text-yellow-400">NO</span>
|
||||
<span class="text-white">SYS</span>
|
||||
</div>
|
||||
<div class="w-1 h-8 bg-yellow-400"></div>
|
||||
<span class="text-gray-400">Peer-to-Peer Network Connection</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="container mx-auto px-6">
|
||||
<TabGroup>
|
||||
<TabList class="flex flex-wrap gap-2 border-b border-yellow-400/20">
|
||||
<Tab v-for="tab in tabItems" as="template" :key="tab" v-slot="{ selected }">
|
||||
<button class="flex items-center space-x-2 px-6 py-3 font-semibold transition-all duration-300 border-b-2"
|
||||
:class="{ 'text-yellow-400 border-yellow-400': selected, 'text-gray-400 border-transparent hover:text-yellow-400 hover:border-yellow-400/50': !selected }">
|
||||
<component :is="tab.icon" class="h-4 w-4"></component>
|
||||
<span>{{ tab.label }}</span>
|
||||
</button>
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels class="mt-6 space-y-6" v-for="tab in tabItems" :key="tab.label">
|
||||
<component :is="tab.tabComponent"></component>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
Reference in New Issue
Block a user