Skip to content

Commit e45fac2

Browse files
committed
webui: Content page
1 parent 09959f3 commit e45fac2

File tree

4 files changed

+212
-0
lines changed

4 files changed

+212
-0
lines changed

web/api/webrpc/content.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package webrpc
2+
3+
import (
4+
"context"
5+
6+
"github.com/ipfs/go-cid"
7+
"golang.org/x/xerrors"
8+
)
9+
10+
// ContentInfo represents information about content location
11+
type ContentInfo struct {
12+
PieceCID string `json:"piece_cid"`
13+
Offset uint64 `json:"offset"`
14+
Size uint64 `json:"size"`
15+
16+
Err string `json:"err"`
17+
}
18+
19+
// FindContentByCID finds content by CID
20+
func (a *WebRPC) FindContentByCID(ctx context.Context, cs string) ([]ContentInfo, error) {
21+
cid, err := cid.Parse(cs)
22+
if err != nil {
23+
return nil, err
24+
}
25+
26+
mh := cid.Hash()
27+
28+
offsets, err := a.deps.IndexStore.PiecesContainingMultihash(ctx, mh)
29+
if err != nil {
30+
return nil, xerrors.Errorf("pieces containing multihash %s: %w", mh, err)
31+
}
32+
33+
var res []ContentInfo
34+
for _, offset := range offsets {
35+
off, err := a.deps.IndexStore.GetOffset(ctx, offset.PieceCid, mh)
36+
if err != nil {
37+
res = append(res, ContentInfo{
38+
PieceCID: offset.PieceCid.String(),
39+
Offset: off,
40+
Size: uint64(offset.BlockSize),
41+
Err: err.Error(),
42+
})
43+
continue
44+
}
45+
res = append(res, ContentInfo{
46+
PieceCID: offset.PieceCid.String(),
47+
Offset: off,
48+
Size: uint64(offset.BlockSize),
49+
})
50+
}
51+
52+
return res, nil
53+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
2+
import RPCCall from '/lib/jsonrpc.mjs';
3+
4+
class ContentPage extends LitElement {
5+
static properties = {
6+
searchCid: { type: String },
7+
results: { type: Array },
8+
loading: { type: Boolean },
9+
error: { type: String }
10+
};
11+
12+
constructor() {
13+
super();
14+
this.searchCid = '';
15+
this.results = [];
16+
this.loading = false;
17+
this.error = '';
18+
}
19+
20+
handleInput(e) {
21+
this.searchCid = e.target.value;
22+
}
23+
24+
async handleFind() {
25+
if (!this.searchCid.trim()) {
26+
this.error = 'Please enter a CID';
27+
return;
28+
}
29+
30+
this.loading = true;
31+
this.error = '';
32+
this.results = [];
33+
34+
try {
35+
const results = await RPCCall('FindContentByCID', [this.searchCid.trim()]);
36+
this.results = results || [];
37+
if (this.results.length === 0) {
38+
this.error = 'No content found for this CID';
39+
}
40+
} catch (err) {
41+
console.error('Error finding content:', err);
42+
this.error = `Error: ${err.message || err}`;
43+
} finally {
44+
this.loading = false;
45+
}
46+
}
47+
48+
render() {
49+
return html`
50+
<link
51+
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
52+
rel="stylesheet"
53+
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
54+
crossorigin="anonymous"
55+
/>
56+
<link rel="stylesheet" href="/ux/main.css" onload="document.body.style.visibility = 'initial'">
57+
58+
<div class="container">
59+
<h2>Find CID</h2>
60+
<div class="search-container">
61+
<input
62+
autofocus
63+
type="text"
64+
placeholder="Enter CID (baf...)"
65+
.value="${this.searchCid}"
66+
@input="${this.handleInput}"
67+
@keypress="${(e) => e.key === 'Enter' && this.handleFind()}"
68+
/>
69+
<button
70+
class="btn btn-primary"
71+
@click="${this.handleFind}"
72+
?disabled="${this.loading}"
73+
>
74+
${this.loading ? 'Searching...' : 'Find'}
75+
</button>
76+
</div>
77+
78+
${this.error ? html`
79+
<div class="alert alert-danger">${this.error}</div>
80+
` : ''}
81+
82+
${this.results.length > 0 ? html`
83+
<h3>Results</h3>
84+
<table class="table table-dark table-striped">
85+
<thead>
86+
<tr>
87+
<th>Piece CID</th>
88+
<th>Offset</th>
89+
<th>Size</th>
90+
</tr>
91+
</thead>
92+
<tbody>
93+
${this.results.map(item => html`
94+
<tr>
95+
<td><a href="/pages/piece/?id=${item.piece_cid}">${item.piece_cid}</a></td>
96+
<td>${item.err ? html`<span class="text-danger">${item.err}</span>` : item.offset}</td>
97+
<td>${this.formatBytes(item.size)}</td>
98+
</tr>
99+
`)}
100+
</tbody>
101+
</table>
102+
` : ''}
103+
</div>
104+
`;
105+
}
106+
107+
formatBytes(bytes) {
108+
if (!bytes) return '0 Bytes';
109+
const k = 1024;
110+
const sizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
111+
const i = Math.floor(Math.log(bytes) / Math.log(k));
112+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
113+
}
114+
115+
static styles = css`
116+
.search-container {
117+
display: grid;
118+
grid-template-columns: 1fr auto;
119+
grid-column-gap: 0.75rem;
120+
margin-bottom: 1rem;
121+
}
122+
`;
123+
}
124+
125+
customElements.define('content-page', ContentPage);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>Content</title>
5+
<script type="module" src="/ux/curio-ux.mjs"></script>
6+
<script type="module" src="/ux/components/Drawer.mjs"></script>
7+
<script type="module" src="content.mjs"></script>
8+
</head>
9+
<body style="visibility:hidden; background:rgb(11, 22, 34)" data-bs-theme="dark">
10+
<curio-ux>
11+
<div class="page" style="margin-left: 20px; margin-right: 10px">
12+
<section class="section">
13+
<div class="row">
14+
<h1>Content</h1>
15+
</div>
16+
<div class="row">
17+
<div class="col-md-auto" style="max-width: 95%">
18+
<content-page></content-page>
19+
</div>
20+
</div>
21+
</section>
22+
</div>
23+
</curio-ux>
24+
</body>
25+
</html>
26+

web/static/ux/curio-ux.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@ class CurioUX extends LitElement {
256256
<span>Wallets</span>
257257
</a>
258258
</li>
259+
<li>
260+
<a href="/pages/content/" class="nav-link text-white ${active=='/pages/content/'? 'active':''}">
261+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi me-2 bi-box" viewBox="0 0 16 16">
262+
<path d="M8.186 1.113a.5.5 0 0 0-.372 0L1.846 3.5 8 5.961 14.154 3.5zM15 4.239l-6.5 2.6v7.922l6.5-2.6V4.24zM7.5 14.762V6.838L1 4.239v7.923zM7.443.184a1.5 1.5 0 0 1 1.114 0l7.129 2.852A.5.5 0 0 1 16 3.5v8.662a1 1 0 0 1-.629.928l-7.185 2.874a.5.5 0 0 1-.372 0L.63 13.09a1 1 0 0 1-.63-.928V3.5a.5.5 0 0 1 .314-.464z"/>
263+
</svg>
264+
<span>Content</span>
265+
</a>
266+
</li>
259267
<li>
260268
<a href="https://docs.curiostorage.org/" target="_blank" class="nav-link text-white">
261269
<svg class="bi me-2 bi-book-half" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">

0 commit comments

Comments
 (0)