Skip to content

Commit

Permalink
feat(gateway): preview dag-cbor/-json with links
Browse files Browse the repository at this point in the history
  • Loading branch information
hacdias committed May 26, 2023
1 parent 00428cd commit 86278bd
Show file tree
Hide file tree
Showing 12 changed files with 313 additions and 48 deletions.
5 changes: 3 additions & 2 deletions gateway/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ func initTemplates() {

type GlobalData struct {
SupportURL string
GatewayURL string
DNSLink bool
}

type DagTemplateData struct {
Expand All @@ -90,6 +92,7 @@ type DagTemplateData struct {
CID string
CodecName string
CodecHex string
Node *ParsedNode
}

type ErrorTemplateData struct {
Expand All @@ -101,8 +104,6 @@ type ErrorTemplateData struct {

type DirectoryTemplateData struct {
GlobalData
GatewayURL string
DNSLink bool
Listing []DirectoryItem
Size string
Path string
Expand Down
27 changes: 27 additions & 0 deletions gateway/assets/dag.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,33 @@
<li><a href="?format=dag-cbor" rel="nofollow">Valid DAG-CBOR</a> (specs at <a href="https://ipld.io/specs/codecs/dag-cbor/spec/" target="_blank" rel="noopener noreferrer">IPLD</a> and <a href="https://www.iana.org/assignments/media-types/application/vnd.ipld.dag-cbor" target="_blank" rel="noopener noreferrer">IANA</a>)</li>
</ul>
</section>
{{ with .Node }}
<section class="full-width">
<header>
<strong>DAG Preview</strong>
</header>
{{ template "node" (args $ .) }}
</section>
{{ end }}
</main>
</body>
</html>

{{ define "node" }}
{{ $root := index . 0 }}
{{ $node := index . 1 }}
{{ if len $node.Values }}
<div class="grid dag">
{{ range $index, $key := $node.Keys }}
{{ template "node" (args $root $key) }}
{{ template "node" (args $root (index $node.Values $index)) }}
{{ end }}
</div>
{{ else }}
<div translate="no">
{{ with $node.CID }}<a class="ipfs-hash" href={{ if $root.DNSLink }}"https://cid.ipfs.tech/#{{ . | urlEscape}}" target="_blank" rel="noreferrer noopener"{{ else }}"{{ $root.GatewayURL }}/ipfs/{{ . | urlEscape}}"{{ end }}>{{ end }}
<code>{{- $node.Value -}}</code>
{{ with $node.CID }}</a>{{ end }}
</div>
{{ end }}
{{ end }}
110 changes: 110 additions & 0 deletions gateway/assets/node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package assets

import (
"fmt"
"html/template"

"github.com/ipld/go-ipld-prime/datamodel"
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
)

type ParsedNode struct {
Keys []*ParsedNode
Values []*ParsedNode
Value string
CID string
}

func ParseNode(node datamodel.Node) (*ParsedNode, error) {
dag := &ParsedNode{}

switch node.Kind() {
case datamodel.Kind_Map:
it := node.MapIterator()

for !it.Done() {
k, v, err := it.Next()
if err != nil {
return nil, err
}

kd, err := ParseNode(k)
if err != nil {
return nil, err
}

vd, err := ParseNode(v)
if err != nil {
return nil, err
}

dag.Keys = append(dag.Keys, kd)
dag.Values = append(dag.Values, vd)
}
case datamodel.Kind_List:
it := node.ListIterator()
for !it.Done() {
k, v, err := it.Next()
if err != nil {
return nil, err
}

vd, err := ParseNode(v)
if err != nil {
return nil, err
}

dag.Keys = append(dag.Keys, &ParsedNode{Value: fmt.Sprint(k)})
dag.Values = append(dag.Values, vd)
}
case datamodel.Kind_Bool:
v, err := node.AsBool()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%t", v)
case datamodel.Kind_Int:
v, err := node.AsInt()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%d", v)
case datamodel.Kind_Float:
v, err := node.AsFloat()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprintf("%f", v)
case datamodel.Kind_String:
v, err := node.AsString()
if err != nil {
return nil, err
}
dag.Value = template.HTMLEscapeString(v)
case datamodel.Kind_Bytes:
v, err := node.AsBytes()
if err != nil {
return nil, err
}
dag.Value = fmt.Sprint(v)
case datamodel.Kind_Link:
lnk, err := node.AsLink()
if err != nil {
return nil, err
}
dag.Value = lnk.String()

cl, isCid := lnk.(cidlink.Link)
if isCid {
dag.CID = cl.Cid.String()
}
case datamodel.Kind_Invalid:
dag.Value = "INVALID"
case datamodel.Kind_Null:
dag.Value = "NULL"
default:
dag.Value = "UNKNOWN"
}

return dag, nil
}
36 changes: 32 additions & 4 deletions gateway/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,17 @@ main section:not(:last-child) {
border-bottom: 1px solid var(--dark-white);
}

main section header {
background-color: var(--near-white);
}

.grid {
display: grid;
}

.grid > div {
padding: .7em;
border-top: 1px solid var(--dark-white);
border-bottom: 1px solid var(--dark-white);
}

.grid.dir {
Expand All @@ -165,8 +169,8 @@ main section:not(:last-child) {
padding-right: 1em;
}

.grid.dir > div:nth-child(-n+4) {
border-top: 0;
.grid.dir > div:nth-last-child(-n+4) {
border-bottom: 0;
}

.grid.dir > div:nth-of-type(8n+5),
Expand All @@ -176,6 +180,31 @@ main section:not(:last-child) {
background-color: var(--near-white);
}

.grid.dag {
grid-template-columns: max-content 1fr;
}

.grid.dag .grid {
padding: 0;
}

.grid.dag > div:nth-last-child(-n+2) {
border-bottom: 0;
}

.grid.dag > div {
background: white
}

.grid.dag > div:nth-child(4n),
.grid.dag > div:nth-child(4n+3) {
background-color: var(--near-white);
}

section > .grid.dag > div:nth-of-type(2n+1) {
padding-left: 1em;
}

.type-icon,
.type-icon > * {
width: 1.15em
Expand Down Expand Up @@ -222,4 +251,3 @@ main section:not(:last-child) {
display: none;
}
}

6 changes: 6 additions & 0 deletions gateway/assets/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ func iconFromExt(filename string) string {
return "ipfs-_blank" // Default is blank icon.
}

// args is a helper function to allow sending more than one object to a template.
func args(args ...interface{}) []interface{} {
return args
}

var funcMap = template.FuncMap{
"iconFromExt": iconFromExt,
"urlEscape": urlEscape,
"args": args,
}

func readFile(fs fs.FS, filename string) ([]byte, error) {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
89 changes: 78 additions & 11 deletions gateway/assets/test/main.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
package main

import (
"embed"
"fmt"
"net/http"
"os"
"strconv"
"strings"

"github.com/ipfs/boxo/gateway/assets"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime/multicodec"
"github.com/ipld/go-ipld-prime/node/basicnode"
mc "github.com/multiformats/go-multicodec"

// Ensure basic codecs are registered.
_ "github.com/ipld/go-ipld-prime/codec/cbor"
_ "github.com/ipld/go-ipld-prime/codec/dagcbor"
_ "github.com/ipld/go-ipld-prime/codec/dagjson"
_ "github.com/ipld/go-ipld-prime/codec/json"
_ "github.com/ipld/go-ipld-prime/codec/raw"
)

//go:embed dag/*.block
var embeds embed.FS

const (
testPath = "/ipfs/QmFooBarQXB2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7/a/b/c"
)

var directoryTestData = assets.DirectoryTemplateData{
GlobalData: assets.GlobalData{
SupportURL: "http://example.com",
GatewayURL: "//localhost:3000",
DNSLink: true,
},
GatewayURL: "//localhost:3000",
DNSLink: true,
Listing: []assets.DirectoryItem{{
Size: "25 MiB",
Name: "short-film.mov",
Expand Down Expand Up @@ -59,14 +75,58 @@ var directoryTestData = assets.DirectoryTemplateData{
Hash: "QmFooBazBar2mzChmMeKY47C43LxUdg1NDJ5MWcKMKxDu7",
}

var dagTestData = assets.DagTemplateData{
GlobalData: assets.GlobalData{
SupportURL: "http://example.com",
},
Path: "/ipfs/baguqeerabn4wonmz6icnk7dfckuizcsf4e4igua2ohdboecku225xxmujepa",
CID: "baguqeerabn4wonmz6icnk7dfckuizcsf4e4igua2ohdboecku225xxmujepa",
CodecName: "dag-json",
CodecHex: "0x129",
var dagTestData = map[string]*assets.DagTemplateData{}

func loadDagTestData() {
entries, err := embeds.ReadDir("dag")
if err != nil {
panic(err)
}

for _, entry := range entries {
cidStr := strings.TrimSuffix(entry.Name(), ".block")
cid, err := cid.Decode(cidStr)
if err != nil {
panic(err)
}

f, err := embeds.Open("dag/" + entry.Name())
if err != nil {
panic(err)
}

codec := cid.Prefix().Codec
decoder, err := multicodec.LookupDecoder(codec)
if err != nil {
panic(err)
}

node := basicnode.Prototype.Any.NewBuilder()
err = decoder(node, f)
if err != nil {
panic(err)
}

cidCodec := mc.Code(cid.Prefix().Codec)

dag, err := assets.ParseNode(node.Build())
if err != nil {
panic(err)
}

dagTestData[cid.String()] = &assets.DagTemplateData{
GlobalData: assets.GlobalData{
SupportURL: "http://example.com",
GatewayURL: "//localhost:3000",
DNSLink: true,
},
Path: "/ipfs/" + cid.String(),
CID: cid.String(),
CodecName: cidCodec.String(),
CodecHex: fmt.Sprintf("0x%x", uint64(cidCodec)),
Node: dag,
}
}
}

func init() {
Expand All @@ -80,6 +140,8 @@ func init() {
ShortHash: "QmbW\u2026sMnR",
})
}

loadDagTestData()
}

func runTemplate(w http.ResponseWriter, filename string, data interface{}) {
Expand All @@ -101,7 +163,12 @@ func main() {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/dag":
runTemplate(w, "dag.html", dagTestData)
cid := r.URL.Query().Get("cid")
if cid == "" {
cid = "bafyreihnpl7ami7esahkfdnemm6idx4r2n6u3apmtcrxlqwuapgjsciihy"
}

runTemplate(w, "dag.html", dagTestData[cid])
case "/directory":
runTemplate(w, "directory.html", directoryTestData)
case "/error":
Expand Down
Loading

0 comments on commit 86278bd

Please sign in to comment.