From 456c59c74604f108018ac0b4481414f1941fb531 Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Sat, 14 Jan 2023 09:45:08 +1000 Subject: [PATCH] Improvements for certificates table, adds expansion object to certificates --- backend/internal/api/handler/certificates.go | 15 ++++++--- .../internal/entity/certificate/methods.go | 11 ++++++- backend/internal/entity/certificate/model.go | 32 +++++++++++++++---- frontend/src/api/npm/getCertificates.ts | 2 +- frontend/src/components/Monospace.tsx | 11 +++++++ frontend/src/components/Table/Formatters.tsx | 31 +++++++++++++----- frontend/src/components/index.ts | 1 + frontend/src/index.scss | 4 +++ .../pages/Certificates/CertificatesTable.tsx | 10 ++++++ 9 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 frontend/src/components/Monospace.tsx diff --git a/backend/internal/api/handler/certificates.go b/backend/internal/api/handler/certificates.go index 44f5f3d0..cc4580b2 100644 --- a/backend/internal/api/handler/certificates.go +++ b/backend/internal/api/handler/certificates.go @@ -26,7 +26,7 @@ func GetCertificates() func(http.ResponseWriter, *http.Request) { return } - certificates, err := certificate.List(pageInfo, middleware.GetFiltersFromContext(r)) + certificates, err := certificate.List(pageInfo, middleware.GetFiltersFromContext(r), getExpandFromContext(r)) if err != nil { h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) } else { @@ -46,11 +46,16 @@ func GetCertificate() func(http.ResponseWriter, *http.Request) { return } - cert, err := certificate.GetByID(certificateID) - if err != nil { + item, err := certificate.GetByID(certificateID) + switch err { + case sql.ErrNoRows: + h.ResultErrorJSON(w, r, http.StatusNotFound, "Not found", nil) + case nil: + // nolint: errcheck,gosec + item.Expand(getExpandFromContext(r)) + h.ResultResponseJSON(w, r, http.StatusOK, item) + default: h.ResultErrorJSON(w, r, http.StatusBadRequest, err.Error(), nil) - } else { - h.ResultResponseJSON(w, r, http.StatusOK, cert) } } } diff --git a/backend/internal/entity/certificate/methods.go b/backend/internal/entity/certificate/methods.go index b0204699..23d15f02 100644 --- a/backend/internal/entity/certificate/methods.go +++ b/backend/internal/entity/certificate/methods.go @@ -102,7 +102,7 @@ func Update(certificate *Model) error { } // List will return a list of certificates -func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) { +func List(pageInfo model.PageInfo, filters []model.Filter, expand []string) (ListResponse, error) { var result ListResponse var exampleModel Model @@ -135,6 +135,15 @@ func List(pageInfo model.PageInfo, filters []model.Filter) (ListResponse, error) return result, err } + if expand != nil { + for idx := range items { + expandErr := items[idx].Expand(expand) + if expandErr != nil { + logger.Error("CertificatesExpansionError", expandErr) + } + } + } + result = ListResponse{ Items: items, Total: totalRows, diff --git a/backend/internal/entity/certificate/model.go b/backend/internal/entity/certificate/model.go index 7dec8f2e..0e39191d 100644 --- a/backend/internal/entity/certificate/model.go +++ b/backend/internal/entity/certificate/model.go @@ -13,8 +13,10 @@ import ( "npm/internal/database" "npm/internal/entity/certificateauthority" "npm/internal/entity/dnsprovider" + "npm/internal/entity/user" "npm/internal/logger" "npm/internal/types" + "npm/internal/util" ) const ( @@ -59,6 +61,7 @@ type Model struct { // Expansions: CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"` DNSProvider *dnsprovider.Model `json:"dns_provider,omitempty"` + User *user.Model `json:"user,omitempty"` } func (m *Model) getByQuery(query string, params []interface{}) error { @@ -161,7 +164,8 @@ func (m *Model) ValidateWildcardSupport() bool { } if hasWildcard { - m.Expand() + // nolint: errcheck, gosec + m.Expand([]string{"certificate-authority", "dns-provider"}) if !m.CertificateAuthority.IsWildcardSupported { return false } @@ -182,15 +186,28 @@ func (m *Model) setDefaultStatus() { } // Expand will populate attached objects for the model -func (m *Model) Expand() { - if m.CertificateAuthorityID > 0 { - certificateAuthority, _ := certificateauthority.GetByID(m.CertificateAuthorityID) +func (m *Model) Expand(items []string) error { + var err error + + if util.SliceContainsItem(items, "certificate-authority") && m.CertificateAuthorityID > 0 { + var certificateAuthority certificateauthority.Model + certificateAuthority, err = certificateauthority.GetByID(m.CertificateAuthorityID) m.CertificateAuthority = &certificateAuthority } - if m.DNSProviderID > 0 { - dnsProvider, _ := dnsprovider.GetByID(m.DNSProviderID) + + if util.SliceContainsItem(items, "dns-provider") && m.DNSProviderID > 0 { + var dnsProvider dnsprovider.Model + dnsProvider, err = dnsprovider.GetByID(m.DNSProviderID) m.DNSProvider = &dnsProvider } + + if util.SliceContainsItem(items, "user") && m.ID > 0 { + var usr user.Model + usr, err = user.GetByID(m.UserID) + m.User = &usr + } + + return err } // GetCertificateLocations will return the paths on disk where the SSL @@ -222,7 +239,8 @@ func (m *Model) GetCertificateLocations() (string, string, string) { func (m *Model) Request() error { logger.Info("Requesting certificate for: #%d %v", m.ID, m.Name) - m.Expand() + // nolint: errcheck, gosec + m.Expand([]string{"certificate-authority", "dns-provider"}) m.Status = StatusRequesting if err := m.Save(); err != nil { logger.Error("CertificateSaveError", err) diff --git a/frontend/src/api/npm/getCertificates.ts b/frontend/src/api/npm/getCertificates.ts index ace316d8..c8df51f4 100644 --- a/frontend/src/api/npm/getCertificates.ts +++ b/frontend/src/api/npm/getCertificates.ts @@ -11,7 +11,7 @@ export async function getCertificates( const { result } = await api.get( { url: "certificates", - params: { limit, offset, sort, ...filters }, + params: { limit, offset, sort, expand: "user", ...filters }, }, abortController, ); diff --git a/frontend/src/components/Monospace.tsx b/frontend/src/components/Monospace.tsx new file mode 100644 index 00000000..0f9dffcd --- /dev/null +++ b/frontend/src/components/Monospace.tsx @@ -0,0 +1,11 @@ +import { Text, TextProps } from "@chakra-ui/react"; + +function Monospace(props: TextProps) { + return ( + + {props.children} + + ); +} + +export { Monospace }; diff --git a/frontend/src/components/Table/Formatters.tsx b/frontend/src/components/Table/Formatters.tsx index 554950c5..4fe988dd 100644 --- a/frontend/src/components/Table/Formatters.tsx +++ b/frontend/src/components/Table/Formatters.tsx @@ -1,5 +1,5 @@ import { Avatar, Badge, Text, Tooltip } from "@chakra-ui/react"; -import { RowAction, RowActionsMenu } from "components"; +import { Monospace, RowAction, RowActionsMenu } from "components"; import { intl } from "locale"; import getNiceDNSProvider from "modules/Acmesh"; @@ -73,13 +73,19 @@ function CapabilitiesFormatter() { function CertificateStatusFormatter() { const formatCell = ({ value }: any) => { - return ( - - {value - ? intl.formatMessage({ id: "ready" }) - : intl.formatMessage({ id: "setup-required" })} - - ); + let color = "cyan.500"; + switch (value) { + case "failed": + color = "red.400"; + break; + case "provided": + color = "green.400"; + break; + case "requesting": + color = "yellow.400"; + break; + } + return {intl.formatMessage({ id: value })}; }; return formatCell; @@ -185,6 +191,14 @@ function HostStatusFormatter() { return formatCell; } +function MonospaceFormatter() { + const formatCell = ({ value }: any) => { + return {value}; + }; + + return formatCell; +} + function UpstreamStatusFormatter() { const formatCell = ({ value, row }: any) => { if (value === "ready") { @@ -245,6 +259,7 @@ export { HostStatusFormatter, HostTypeFormatter, IDFormatter, + MonospaceFormatter, SecondsFormatter, UpstreamStatusFormatter, }; diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index a4b99784..47caaa09 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -5,6 +5,7 @@ export * from "./HelpDrawer"; export * from "./Loader"; export * from "./Loading"; export * from "./LocalePicker"; +export * from "./Monospace"; export * from "./Navigation"; export * from "./Permissions"; export * from "./PrettyButton"; diff --git a/frontend/src/index.scss b/frontend/src/index.scss index 69ed5227..9d011adf 100644 --- a/frontend/src/index.scss +++ b/frontend/src/index.scss @@ -25,6 +25,10 @@ table td.w-80 { width: 80px; } +span.monospace { + font-family: monospace; +} + /* helpdoc */ .helpdoc-body { p { diff --git a/frontend/src/pages/Certificates/CertificatesTable.tsx b/frontend/src/pages/Certificates/CertificatesTable.tsx index 0af0aa05..c983d4b0 100644 --- a/frontend/src/pages/Certificates/CertificatesTable.tsx +++ b/frontend/src/pages/Certificates/CertificatesTable.tsx @@ -3,7 +3,10 @@ import { useEffect, useMemo } from "react"; import { tableEvents, ActionsFormatter, + CertificateStatusFormatter, + GravatarFormatter, IDFormatter, + MonospaceFormatter, TableFilter, TableLayout, TablePagination, @@ -41,6 +44,11 @@ function CertificatesTable({ }: CertificatesTableProps) { const [columns, tableData] = useMemo(() => { const columns = [ + { + accessor: "user.gravatarUrl", + Cell: GravatarFormatter(), + className: "w-80", + }, { Header: intl.formatMessage({ id: "column.id" }), accessor: "id", @@ -53,6 +61,7 @@ function CertificatesTable({ accessor: "name", sortable: true, Filter: TextFilter, + Cell: MonospaceFormatter(), }, { Header: intl.formatMessage({ id: "column.validation-type" }), @@ -65,6 +74,7 @@ function CertificatesTable({ accessor: "status", sortable: true, Filter: TextFilter, + Cell: CertificateStatusFormatter(), }, { id: "actions",