Compare commits
11 Commits
feat/secur
...
main
Author | SHA1 | Date | |
---|---|---|---|
978aafc204 | |||
47fd9bd859 | |||
13bb965b28 | |||
5405c3e12f | |||
1eab163e10 | |||
7c054d6ba3 | |||
4a46cd505d | |||
1fa91a7228 | |||
8e91724462 | |||
1a1b44743a | |||
827eceed2b |
58
analysis.py
58
analysis.py
@ -24,10 +24,10 @@ P = Player
|
|||||||
def sociogram_json():
|
def sociogram_json():
|
||||||
nodes = []
|
nodes = []
|
||||||
necessary_nodes = set()
|
necessary_nodes = set()
|
||||||
links = []
|
edges = []
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
for p in session.exec(select(P)).fetchall():
|
for p in session.exec(select(P)).fetchall():
|
||||||
nodes.append({"id": p.name, "appearance": 1})
|
nodes.append({"id": p.name, "label": p.name})
|
||||||
subquery = (
|
subquery = (
|
||||||
select(C.user, func.max(C.time).label("latest"))
|
select(C.user, func.max(C.time).label("latest"))
|
||||||
.where(C.time > datetime(2025, 2, 1, 10))
|
.where(C.time > datetime(2025, 2, 1, 10))
|
||||||
@ -44,9 +44,58 @@ def sociogram_json():
|
|||||||
# G.add_edge(c.user, p)
|
# G.add_edge(c.user, p)
|
||||||
# p_id = session.exec(select(P.id).where(P.name == p)).one()
|
# p_id = session.exec(select(P.id).where(P.name == p)).one()
|
||||||
necessary_nodes.add(p)
|
necessary_nodes.add(p)
|
||||||
links.append({"source": c.user, "target": p})
|
edges.append({"from": c.user, "to": p, "relation": "likes"})
|
||||||
|
for p in c.hate:
|
||||||
|
edges.append({"from": c.user, "to": p, "relation": "dislikes"})
|
||||||
# nodes = [n for n in nodes if n["name"] in necessary_nodes]
|
# nodes = [n for n in nodes if n["name"] in necessary_nodes]
|
||||||
return JSONResponse({"nodes": nodes, "links": links})
|
return JSONResponse({"nodes": nodes, "edges": edges})
|
||||||
|
|
||||||
|
|
||||||
|
def graph_json():
|
||||||
|
nodes = []
|
||||||
|
edges = []
|
||||||
|
with Session(engine) as session:
|
||||||
|
for p in session.exec(select(P)).fetchall():
|
||||||
|
nodes.append({"id": p.name, "label": p.name})
|
||||||
|
subquery = (
|
||||||
|
select(C.user, func.max(C.time).label("latest"))
|
||||||
|
.where(C.time > datetime(2025, 2, 1, 10))
|
||||||
|
.group_by(C.user)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
statement2 = select(C).join(
|
||||||
|
subquery, (C.user == subquery.c.user) & (C.time == subquery.c.latest)
|
||||||
|
)
|
||||||
|
for c in session.exec(statement2):
|
||||||
|
for i, p in enumerate(c.love):
|
||||||
|
edges.append(
|
||||||
|
{
|
||||||
|
"id": f"{c.user}->{p}",
|
||||||
|
"source": c.user,
|
||||||
|
"target": p,
|
||||||
|
"size": max(1.0 - 0.1 * i, 0.3),
|
||||||
|
"data": {"relation": 2},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for p in c.hate:
|
||||||
|
edges.append(
|
||||||
|
{
|
||||||
|
"id": f"{c.user}-x>{p}",
|
||||||
|
"source": c.user,
|
||||||
|
"target": p,
|
||||||
|
"size": 0.3,
|
||||||
|
"data": {"relation": 0},
|
||||||
|
"fill": "#ff7c7c",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
G = nx.DiGraph()
|
||||||
|
G.add_weighted_edges_from([(e["source"], e["target"], e["size"]) for e in edges])
|
||||||
|
in_degrees = G.in_degree(weight="weight")
|
||||||
|
nodes = [
|
||||||
|
dict(node, **{"data": {"inDegree": in_degrees[node["id"]]}}) for node in nodes
|
||||||
|
]
|
||||||
|
return JSONResponse({"nodes": nodes, "edges": edges})
|
||||||
|
|
||||||
|
|
||||||
def sociogram_data(show: int | None = 2):
|
def sociogram_data(show: int | None = 2):
|
||||||
@ -144,6 +193,7 @@ async def render_sociogram(params: Params):
|
|||||||
|
|
||||||
|
|
||||||
analysis_router.add_api_route("/json", endpoint=sociogram_json, methods=["GET"])
|
analysis_router.add_api_route("/json", endpoint=sociogram_json, methods=["GET"])
|
||||||
|
analysis_router.add_api_route("/graph_json", endpoint=graph_json, methods=["GET"])
|
||||||
analysis_router.add_api_route("/image", endpoint=render_sociogram, methods=["POST"])
|
analysis_router.add_api_route("/image", endpoint=render_sociogram, methods=["POST"])
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
2
db.py
2
db.py
@ -12,7 +12,7 @@ from sqlmodel import (
|
|||||||
with open("db.secrets", "r") as f:
|
with open("db.secrets", "r") as f:
|
||||||
db_secrets = f.readline().strip()
|
db_secrets = f.readline().strip()
|
||||||
|
|
||||||
engine = create_engine(db_secrets)
|
engine = create_engine(db_secrets, connect_args={"connect_timeout": 8})
|
||||||
del db_secrets
|
del db_secrets
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,15 +10,14 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"d3": "^7.9.0",
|
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-sortablejs": "^6.1.4",
|
"react-sortablejs": "^6.1.4",
|
||||||
|
"reagraph": "^4.21.2",
|
||||||
"sortablejs": "^1.15.6"
|
"sortablejs": "^1.15.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
"@types/d3": "^7.4.3",
|
|
||||||
"@types/react": "^18.3.18",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.5",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@types/sortablejs": "^1.15.8",
|
"@types/sortablejs": "^1.15.8",
|
||||||
|
@ -9,6 +9,7 @@ from db import engine, User
|
|||||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseSettings):
|
||||||
@ -47,10 +48,13 @@ def get_password_hash(password):
|
|||||||
|
|
||||||
def get_user(username: str | None):
|
def get_user(username: str | None):
|
||||||
if username:
|
if username:
|
||||||
|
try:
|
||||||
with Session(engine) as session:
|
with Session(engine) as session:
|
||||||
return session.exec(
|
return session.exec(
|
||||||
select(User).where(User.username == username)
|
select(User).where(User.username == username)
|
||||||
).one_or_none()
|
).one_or_none()
|
||||||
|
except OperationalError:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str):
|
def authenticate_user(username: str, password: str):
|
||||||
|
103
src/App.css
103
src/App.css
@ -23,6 +23,87 @@ footer {
|
|||||||
font-size: x-small;
|
font-size: x-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*=========Network Controls=========*/
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
z-index: 9;
|
||||||
|
position: absolute;
|
||||||
|
width: 240px;
|
||||||
|
right: 24px;
|
||||||
|
top: 1vh;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
.control {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 4px 2px;
|
||||||
|
background-color: aliceblue;
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#three-slider {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The switch - the box around the slider */
|
||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
width: 68px;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide default HTML checkbox */
|
||||||
|
.switch input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The slider */
|
||||||
|
.slider {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: #ccc;
|
||||||
|
border-radius: 34px;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slider:before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
left: 4px;
|
||||||
|
bottom: 4px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-transition: .4s;
|
||||||
|
transition: .4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+.slider {
|
||||||
|
background-color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus+.slider {
|
||||||
|
box-shadow: 0 0 1px #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:checked+.slider:before {
|
||||||
|
-webkit-transform: translateX(26px);
|
||||||
|
-ms-transform: translateX(26px);
|
||||||
|
transform: translateX(26px);
|
||||||
|
}
|
||||||
|
|
||||||
.grey {
|
.grey {
|
||||||
color: #444;
|
color: #444;
|
||||||
@ -168,13 +249,24 @@ button,
|
|||||||
#control-panel {
|
#control-panel {
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control {
|
||||||
|
font-size: 80%;
|
||||||
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (max-width: 768px) {
|
@media only screen and (max-width: 768px) {
|
||||||
#control-panel {
|
#control-panel {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.networkroute {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.submit_text {
|
.submit_text {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -241,6 +333,8 @@ button,
|
|||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*======LOGO=======*/
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
position: relative;
|
position: relative;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -268,6 +362,15 @@ button,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.networkroute {
|
||||||
|
z-index: 10;
|
||||||
|
position: absolute;
|
||||||
|
top: 24px;
|
||||||
|
left: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*======SPINNER=======*/
|
||||||
|
|
||||||
.loader {
|
.loader {
|
||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
12
src/App.tsx
12
src/App.tsx
@ -5,19 +5,19 @@ import Header from "./Header";
|
|||||||
import Rankings from "./Rankings";
|
import Rankings from "./Rankings";
|
||||||
import { BrowserRouter, Routes, Route } from "react-router";
|
import { BrowserRouter, Routes, Route } from "react-router";
|
||||||
import { SessionProvider } from "./Session";
|
import { SessionProvider } from "./Session";
|
||||||
|
import { GraphComponent } from "./Network";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
//const [data, setData] = useState({ nodes: [], links: [] } as SociogramData);
|
|
||||||
//async function loadData() {
|
|
||||||
// await fetch(`${baseUrl}api/analysis/json`, { method: "GET" }).then(resp => resp.json() as unknown as SociogramData).then(json => { setData(json) })
|
|
||||||
//}
|
|
||||||
//useEffect(() => { loadData() }, [])
|
|
||||||
//
|
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Header />
|
<Header />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<Rankings />} />
|
<Route index element={<Rankings />} />
|
||||||
|
<Route path="/network" element={
|
||||||
|
<SessionProvider>
|
||||||
|
<GraphComponent />
|
||||||
|
</SessionProvider>
|
||||||
|
} />
|
||||||
<Route path="/analysis" element={
|
<Route path="/analysis" element={
|
||||||
<SessionProvider>
|
<SessionProvider>
|
||||||
<Analysis />
|
<Analysis />
|
||||||
|
@ -5,7 +5,7 @@ export default function Footer() {
|
|||||||
<div className="navbar">
|
<div className="navbar">
|
||||||
<Link to="/" ><span>Form</span></Link>
|
<Link to="/" ><span>Form</span></Link>
|
||||||
<span>|</span>
|
<span>|</span>
|
||||||
<Link to="/analysis" ><span>Trainer Analysis</span></Link>
|
<Link to="/network" ><span>Trainer Analysis</span></Link>
|
||||||
</div>
|
</div>
|
||||||
<p className="grey extra-margin">
|
<p className="grey extra-margin">
|
||||||
something not working?
|
something not working?
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { baseUrl } from "./api";
|
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
return <div className="logo">
|
return <div className="logo" id="logo">
|
||||||
<a href={baseUrl}>
|
<a href={"/"}>
|
||||||
<img alt="logo" height="66%" src="logo.svg" />
|
<img alt="logo" height="66%" src="logo.svg" />
|
||||||
<h3 className="centered">cutt</h3>
|
<h3 className="centered">cutt</h3>
|
||||||
</a>
|
</a>
|
||||||
|
151
src/Network.tsx
Normal file
151
src/Network.tsx
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { apiAuth } from "./api";
|
||||||
|
import { GraphCanvas, GraphCanvasRef, GraphEdge, GraphNode, SelectionProps, SelectionResult, useSelection } from "reagraph";
|
||||||
|
import { customTheme } from "./NetworkTheme";
|
||||||
|
|
||||||
|
interface NetworkData {
|
||||||
|
nodes: GraphNode[],
|
||||||
|
edges: GraphEdge[],
|
||||||
|
}
|
||||||
|
interface CustomSelectionProps extends SelectionProps {
|
||||||
|
ignore: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const useCustomSelection = (props: CustomSelectionProps): SelectionResult => {
|
||||||
|
var result = useSelection(props);
|
||||||
|
result.actives = result.actives.filter((s) => !props.ignore.includes(s))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const GraphComponent = () => {
|
||||||
|
const [data, setData] = useState({ nodes: [], edges: [] } as NetworkData);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [threed, setThreed] = useState(false);
|
||||||
|
const [likes, setLikes] = useState(2);
|
||||||
|
const [popularity, setPopularity] = useState(false);
|
||||||
|
const logo = document.getElementById("logo")
|
||||||
|
if (logo) {
|
||||||
|
logo.className = "logo networkroute";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadData() {
|
||||||
|
setLoading(true);
|
||||||
|
await apiAuth("analysis/graph_json", null)
|
||||||
|
.then(json => json as Promise<NetworkData>).then(json => { setData(json) })
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => { loadData() }, [])
|
||||||
|
|
||||||
|
const graphRef = useRef<GraphCanvasRef | null>(null);
|
||||||
|
|
||||||
|
function handleThreed() {
|
||||||
|
setThreed(!threed)
|
||||||
|
graphRef.current?.fitNodesInView();
|
||||||
|
graphRef.current?.centerGraph();
|
||||||
|
graphRef.current?.resetControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlePopularity() {
|
||||||
|
setPopularity(!popularity)
|
||||||
|
//graphRef.current?.fitNodesInView();
|
||||||
|
//graphRef.current?.centerGraph();
|
||||||
|
//graphRef.current?.resetControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
function showLabel() {
|
||||||
|
switch (likes) {
|
||||||
|
case 0: return "dislike";
|
||||||
|
case 1: return "both";
|
||||||
|
case 2: return "like";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { selections, actives, onNodeClick, onCanvasClick } = useCustomSelection({
|
||||||
|
ref: graphRef,
|
||||||
|
nodes: data.nodes,
|
||||||
|
edges: data.edges.filter((edge) => edge.data.relation === likes),
|
||||||
|
ignore: data.edges.map((edge) => { return (likes === 1 && edge.data.relation !== 2) ? edge.id : "" }),
|
||||||
|
pathSelectionType: 'out',
|
||||||
|
type: 'multiModifier'
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'absolute', top: 0, bottom: 0, left: 0, right: 0 }}>
|
||||||
|
<div className="controls" >
|
||||||
|
|
||||||
|
<div className="control" onClick={handleThreed}>
|
||||||
|
<span>2D</span>
|
||||||
|
<div className="switch">
|
||||||
|
<input type="checkbox" checked={threed} />
|
||||||
|
<span className="slider round"></span>
|
||||||
|
</div>
|
||||||
|
<span>3D</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="control">
|
||||||
|
<div className="stack column">
|
||||||
|
<datalist id="markers">
|
||||||
|
<option value="0"></option>
|
||||||
|
<option value="1"></option>
|
||||||
|
<option value="2"></option>
|
||||||
|
</datalist>
|
||||||
|
<div id="three-slider">
|
||||||
|
<label>😬</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
list="markers"
|
||||||
|
min="0"
|
||||||
|
max="2"
|
||||||
|
step="1"
|
||||||
|
width="16px"
|
||||||
|
onChange={(evt) => setLikes(Number(evt.target.value))}
|
||||||
|
/>
|
||||||
|
<label>😍</label>
|
||||||
|
</div>
|
||||||
|
{showLabel()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="control" onClick={handlePopularity}>
|
||||||
|
<div className="switch">
|
||||||
|
<input type="checkbox" checked={popularity} />
|
||||||
|
<span className="slider round"></span>
|
||||||
|
</div>
|
||||||
|
<span>popularity<sup>*</sup></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{popularity && <div style={{ position: 'absolute', bottom: 0, right: "10px", zIndex: 10 }}>
|
||||||
|
<span className="grey" style={{ fontSize: "70%" }}><sup>*</sup>popularity meassured by rank-weighted in-degree</span>
|
||||||
|
</div>}
|
||||||
|
|
||||||
|
{
|
||||||
|
loading ? <span className="loader" /> :
|
||||||
|
<GraphCanvas
|
||||||
|
draggable
|
||||||
|
cameraMode={threed ? "rotate" : "pan"}
|
||||||
|
layoutType={threed ? "forceDirected3d" : "forceDirected2d"}
|
||||||
|
layoutOverrides={{
|
||||||
|
nodeStrength: -200,
|
||||||
|
linkDistance: 100
|
||||||
|
}}
|
||||||
|
labelType="nodes"
|
||||||
|
sizingType="attribute"
|
||||||
|
sizingAttribute={popularity ? "inDegree" : undefined}
|
||||||
|
ref={graphRef}
|
||||||
|
theme={customTheme}
|
||||||
|
nodes={data.nodes}
|
||||||
|
edges={data.edges.filter((edge) => edge.data.relation === likes || likes === 1)}
|
||||||
|
selections={selections}
|
||||||
|
actives={actives}
|
||||||
|
onCanvasClick={onCanvasClick}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
59
src/NetworkTheme.tsx
Normal file
59
src/NetworkTheme.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { Theme } from "reagraph";
|
||||||
|
|
||||||
|
export const customTheme: Theme = {
|
||||||
|
canvas: {
|
||||||
|
background: 'aliceblue',
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fill: '#69F',
|
||||||
|
activeFill: '#36C',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.333,
|
||||||
|
label: {
|
||||||
|
color: '#404040',
|
||||||
|
stroke: 'white',
|
||||||
|
activeColor: 'black'
|
||||||
|
},
|
||||||
|
subLabel: {
|
||||||
|
color: '#ddd',
|
||||||
|
stroke: 'transparent',
|
||||||
|
activeColor: '#1DE9AC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lasso: {
|
||||||
|
border: '1px solid #55aaff',
|
||||||
|
background: 'rgba(75, 160, 255, 0.1)'
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
fill: '#69F',
|
||||||
|
activeFill: '#36C'
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
fill: '#bed4ff',
|
||||||
|
activeFill: '#36C',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.333,
|
||||||
|
label: {
|
||||||
|
stroke: '#fff',
|
||||||
|
color: '#2A6475',
|
||||||
|
activeColor: '#1DE9AC',
|
||||||
|
fontSize: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
fill: '#bed4ff',
|
||||||
|
activeFill: '#36C'
|
||||||
|
},
|
||||||
|
cluster: {
|
||||||
|
stroke: '#D8E6EA',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.1,
|
||||||
|
label: {
|
||||||
|
stroke: '#fff',
|
||||||
|
color: '#2A6475'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
11
src/api.ts
11
src/api.ts
@ -20,13 +20,16 @@ export default async function api(path: string, data: any): Promise<any> {
|
|||||||
|
|
||||||
export async function apiAuth(path: string, data: any, method: string = "GET"): Promise<any> {
|
export async function apiAuth(path: string, data: any, method: string = "GET"): Promise<any> {
|
||||||
|
|
||||||
const req = new Request(`${baseUrl}api/${path}`, {
|
const req = new Request(`${baseUrl}api/${path}`,
|
||||||
method: method, headers: {
|
{
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
"Authorization": `Bearer ${token()} `,
|
"Authorization": `Bearer ${token()} `,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
...(data && { body: JSON.stringify(data) })
|
||||||
});
|
}
|
||||||
|
);
|
||||||
let resp: Response;
|
let resp: Response;
|
||||||
try {
|
try {
|
||||||
resp = await fetch(req);
|
resp = await fetch(req);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user