add sidebar with instance details
This commit is contained in:
parent
93932c5196
commit
ef18276c21
|
@ -19,11 +19,27 @@ class InstanceListSerializer(serializers.ModelSerializer):
|
|||
|
||||
|
||||
class InstanceDetailSerializer(serializers.ModelSerializer):
|
||||
userCount = serializers.SerializerMethodField()
|
||||
statusCount = serializers.SerializerMethodField()
|
||||
domainCount = serializers.SerializerMethodField()
|
||||
lastUpdated = serializers.SerializerMethodField()
|
||||
peers = InstanceListSerializer(many=True, read_only=True)
|
||||
|
||||
def get_userCount(self, obj):
|
||||
return obj.user_count
|
||||
|
||||
def get_statusCount(self, obj):
|
||||
return obj.status_count
|
||||
|
||||
def get_domainCount(self, obj):
|
||||
return obj.domain_count
|
||||
|
||||
def get_lastUpdated(self, obj):
|
||||
return obj.last_updated
|
||||
|
||||
class Meta:
|
||||
model = Instance
|
||||
fields = '__all__'
|
||||
fields = ('name', 'description', 'version', 'userCount', 'statusCount', 'domainCount', 'peers', 'lastUpdated')
|
||||
|
||||
|
||||
class EdgeSerializer(serializers.ModelSerializer):
|
||||
|
|
|
@ -16,7 +16,8 @@
|
|||
"react-sigma": "^1.2.30",
|
||||
"react-virtualized": "^9.20.1",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.3.0"
|
||||
"redux-thunk": "^2.3.0",
|
||||
"sanitize-html": "^1.18.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts-ts start",
|
||||
|
@ -32,6 +33,7 @@
|
|||
"@types/react-dom": "^16.0.7",
|
||||
"@types/react-redux": "^6.0.6",
|
||||
"@types/react-virtualized": "^9.18.7",
|
||||
"@types/sanitize-html": "^1.18.0",
|
||||
"typescript": "^3.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@ import { IconNames } from '@blueprintjs/icons';
|
|||
|
||||
import { Graph } from './components/Graph';
|
||||
import { Nav } from './components/Nav';
|
||||
import { Sidebar } from './components/Sidebar';
|
||||
import { fetchGraph, fetchInstances } from './redux/actions';
|
||||
import { IAppState, IGraph, IInstance } from './redux/types';
|
||||
|
||||
interface IAppProps {
|
||||
currentInstanceName?: string | null;
|
||||
graph?: IGraph;
|
||||
instances?: IInstance[],
|
||||
isLoadingGraph: boolean;
|
||||
|
@ -27,7 +27,7 @@ class AppImpl extends React.Component<IAppProps> {
|
|||
} else if (this.props.isLoadingGraph) {
|
||||
body = this.loadingState("Loading graph...");
|
||||
} else if (!!this.props.graph) {
|
||||
body = <Graph />;
|
||||
body = this.graphState();
|
||||
}
|
||||
return (
|
||||
<div className="App bp3-dark">
|
||||
|
@ -41,6 +41,15 @@ class AppImpl extends React.Component<IAppProps> {
|
|||
this.props.fetchInstances();
|
||||
}
|
||||
|
||||
private graphState = () => {
|
||||
return (
|
||||
<div>
|
||||
<Sidebar />
|
||||
<Graph />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private welcomeState = () => {
|
||||
const numInstances = this.props.instances ? this.props.instances.length : "lots of";
|
||||
const description = `There are ${numInstances} known instances, so loading the graph might take a little while. Ready?`
|
||||
|
@ -67,7 +76,6 @@ class AppImpl extends React.Component<IAppProps> {
|
|||
}
|
||||
|
||||
const mapStateToProps = (state: IAppState) => ({
|
||||
currentInstanceName: state.currentInstanceName,
|
||||
graph: state.data.graph,
|
||||
instances: state.data.instances,
|
||||
isLoadingGraph: state.data.isLoadingGraph,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { NodeShapes, RandomizeNodePositions, RelativeSize, Sigma, SigmaEnableWebGL, LoadGEXF, Filter } from 'react-sigma';
|
||||
import { RandomizeNodePositions, RelativeSize, Sigma, SigmaEnableWebGL, Filter } from 'react-sigma';
|
||||
|
||||
import { selectInstance } from '../redux/actions';
|
||||
import { selectAndLoadInstance } from '../redux/actions';
|
||||
|
||||
const STYLE = {
|
||||
bottom: "0",
|
||||
|
@ -14,6 +14,7 @@ const STYLE = {
|
|||
const SETTINGS = {
|
||||
defaultEdgeColor: "#5C7080",
|
||||
defaultNodeColor: "#CED9E0",
|
||||
defaultNodeHoverColor: "#48AFF0",
|
||||
drawEdges: true,
|
||||
drawLabels: true,
|
||||
edgeColor: "default",
|
||||
|
@ -31,8 +32,8 @@ class GraphImpl extends React.Component {
|
|||
renderer="webgl"
|
||||
settings={SETTINGS}
|
||||
style={STYLE}
|
||||
onClickNode={(e) => this.props.selectInstance(e.data.node.label)}
|
||||
onClickStage={(e) => this.props.selectInstance(null)}
|
||||
onClickNode={(e) => this.props.selectAndLoadInstance(e.data.node.label)}
|
||||
onClickStage={(e) => this.props.selectAndLoadInstance(null)}
|
||||
>
|
||||
<RandomizeNodePositions />
|
||||
<Filter neighborsOf={this.props.currentInstanceName} />
|
||||
|
@ -40,21 +41,13 @@ class GraphImpl extends React.Component {
|
|||
</Sigma>
|
||||
)
|
||||
}
|
||||
|
||||
// onClickNode = (e) => {
|
||||
// this.props.selectInstance(e.data.node.label);
|
||||
// }
|
||||
|
||||
// zoomToNode = (camera, node) => {
|
||||
// s
|
||||
// }
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
currentInstanceName: state.currentInstanceName,
|
||||
currentInstanceName: state.currentInstance.currentInstanceName,
|
||||
graph: state.data.graph,
|
||||
})
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
selectInstance: (instanceName) => dispatch(selectInstance(instanceName)),
|
||||
selectAndLoadInstance: (instanceName) => dispatch(selectAndLoadInstance(instanceName)),
|
||||
})
|
||||
export const Graph = connect(mapStateToProps, mapDispatchToProps)(GraphImpl)
|
||||
|
|
|
@ -6,13 +6,13 @@ import { Button, MenuItem } from '@blueprintjs/core';
|
|||
import { IconNames } from '@blueprintjs/icons';
|
||||
import { IItemRendererProps, ItemPredicate, Select } from '@blueprintjs/select';
|
||||
|
||||
import { selectInstance } from '../redux/actions';
|
||||
import { selectAndLoadInstance } from '../redux/actions';
|
||||
import { IAppState, IInstance } from '../redux/types';
|
||||
|
||||
interface IInstanceSearchProps {
|
||||
currentInstanceName: string | null;
|
||||
instances?: IInstance[];
|
||||
selectInstance: (instanceName: string) => void;
|
||||
selectAndLoadInstance: (instanceName: string) => void;
|
||||
}
|
||||
|
||||
const InstanceSelect = Select.ofType<IInstance>();
|
||||
|
@ -75,15 +75,15 @@ class InstanceSearchImpl extends React.Component<IInstanceSearchProps> {
|
|||
}
|
||||
|
||||
private onItemSelect = (item: IInstance, event?: React.SyntheticEvent<HTMLElement>) => {
|
||||
this.props.selectInstance(item.name);
|
||||
this.props.selectAndLoadInstance(item.name);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IAppState) => ({
|
||||
currentInstanceName: state.currentInstanceName,
|
||||
currentInstanceName: state.currentInstance.currentInstanceName,
|
||||
instances: state.data.instances,
|
||||
})
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
selectInstance: (instanceName: string) => dispatch(selectInstance(instanceName)),
|
||||
selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any),
|
||||
})
|
||||
export const InstanceSearch = connect(mapStateToProps, mapDispatchToProps)(InstanceSearchImpl)
|
||||
|
|
|
@ -1,20 +1,11 @@
|
|||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { Alignment, Button, Icon, Navbar } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
|
||||
import { selectInstance } from '../redux/actions';
|
||||
import { IAppState, IInstance } from '../redux/types';
|
||||
import { InstanceSearch } from './InstanceSearch';
|
||||
|
||||
interface INavProps {
|
||||
instances?: IInstance[];
|
||||
selectInstance: (instance: string) => void;
|
||||
}
|
||||
|
||||
class NavImpl extends React.Component<INavProps> {
|
||||
export class Nav extends React.Component {
|
||||
public render() {
|
||||
return (
|
||||
<Navbar>
|
||||
|
@ -38,11 +29,3 @@ class NavImpl extends React.Component<INavProps> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IAppState) => ({
|
||||
instances: state.data.instances,
|
||||
})
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
selectInstance: (instanceName: string) => dispatch(selectInstance(instanceName)),
|
||||
})
|
||||
export const Nav = connect(mapStateToProps, mapDispatchToProps)(NavImpl)
|
176
frontend/src/components/Sidebar.tsx
Normal file
176
frontend/src/components/Sidebar.tsx
Normal file
|
@ -0,0 +1,176 @@
|
|||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import * as sanitize from 'sanitize-html';
|
||||
|
||||
import { Card, Classes, Divider, Elevation, HTMLTable, NonIdealState } from '@blueprintjs/core';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
|
||||
import { selectAndLoadInstance } from '../redux/actions';
|
||||
import { IAppState, IInstanceDetails } from '../redux/types';
|
||||
|
||||
interface ISidebarProps {
|
||||
instanceName: string | null,
|
||||
instanceDetails: IInstanceDetails | null,
|
||||
isLoadingInstanceDetails: boolean;
|
||||
selectAndLoadInstance: (instanceName: string) => void;
|
||||
}
|
||||
class SidebarImpl extends React.Component<ISidebarProps> {
|
||||
public render() {
|
||||
return (
|
||||
<Card className="fediverse-sidebar" elevation={Elevation.TWO}>
|
||||
{this.renderSidebarContents()}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
private renderSidebarContents = () => {
|
||||
if (this.props.isLoadingInstanceDetails) {
|
||||
return this.renderLoadingState();
|
||||
} else if (!this.props.instanceDetails) {
|
||||
return this.renderEmptyState();
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>{this.props.instanceName || "No instance selected"}</h2>
|
||||
<Divider />
|
||||
{this.renderDescription()}
|
||||
{this.renderVersion()}
|
||||
{this.renderCounts()}
|
||||
{this.renderPeers()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderDescription = () => {
|
||||
const description = this.props.instanceDetails!.description;
|
||||
if (!description) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h4>Description</h4>
|
||||
<div className={Classes.RUNNING_TEXT} dangerouslySetInnerHTML={{__html: sanitize(description)}} />
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderVersion = () => {
|
||||
const version = this.props.instanceDetails!.version;
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h4>Version</h4>
|
||||
<code className={Classes.CODE}>{version}</code>
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderCounts = () => {
|
||||
const userCount = this.props.instanceDetails!.userCount;
|
||||
const statusCount = this.props.instanceDetails!.statusCount;
|
||||
const domainCount = this.props.instanceDetails!.domainCount;
|
||||
if (!userCount && !statusCount && !domainCount) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h4>Stats</h4>
|
||||
<HTMLTable small={true} striped={true} className="fediverse-sidebar-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Users</td>
|
||||
<td>{userCount || "Unknown"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Statuses</td>
|
||||
<td>{statusCount || "Unknown"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Known peers</td>
|
||||
<td>{domainCount || "Unknown"}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</HTMLTable>
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderPeers = () => {
|
||||
const peers = this.props.instanceDetails!.peers;
|
||||
if (!peers) {
|
||||
return;
|
||||
}
|
||||
const peerRows = peers.map(instance => (
|
||||
<tr key={instance.name} onClick={this.selectInstance}>
|
||||
<td>{instance.name}</td>
|
||||
</tr>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<h4>Known instances</h4>
|
||||
<HTMLTable small={true} striped={true} interactive={true} className="fediverse-sidebar-table">
|
||||
<tbody>
|
||||
{peerRows}
|
||||
</tbody>
|
||||
</HTMLTable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private renderEmptyState = () => {
|
||||
return (
|
||||
<NonIdealState
|
||||
icon={IconNames.CIRCLE}
|
||||
title="No instance selected"
|
||||
description="Select an instance from the graph or the top-right dropdown to see its details."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private renderLoadingState = () => {
|
||||
return (
|
||||
<div>
|
||||
<h4><span className={Classes.SKELETON}>Description</span></h4>
|
||||
<p className={Classes.SKELETON}>
|
||||
Eaque rerum sequi unde omnis voluptatibus non quia fugit. Dignissimos asperiores aut incidunt.
|
||||
Cupiditate sit voluptates quia nulla et saepe id suscipit.
|
||||
Voluptas sed rerum placeat consectetur pariatur necessitatibus tempora.
|
||||
Eaque rerum sequi unde omnis voluptatibus non quia fugit. Dignissimos asperiores aut incidunt.
|
||||
Cupiditate sit voluptates quia nulla et saepe id suscipit.
|
||||
Voluptas sed rerum placeat consectetur pariatur necessitatibus tempora.
|
||||
</p>
|
||||
<h4><span className={Classes.SKELETON}>Version</span></h4>
|
||||
<p className={Classes.SKELETON}>
|
||||
Eaque rerum sequi unde omnis voluptatibus non quia fugit.
|
||||
</p>
|
||||
<h4><span className={Classes.SKELETON}>Stats</span></h4>
|
||||
<p className={Classes.SKELETON}>
|
||||
Eaque rerum sequi unde omnis voluptatibus non quia fugit. Dignissimos asperiores aut incidunt.
|
||||
Cupiditate sit voluptates quia nulla et saepe id suscipit.
|
||||
Eaque rerum sequi unde omnis voluptatibus non quia fugit. Dignissimos asperiores aut incidunt.
|
||||
Cupiditate sit voluptates quia nulla et saepe id suscipit.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private selectInstance = (e: any)=> {
|
||||
this.props.selectAndLoadInstance(e.target.innerText);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IAppState) => ({
|
||||
instanceDetails: state.currentInstance.currentInstanceDetails,
|
||||
instanceName: state.currentInstance.currentInstanceName,
|
||||
isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
selectAndLoadInstance: (instanceName: string) => dispatch(selectAndLoadInstance(instanceName) as any),
|
||||
});
|
||||
export const Sidebar = connect(mapStateToProps, mapDispatchToProps)(SidebarImpl);
|
|
@ -16,3 +16,19 @@ html, body {
|
|||
min-width: 300px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.fediverse-sidebar {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
min-width: 300px;
|
||||
width: 25%;
|
||||
z-index: 20;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.fediverse-sidebar-table {
|
||||
width: 100%;
|
||||
}
|
|
@ -1,14 +1,20 @@
|
|||
import { Dispatch } from 'redux';
|
||||
|
||||
import { getFromApi } from '../util';
|
||||
import { ActionType, IGraph, IInstance } from './types';
|
||||
import { ActionType, IGraph, IInstance, IInstanceDetails } from './types';
|
||||
|
||||
export const selectInstance = (instanceName: string) => {
|
||||
// selectInstance and deselectInstance are not exported since we only call them from selectAndLoadInstance()
|
||||
const selectInstance = (instanceName: string) => {
|
||||
return {
|
||||
payload: instanceName,
|
||||
type: ActionType.SELECT_INSTANCE,
|
||||
}
|
||||
}
|
||||
const deselectInstance = () => {
|
||||
return {
|
||||
type: ActionType.DESELECT_INSTANCE,
|
||||
}
|
||||
}
|
||||
|
||||
export const requestInstances = () => {
|
||||
return {
|
||||
|
@ -22,7 +28,6 @@ export const receiveInstances = (instances: IInstance[]) => {
|
|||
type: ActionType.RECEIVE_INSTANCES,
|
||||
}
|
||||
}
|
||||
|
||||
export const requestGraph = () => {
|
||||
return {
|
||||
type: ActionType.REQUEST_GRAPH,
|
||||
|
@ -36,6 +41,14 @@ export const receiveGraph = (graph: IGraph) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const receiveInstanceDetails = (instanceDetails: IInstanceDetails) => {
|
||||
return {
|
||||
payload: instanceDetails,
|
||||
type: ActionType.RECEIVE_INSTANCE_DETAILS,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Async actions: https://redux.js.org/advanced/asyncactions */
|
||||
|
||||
export const fetchInstances = () => {
|
||||
|
@ -43,8 +56,20 @@ export const fetchInstances = () => {
|
|||
return (dispatch: Dispatch) => {
|
||||
dispatch(requestInstances());
|
||||
return getFromApi("instances")
|
||||
.then(instances => dispatch(receiveInstances(instances))
|
||||
);
|
||||
.then(instances => dispatch(receiveInstances(instances)));
|
||||
}
|
||||
}
|
||||
|
||||
export const selectAndLoadInstance = (instanceName: string) => {
|
||||
// TODO: handle errors
|
||||
return (dispatch: Dispatch) => {
|
||||
if (!instanceName) {
|
||||
dispatch(deselectInstance());
|
||||
return;
|
||||
}
|
||||
dispatch(selectInstance(instanceName));
|
||||
return getFromApi("instances/" + instanceName)
|
||||
.then(details => dispatch(receiveInstanceDetails(details)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +84,6 @@ export const fetchGraph = () => {
|
|||
nodes: responses[1],
|
||||
};
|
||||
})
|
||||
.then(graph => dispatch(receiveGraph(graph)))
|
||||
.then(graph => dispatch(receiveGraph(graph)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { combineReducers } from 'redux';
|
||||
|
||||
import { ActionType, IAction, IDataState } from './types';
|
||||
import { ActionType, IAction, ICurrentInstanceState, IDataState } from './types';
|
||||
|
||||
const initialDataState = {
|
||||
isLoadingGraph: false,
|
||||
|
@ -36,16 +36,37 @@ const data = (state: IDataState = initialDataState, action: IAction) => {
|
|||
}
|
||||
}
|
||||
|
||||
const currentInstanceName = (state: string | null = null, action: IAction): string | null => {
|
||||
const initialCurrentInstanceState = {
|
||||
currentInstanceDetails: null,
|
||||
currentInstanceName: null,
|
||||
isLoadingInstanceDetails: false
|
||||
};
|
||||
const currentInstance = (state = initialCurrentInstanceState , action: IAction): ICurrentInstanceState => {
|
||||
switch (action.type) {
|
||||
case ActionType.SELECT_INSTANCE:
|
||||
return action.payload;
|
||||
return {
|
||||
...state,
|
||||
currentInstanceName: action.payload,
|
||||
isLoadingInstanceDetails: true,
|
||||
};
|
||||
case ActionType.RECEIVE_INSTANCE_DETAILS:
|
||||
return {
|
||||
...state,
|
||||
currentInstanceDetails: action.payload,
|
||||
isLoadingInstanceDetails: false,
|
||||
}
|
||||
case ActionType.DESELECT_INSTANCE:
|
||||
return {
|
||||
...state,
|
||||
currentInstanceDetails: null,
|
||||
currentInstanceName: null,
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const rootReducer = combineReducers({
|
||||
currentInstanceName,
|
||||
currentInstance,
|
||||
data,
|
||||
})
|
|
@ -4,6 +4,8 @@ export enum ActionType {
|
|||
RECEIVE_INSTANCES = 'RECEIVE_INSTANCES',
|
||||
REQUEST_GRAPH = 'REQUEST_GRAPH',
|
||||
RECEIVE_GRAPH = 'RECEIVE_GRAPH',
|
||||
RECEIVE_INSTANCE_DETAILS = 'RECEIVE_INSTANCE_DETAILS',
|
||||
DESELECT_INSTANCE = "DESELECT_INSTANCE",
|
||||
}
|
||||
|
||||
export interface IAction {
|
||||
|
@ -16,6 +18,17 @@ export interface IInstance {
|
|||
numUsers?: number,
|
||||
}
|
||||
|
||||
export interface IInstanceDetails {
|
||||
name: string;
|
||||
peers?: IInstance[];
|
||||
description?: string;
|
||||
domainCount?: number;
|
||||
statusCount?: number;
|
||||
userCount?: number;
|
||||
version?: string;
|
||||
lastUpdated?: string;
|
||||
}
|
||||
|
||||
interface IGraphNode {
|
||||
id: string;
|
||||
label: string;
|
||||
|
@ -36,6 +49,12 @@ export interface IGraph {
|
|||
|
||||
// Redux state
|
||||
|
||||
export interface ICurrentInstanceState {
|
||||
currentInstanceDetails: IInstanceDetails | null,
|
||||
currentInstanceName: string | null,
|
||||
isLoadingInstanceDetails: boolean,
|
||||
}
|
||||
|
||||
export interface IDataState {
|
||||
instances?: IInstance[],
|
||||
graph?: IGraph,
|
||||
|
@ -44,6 +63,6 @@ export interface IDataState {
|
|||
}
|
||||
|
||||
export interface IAppState {
|
||||
currentInstanceName: string | null,
|
||||
currentInstance: ICurrentInstanceState;
|
||||
data: IDataState,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,10 @@
|
|||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/sanitize-html@^1.18.0":
|
||||
version "1.18.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.18.0.tgz#de5cb560a41308ea8474e93b9d10bbb4050692f5"
|
||||
|
||||
abab@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
|
||||
|
@ -309,7 +313,7 @@ array-union@^1.0.1:
|
|||
dependencies:
|
||||
array-uniq "^1.0.1"
|
||||
|
||||
array-uniq@^1.0.1:
|
||||
array-uniq@^1.0.1, array-uniq@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
|
||||
|
@ -2175,7 +2179,7 @@ domain-browser@^1.1.1:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda"
|
||||
|
||||
domelementtype@1:
|
||||
domelementtype@1, domelementtype@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2"
|
||||
|
||||
|
@ -2195,6 +2199,12 @@ domhandler@2.1:
|
|||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domhandler@^2.3.0:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803"
|
||||
dependencies:
|
||||
domelementtype "1"
|
||||
|
||||
domutils@1.1:
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485"
|
||||
|
@ -2208,6 +2218,13 @@ domutils@1.5.1:
|
|||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
domutils@^1.5.1:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"
|
||||
dependencies:
|
||||
dom-serializer "0"
|
||||
domelementtype "1"
|
||||
|
||||
dot-prop@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57"
|
||||
|
@ -2295,7 +2312,7 @@ enhanced-resolve@^3.0.0, enhanced-resolve@^3.4.0:
|
|||
object-assign "^4.0.1"
|
||||
tapable "^0.2.7"
|
||||
|
||||
entities@~1.1.1:
|
||||
entities@^1.1.1, entities@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0"
|
||||
|
||||
|
@ -3221,6 +3238,17 @@ html-webpack-plugin@2.29.0:
|
|||
pretty-error "^2.0.2"
|
||||
toposort "^1.0.0"
|
||||
|
||||
htmlparser2@^3.9.0:
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
||||
dependencies:
|
||||
domelementtype "^1.3.0"
|
||||
domhandler "^2.3.0"
|
||||
domutils "^1.5.1"
|
||||
entities "^1.1.1"
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.2"
|
||||
|
||||
htmlparser2@~3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe"
|
||||
|
@ -4318,6 +4346,10 @@ lodash.camelcase@^4.3.0:
|
|||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
||||
lodash.debounce@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||
|
@ -4330,10 +4362,18 @@ lodash.endswith@^4.2.1:
|
|||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.endswith/-/lodash.endswith-4.2.1.tgz#fed59ac1738ed3e236edd7064ec456448b37bc09"
|
||||
|
||||
lodash.escaperegexp@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
|
||||
lodash.isfunction@^3.0.8:
|
||||
version "3.0.9"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
|
||||
|
||||
lodash.isplainobject@^4.0.6:
|
||||
version "4.0.6"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
|
||||
|
||||
lodash.isstring@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
|
||||
|
@ -4342,6 +4382,10 @@ lodash.memoize@^4.1.2:
|
|||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
||||
lodash.mergewith@^4.6.0:
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
|
||||
|
||||
lodash.sortby@^4.7.0:
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
|
@ -5494,7 +5538,7 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
|
|||
source-map "^0.5.6"
|
||||
supports-color "^3.2.3"
|
||||
|
||||
postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.13:
|
||||
postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.13, postcss@^6.0.14:
|
||||
version "6.0.23"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
|
||||
dependencies:
|
||||
|
@ -6231,6 +6275,21 @@ sane@^2.0.0:
|
|||
optionalDependencies:
|
||||
fsevents "^1.2.3"
|
||||
|
||||
sanitize-html@^1.18.4:
|
||||
version "1.18.4"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.18.4.tgz#ffdeea13b555dd5e872e9a68b79e5e716cd8c543"
|
||||
dependencies:
|
||||
chalk "^2.3.0"
|
||||
htmlparser2 "^3.9.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.escaperegexp "^4.1.2"
|
||||
lodash.isplainobject "^4.0.6"
|
||||
lodash.isstring "^4.0.1"
|
||||
lodash.mergewith "^4.6.0"
|
||||
postcss "^6.0.14"
|
||||
srcset "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
sax@^1.2.4, sax@~1.2.1:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
|
@ -6560,6 +6619,13 @@ sprintf-js@~1.0.2:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
|
||||
srcset@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
||||
dependencies:
|
||||
array-uniq "^1.0.2"
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.14.2"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Django 2.1 on 2018-08-30 19:57
|
||||
# Generated by Django 2.1 on 2018-09-01 14:00
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
@ -37,7 +37,7 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
migrations.AddField(
|
||||
model_name='instance',
|
||||
name='following',
|
||||
field=models.ManyToManyField(related_name='followers', through='scraper.PeerRelationship', to='scraper.Instance'),
|
||||
name='peers',
|
||||
field=models.ManyToManyField(through='scraper.PeerRelationship', to='scraper.Instance'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -20,7 +20,7 @@ class Instance(models.Model):
|
|||
status = models.CharField(max_length=100)
|
||||
|
||||
# Foreign keys
|
||||
following = models.ManyToManyField('self', symmetrical=False, through='PeerRelationship', related_name="followers")
|
||||
peers = models.ManyToManyField('self', symmetrical=False, through='PeerRelationship')
|
||||
|
||||
# Automatic fields
|
||||
first_seen = models.DateTimeField(auto_now_add=True)
|
||||
|
|
Loading…
Reference in a new issue