diff --git a/deps/apiinfo.go b/deps/apiinfo.go index 789f2e89f..75b786d63 100644 --- a/deps/apiinfo.go +++ b/deps/apiinfo.go @@ -7,6 +7,7 @@ import ( "math/rand" "net/http" "reflect" + "strings" "sync" "time" @@ -71,11 +72,13 @@ func GetFullNodeAPIV1Curio(ctx *cli.Context, ainfoCfg []string) (api.Chain, json } // Compare with binary's network using BuildTypeString() - if string(networkName) != build.BuildTypeString()[1:] { - clog.Warnf("Network mismatch for node %s: binary built for %s but node is on %s", - head.addr, build.BuildTypeString()[1:], networkName) - closer() - continue + if !(strings.HasPrefix(string(networkName), "test") || strings.HasPrefix(string(networkName), "local")) { + if string(networkName) != build.BuildTypeString()[1:] { + clog.Warnf("Network mismatch for node %s: binary built for %s but node is on %s", + head.addr, build.BuildTypeString()[1:], networkName) + closer() + continue + } } fullNodes = append(fullNodes, v1api) diff --git a/web/api/webrpc/storage_stats.go b/web/api/webrpc/storage_stats.go index 6a43bed88..82b45da3a 100644 --- a/web/api/webrpc/storage_stats.go +++ b/web/api/webrpc/storage_stats.go @@ -8,6 +8,7 @@ import ( "github.com/samber/lo" "github.com/snadrus/must" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" @@ -80,7 +81,7 @@ func (a *WebRPC) StorageUseStats(ctx context.Context) ([]StorageUseStats, error) return stats, nil } -type StorageGCMarks struct { +type StorageGCMark struct { Actor int64 `db:"sp_id"` SectorNum int64 `db:"sector_num"` FileType int64 `db:"sector_filetype"` @@ -102,16 +103,70 @@ type StorageGCMarks struct { Miner string } -func (a *WebRPC) StorageGCMarks(ctx context.Context) ([]*StorageGCMarks, error) { - var marks []*StorageGCMarks - err := a.deps.DB.Select(ctx, &marks, ` - SELECT m.sp_id, m.sector_num, m.sector_filetype, m.storage_id, m.created_at, m.approved, m.approved_at, sl.can_seal, sl.can_store, sl.urls - FROM storage_removal_marks m LEFT JOIN storage_path sl ON m.storage_id = sl.storage_id - ORDER BY created_at DESC`) +type StorageGCMarks struct { + Marks []*StorageGCMark + Total int +} + +func (a *WebRPC) StorageGCMarks(ctx context.Context, miner *string, sectorNum *int64, limit int, offset int) (*StorageGCMarks, error) { + var spID *int64 + if miner != nil { + maddr, err := address.NewFromString(*miner) + if err != nil { + return nil, err + } + sp_id, err := address.IDFromAddress(maddr) + if err != nil { + return nil, err + } + tspid := int64(sp_id) + spID = &tspid + } + + if sectorNum != nil && *sectorNum < 0 { + return nil, xerrors.Errorf("invalid sector_num: %d", *sectorNum) + } + + var marks []*StorageGCMark + var total int + + // Get the total count of rows + err := a.deps.DB.QueryRow(ctx, `SELECT + COUNT(*) + FROM storage_removal_marks + WHERE + ($1::BIGINT IS NULL OR sp_id = $1) + AND ($2::BIGINT IS NULL OR sector_num = $2)`, spID, sectorNum).Scan(&total) if err != nil { - return nil, err + return nil, xerrors.Errorf("querying storage removal marks: %w", err) } + err = a.deps.DB.Select(ctx, &marks, ` + SELECT + m.sp_id, + m.sector_num, + m.sector_filetype, + m.storage_id, + m.created_at, + m.approved, + m.approved_at, + sl.can_seal, + sl.can_store, + sl.urls + FROM storage_removal_marks m + LEFT JOIN storage_path sl ON m.storage_id = sl.storage_id + WHERE + ($1::BIGINT IS NULL OR m.sp_id = $1) + AND ($2::BIGINT IS NULL OR m.sector_num = $2) + ORDER BY created_at + DESC LIMIT $3 + OFFSET $4`, spID, sectorNum, limit, offset) + if err != nil { + return nil, xerrors.Errorf("querying storage removal marks: %w", err) + } + + minerMap := make(map[int64]address.Address) + for i, m := range marks { marks[i].TypeName = storiface.SectorFileType(m.FileType).String() @@ -129,14 +184,21 @@ func (a *WebRPC) StorageGCMarks(ctx context.Context) ([]*StorageGCMarks, error) return must.One(url.Parse(u)).Host }) marks[i].Urls = strings.Join(us, ", ") - maddr, err := address.NewIDAddress(uint64(marks[i].Actor)) - if err != nil { - return nil, err + maddr, ok := minerMap[marks[i].Actor] + if !ok { + maddr, err = address.NewIDAddress(uint64(marks[i].Actor)) + if err != nil { + return nil, err + } + minerMap[marks[i].Actor] = maddr } marks[i].Miner = maddr.String() } - return marks, nil + return &StorageGCMarks{ + Marks: marks, + Total: total, + }, nil } func (a *WebRPC) StorageGCApprove(ctx context.Context, actor int64, sectorNum int64, fileType int64, storageID string) error { diff --git a/web/static/gc/gc-marks.mjs b/web/static/gc/gc-marks.mjs index 3fe9d3827..5523e0072 100644 --- a/web/static/gc/gc-marks.mjs +++ b/web/static/gc/gc-marks.mjs @@ -3,29 +3,117 @@ import RPCCall from '/lib/jsonrpc.mjs'; class StorageGCStats extends LitElement { static properties = { - data: { type: Array } + data: { type: Array }, + pageSize: { type: Number }, + currentPage: { type: Number }, + totalPages: { type: Number }, + totalCount: { type: Number }, + miner: { type: String }, + sectorNum: { type: Number }, }; constructor() { super(); this.data = []; - this.loadData(); + this.pageSize = 50; // Default number of rows per page + this.currentPage = 1; + this.totalPages = 0; + this.totalCount = 0; + this.miner = null; // Default: No Miner filter + this.sectorNum = null; // Default: No Sector Number filter + this.loadData(); // Load initial data for page 1 } - async loadData() { - this.data = await RPCCall('StorageGCMarks'); + async loadData(page = 1) { + const offset = (page - 1) * this.pageSize; + + // Fetch data from the backend with limit, offset, and optional filters + const response = await RPCCall('StorageGCMarks', [ + this.miner, // Include Miner filter if set + this.sectorNum, // Include Sector Number filter if set + this.pageSize, + offset, + ]); + + this.data = response.Marks || []; // Data for the current page + this.totalCount = response.Total || 0; // Total rows matching the filters + this.totalPages = Math.ceil(this.totalCount / this.pageSize); // Calculate total pages + this.currentPage = page; // Update the current page this.requestUpdate(); } async approveEntry(entry) { await RPCCall('StorageGCApprove', [entry.Actor, entry.SectorNum, entry.FileType, entry.StorageID]); + this.loadData(this.currentPage); // Reload current page + } + + updateFilters(event) { + const { name, value } = event.target; + if (name === 'miner') { + this.miner = value ? value.trim() : null; // Trim spaces for miner ID + } else if (name === 'sectorNum') { + this.sectorNum = value ? Number(value) : null; // Convert sectorNum to a number + } + } + + applyFilters() { + this.currentPage = 1; // Reset to page 1 when filters are applied this.loadData(); } + renderFilters() { + return html` + <div class="filter-container mb-3"> + <label for="miner">Miner:</label> + <input + id="miner" + name="miner" + type="text" + @input="${this.updateFilters}" + placeholder="Enter Miner ID" + > + + <label for="sectorNum">Sector Number:</label> + <input + id="sectorNum" + name="sectorNum" + type="number" + @input="${this.updateFilters}" + placeholder="Enter Sector Number" + > + <p></p> + <button @click="${this.applyFilters}" class="btn btn-primary">Apply Filters</button> + </div> + `; + } + + renderPagination() { + return html` + <nav> + <ul class="pagination"> + <li class="page-item ${this.currentPage === 1 ? 'disabled' : ''}"> + <button class="page-link" @click="${() => this.loadData(this.currentPage - 1)}">Previous</button> + </li> + <li class="page-item disabled"> + <span class="page-link"> + Page ${this.currentPage} of ${this.totalPages} + </span> + </li> + <li class="page-item ${this.currentPage === this.totalPages ? 'disabled' : ''}"> + <button class="page-link" @click="${() => this.loadData(this.currentPage + 1)}">Next</button> + </li> + </ul> + </nav> + `; + } + render() { return html` <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> <link rel="stylesheet" href="/ux/main.css" onload="document.body.style.visibility = 'initial'"> + <p></p> + ${this.renderFilters()} + <p></p> <table class="table table-dark"> <thead> <tr> @@ -44,26 +132,22 @@ class StorageGCStats extends LitElement { <td>${entry.Miner}</td> <td>${entry.SectorNum}</td> <td> - <div> - ${entry.StorageID} - </div> - <div> - ${entry.Urls} - </div> + <div>${entry.StorageID}</div> + <div>${entry.Urls}</div> </td> <td>${entry.PathType}</td> <td>${entry.TypeName}</td> <td>${entry.CreatedAt}</td> <td> - ${entry.Approved ? - "Yes " + entry.ApprovedAt : - html`No <button @click="${() => this.approveEntry(entry)}" class="btn btn-primary btn-sm">Approve</button>` - } + ${entry.Approved + ? `Yes ${entry.ApprovedAt}` + : html`No <button @click="${() => this.approveEntry(entry)}" class="btn btn-primary btn-sm">Approve</button>`} </td> </tr> - `)} + `)} </tbody> </table> + ${this.renderPagination()} `; } } @@ -76,7 +160,7 @@ class ApproveAllButton extends LitElement { constructor() { super(); - this.unapprove = false; // default is false, meaning "Approve All" + this.unapprove = false; // Default is "Approve All" } async handleClick() { @@ -98,4 +182,4 @@ class ApproveAllButton extends LitElement { `; } } -customElements.define('approve-all-button', ApproveAllButton); \ No newline at end of file +customElements.define('approve-all-button', ApproveAllButton); diff --git a/web/static/pages/pipeline_porep/pipeline-porep-sectors.mjs b/web/static/pages/pipeline_porep/pipeline-porep-sectors.mjs index 3a878ce02..50547da64 100644 --- a/web/static/pages/pipeline_porep/pipeline-porep-sectors.mjs +++ b/web/static/pages/pipeline_porep/pipeline-porep-sectors.mjs @@ -382,6 +382,7 @@ export function renderSectorState(name, rowspan, sector, task, after, started) { // 1) "waiting for precommit" if ( name === 'PComm Msg' && + sector.AfterSynthetic && sector.PreCommitReadyAt && !sector.AfterPrecommitMsg && !sector.TaskPrecommitMsg