-
-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IPFS nodes healthcheck and API client #618
Changes from all commits
871f9a6
a2af67f
5ee89c7
748a6ef
33ac5fc
568a281
602c9f0
b6c6b8f
c84f8c0
6492414
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<template> | ||
<NodesTableContainer> | ||
<NodesTableHead hide-label /> | ||
|
||
<tbody> | ||
<IpfsNodesTableItem v-for="node in ipfsNodes" :key="node.url" blockchain="adm" :node="node" /> | ||
</tbody> | ||
</NodesTableContainer> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { computed, defineComponent } from 'vue' | ||
import { useStore } from 'vuex' | ||
import NodesTableContainer from '@/components/nodes/components/NodesTableContainer.vue' | ||
import NodesTableHead from '@/components/nodes/components/NodesTableHead.vue' | ||
import IpfsNodesTableItem from './IpfsNodesTableItem.vue' | ||
import { sortNodesFn } from '@/components/nodes/utils/sortNodesFn' | ||
|
||
const className = 'adm-nodes-table' | ||
const classes = { | ||
root: className | ||
} | ||
|
||
export default defineComponent({ | ||
components: { | ||
NodesTableContainer, | ||
NodesTableHead, | ||
IpfsNodesTableItem | ||
}, | ||
setup() { | ||
const store = useStore() | ||
const ipfsNodes = computed(() => { | ||
const arr = store.getters['nodes/ipfs'] | ||
|
||
return [...arr].sort(sortNodesFn) | ||
}) | ||
|
||
return { | ||
ipfsNodes, | ||
classes | ||
} | ||
} | ||
}) | ||
</script> | ||
|
||
<style lang="scss"> | ||
@import 'vuetify/settings'; | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
<template> | ||
<tr :class="classes.root"> | ||
<NodeColumn checkbox> | ||
<NodeStatusCheckbox :value="active" @change="toggleActiveStatus" /> | ||
</NodeColumn> | ||
|
||
<NodeColumn> | ||
<NodeUrl :node="node" /> | ||
<NodeVersion v-if="node.version && active" :node="node" /> | ||
</NodeColumn> | ||
|
||
<NodeColumn :colspan="!showSocketColumn ? 2 : 1"> | ||
<NodeStatus :node="node" /> | ||
</NodeColumn> | ||
|
||
<NodeColumn v-if="showSocketColumn"> | ||
<SocketSupport :node="node" /> | ||
</NodeColumn> | ||
</tr> | ||
</template> | ||
|
||
<script lang="ts"> | ||
import { computed, PropType } from 'vue' | ||
import { useStore } from 'vuex' | ||
import type { NodeStatusResult } from '@/lib/nodes/abstract.node' | ||
import NodeUrl from '@/components/nodes/components/NodeUrl.vue' | ||
import NodeColumn from '@/components/nodes/components/NodeColumn.vue' | ||
import NodeStatus from '@/components/nodes/components/NodeStatus.vue' | ||
import NodeVersion from '@/components/nodes/components/NodeVersion.vue' | ||
import SocketSupport from '@/components/nodes/components/SocketSupport.vue' | ||
import NodeStatusCheckbox from '@/components/nodes/components/NodeStatusCheckbox.vue' | ||
|
||
const className = 'adm-nodes-table-item' | ||
const classes = { | ||
root: className, | ||
column: `${className}__column`, | ||
columnCheckbox: `${className}__column--checkbox`, | ||
checkbox: `${className}__checkbox` | ||
} | ||
|
||
export default { | ||
components: { | ||
NodeStatusCheckbox, | ||
NodeColumn, | ||
NodeStatus, | ||
NodeVersion, | ||
SocketSupport, | ||
NodeUrl | ||
}, | ||
props: { | ||
node: { | ||
type: Object as PropType<NodeStatusResult>, | ||
required: true | ||
} | ||
}, | ||
setup(props) { | ||
const store = useStore() | ||
|
||
const url = computed(() => props.node.url) | ||
const active = computed(() => props.node.active) | ||
const socketSupport = computed(() => props.node.socketSupport) | ||
const isUnsupported = computed(() => props.node.status === 'unsupported_version') | ||
const type = computed(() => props.node.type) | ||
const showSocketColumn = computed(() => active.value && !isUnsupported.value) | ||
|
||
const toggleActiveStatus = () => { | ||
store.dispatch('nodes/toggle', { | ||
type: type.value, | ||
url: url.value, | ||
active: !active.value | ||
}) | ||
store.dispatch('nodes/updateStatus') | ||
} | ||
|
||
const computedResult = computed(() => { | ||
const baseUrl = new URL(url.value) | ||
const protocol = baseUrl.protocol | ||
const hostname = baseUrl.hostname | ||
const port = baseUrl.port | ||
const result = /^[\d.]+$/.test(hostname) | ||
|
||
let nodeName = null | ||
let domain = null | ||
|
||
if (!result) { | ||
const regex = /([^.]*)\.(.*)/ | ||
const parts = hostname.match(regex) | ||
if (parts !== null) { | ||
nodeName = parts[1] | ||
domain = parts[2] | ||
} | ||
} | ||
|
||
return { | ||
protocol, | ||
hostname, | ||
nodeName, | ||
domain, | ||
result, | ||
port | ||
} | ||
}) | ||
Comment on lines
+75
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you convert this part into a hook and reuse it in both components: Example of usage: const { protocol, hostname, nodeName, domain, result, port } = useNodeUrl(url); Put the hook in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's important not to lose the reactivity of |
||
|
||
return { | ||
classes, | ||
url, | ||
active, | ||
socketSupport, | ||
isUnsupported, | ||
showSocketColumn, | ||
toggleActiveStatus, | ||
computedResult | ||
} | ||
} | ||
} | ||
</script> | ||
|
||
<style lang="scss"> | ||
.ipfs-nodes-table-item { | ||
line-height: 14px; | ||
} | ||
</style> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default as IpfsNodesTable } from './IpfsNodesTable.vue' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { isNodeOfflineError } from '@/lib/nodes/utils/errors' | ||
import { IpfsNode, Payload, RequestConfig } from './IpfsNode.ts' | ||
import { Client } from '../abstract.client' | ||
|
||
/** | ||
* Provides methods for calling the ADAMANT API. | ||
* | ||
* The `ApiClient` instance automatically selects an ADAMANT node to | ||
* send the API-requests to and switches to another node if the current one | ||
* is not available at the moment. | ||
*/ | ||
Comment on lines
+5
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update JSDoc: |
||
export class IpfsClient extends Client<IpfsNode> { | ||
constructor(endpoints: string[] = [], minNodeVersion = '0.0.0') { | ||
super('ipfs') | ||
this.nodes = endpoints.map((endpoint) => new IpfsNode(endpoint, minNodeVersion)) | ||
this.minNodeVersion = minNodeVersion | ||
|
||
void this.watchNodeStatusChange() | ||
} | ||
|
||
/** | ||
* Performs a GET API request. | ||
* @param {String} url relative API url | ||
* @param {any} params request params (an object) or a function that accepts `ApiNode` and returns the request params | ||
*/ | ||
get<P extends Payload = Payload>(url: string, params: P) { | ||
return this.request({ method: 'get', url, payload: params }) | ||
} | ||
|
||
/** | ||
* Performs a POST API request. | ||
* @param {String} url relative API url | ||
* @param {any} payload request payload (an object) or a function that accepts `ApiNode` and returns the request payload | ||
*/ | ||
post<P extends Payload = Payload>(url: string, payload: P) { | ||
return this.request({ method: 'post', url, payload }) | ||
} | ||
|
||
/** | ||
* Performs an API request. | ||
* @param {RequestConfig} config request config | ||
*/ | ||
async request<P extends Payload = Payload, R = any>(config: RequestConfig<P>): Promise<R> { | ||
const node = this.useFastest ? this.getFastestNode() : this.getRandomNode() | ||
if (!node) { | ||
// All nodes seem to be offline: let's refresh the statuses | ||
this.checkHealth() | ||
// But there's nothing we can do right now | ||
return Promise.reject(new Error('No online nodes at the moment')) | ||
} | ||
|
||
return node.request(config).catch((error) => { | ||
if (isNodeOfflineError(error)) { | ||
// Initiate nodes status check | ||
this.checkHealth() | ||
// If the selected node is not available, repeat the request with another one. | ||
return this.request(config) | ||
} | ||
throw error | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also add this selector in <style> block. Leave empty if no styles