statistics: Better display on the front page
This commit is contained in:
parent
bc1bf0db1c
commit
9a1cf4e53b
12 changed files with 181 additions and 59 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
package cli
|
package cli
|
||||||
|
|
||||||
// ===========================================================================================================
|
// ===========================================================================================================
|
||||||
// This file was generated automatically at 16-11-2022 12:32:57 using gogenlicense.
|
// This file was generated automatically at 16-11-2022 18:40:47 using gogenlicense.
|
||||||
// Do not edit manually, as changes may be overwritten.
|
// Do not edit manually, as changes may be overwritten.
|
||||||
// ===========================================================================================================
|
// ===========================================================================================================
|
||||||
|
|
||||||
|
|
@ -1631,7 +1631,7 @@ package cli
|
||||||
// # Generation
|
// # Generation
|
||||||
//
|
//
|
||||||
// This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool.
|
// This variable and the associated documentation have been automatically generated using the 'gogenlicense' tool.
|
||||||
// It was last updated at 16-11-2022 12:32:57.
|
// It was last updated at 16-11-2022 18:40:47.
|
||||||
var LegalNotices string
|
var LegalNotices string
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@
|
||||||
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
|
<a href="{{.URL}}" target="_blank" rel="noopener noreferrer">{{.URL}}</a><br>
|
||||||
<small>
|
<small>
|
||||||
{{ .Statistics.Bundles.Summary }}
|
{{ .Statistics.Bundles.Summary }}
|
||||||
|
|
||||||
|
{{ $edit := .Statistics.Bundles.LastEdit }}
|
||||||
|
{{ if $edit.Valid }}
|
||||||
|
<br />
|
||||||
|
last edited {{ $edit.Time.Format "2006-01-02T15:04:05Z07:00" }}
|
||||||
|
{{ end }}
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -310,7 +310,7 @@
|
||||||
<code>{{ .Count }}</code>
|
<code>{{ .Count }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>{{ .LastEdit }}</code>
|
<code class="date">{{ .LastEdit.Time.Format "2006-01-02T15:04:05Z07:00" }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<code>{{ .MainBundle }}</code>
|
<code>{{ .MainBundle }}</code>
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,13 @@ var AssetsComponentsIndex = Assets{
|
||||||
|
|
||||||
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
|
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
|
||||||
var AssetsControlIndex = Assets{
|
var AssetsControlIndex = Assets{
|
||||||
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script src="/static/ControlIndex.613b02c2.js" nomodule="" defer></script>`,
|
Scripts: `<script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlIndex.6d1d8ee0.js"></script><script src="/static/ControlIndex.03d7b00f.js" nomodule="" defer></script>`,
|
||||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
|
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css">`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
|
// AssetsControlInstance contains assets for the 'ControlInstance' entrypoint.
|
||||||
var AssetsControlInstance = Assets{
|
var AssetsControlInstance = Assets{
|
||||||
Scripts: `<script nomodule="" defer src="/static/ControlIndex.613b02c2.js"></script><script type="module" src="/static/ControlIndex.cfbf936d.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
|
Scripts: `<script nomodule="" defer src="/static/ControlIndex.03d7b00f.js"></script><script type="module" src="/static/ControlIndex.6d1d8ee0.js"></script><script type="module" src="/static/HomeHome.38d394c2.js"></script><script src="/static/HomeHome.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/ControlInstance.66b95713.js"></script><script src="/static/ControlInstance.9cc7166d.js" nomodule="" defer></script>`,
|
||||||
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
|
Styles: `<link rel="stylesheet" href="/static/HomeHome.a75f04fa.css"><link rel="stylesheet" href="/static/ControlIndex.6d59e220.css"><link rel="stylesheet" href="/static/ControlIndex.6d2ae968.css"><link rel="stylesheet" href="/static/ControlInstance.38d394c2.css">`,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
1
internal/dis/component/control/static/dist/ControlIndex.03d7b00f.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.03d7b00f.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
internal/dis/component/control/static/dist/ControlIndex.6d1d8ee0.js
vendored
Normal file
1
internal/dis/component/control/static/dist/ControlIndex.6d1d8ee0.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,17 @@
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
|
const types: Record<string, (element: HTMLElement) => HTMLElement | string> = {
|
||||||
"date": (element) => {
|
"date": (element) => {
|
||||||
return dayjs(element.innerText).format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
|
const value = dayjs(element.innerText);
|
||||||
|
const text = value.format('YYYY-MM-DD HH:mm:ss ([UTC]Z)')
|
||||||
|
|
||||||
|
// if the date is the zero date, then it is assumed to be invalid
|
||||||
|
if (value.unix() === 0) {
|
||||||
|
const code = document.createElement('code')
|
||||||
|
code.style.color = 'gray'
|
||||||
|
code.append(text)
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
return text
|
||||||
},
|
},
|
||||||
"path": (element) => {
|
"path": (element) => {
|
||||||
const text = element.innerText.split("/");
|
const text = element.innerText.split("/");
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
package phpx
|
|
||||||
|
|
||||||
import "encoding/json"
|
|
||||||
|
|
||||||
// BooleanIsh represents a boolean php value.
|
|
||||||
//
|
|
||||||
// The value can be serialized to and from php and will behave accordingly.
|
|
||||||
//
|
|
||||||
// The value will always be Marshaled as "true" or "false".
|
|
||||||
//
|
|
||||||
// When Unmarshaled, it behaves as described on https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting.
|
|
||||||
type BooleanIsh bool
|
|
||||||
|
|
||||||
func (bi BooleanIsh) MarshalJSON() ([]byte, error) {
|
|
||||||
if bi {
|
|
||||||
return []byte("true"), nil
|
|
||||||
}
|
|
||||||
return []byte("false"), nil
|
|
||||||
}
|
|
||||||
func (bi *BooleanIsh) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
// unmarshal into a generic value
|
|
||||||
var value any
|
|
||||||
err = json.Unmarshal(data, &value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if it is false ish
|
|
||||||
var isFalseIsh bool
|
|
||||||
switch d := value.(type) {
|
|
||||||
case bool:
|
|
||||||
isFalseIsh = !d
|
|
||||||
case int:
|
|
||||||
isFalseIsh = d == 0
|
|
||||||
case float64:
|
|
||||||
isFalseIsh = d == 0
|
|
||||||
case string:
|
|
||||||
isFalseIsh = d == "" || d == "0"
|
|
||||||
case []any:
|
|
||||||
isFalseIsh = len(d) == 0
|
|
||||||
case map[string]any:
|
|
||||||
isFalseIsh = len(d) == 0
|
|
||||||
case nil:
|
|
||||||
isFalseIsh = true
|
|
||||||
}
|
|
||||||
*bi = BooleanIsh(!isFalseIsh)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
138
internal/phpx/types.go
Normal file
138
internal/phpx/types.go
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
package phpx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PHPBoolean represents a boolean php value.
|
||||||
|
//
|
||||||
|
// The value can be marshaled to and from php and will behave as a PHP would behave.
|
||||||
|
//
|
||||||
|
// The value will always be marshaled as "true" or "false".
|
||||||
|
// Unmarshaling uses [Boolean].
|
||||||
|
type PHPBoolean bool
|
||||||
|
|
||||||
|
func (bi PHPBoolean) MarshalJSON() ([]byte, error) {
|
||||||
|
if bi {
|
||||||
|
return []byte("true"), nil
|
||||||
|
}
|
||||||
|
return []byte("false"), nil
|
||||||
|
}
|
||||||
|
func (bi *PHPBoolean) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
// unmarshal into a generic value
|
||||||
|
var value any
|
||||||
|
err = json.Unmarshal(data, &value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// cast into a boolean
|
||||||
|
cast, ok := Boolean(value)
|
||||||
|
if !ok {
|
||||||
|
value = false
|
||||||
|
}
|
||||||
|
*bi = PHPBoolean(cast)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boolean tries to cast the given value to a boolean.
|
||||||
|
//
|
||||||
|
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
|
||||||
|
// Value treates all values as the boolean true, except for the ones listed at [doc].
|
||||||
|
//
|
||||||
|
// [doc]: https://www.php.net/manual/en/language.types.boolean.php#language.types.boolean.casting
|
||||||
|
func Boolean(value any) (b bool, ok bool) {
|
||||||
|
switch d := value.(type) {
|
||||||
|
case bool:
|
||||||
|
return d, true
|
||||||
|
case float64:
|
||||||
|
return d != 0, true
|
||||||
|
case string:
|
||||||
|
return (d != "" && d != "0"), true
|
||||||
|
case []any:
|
||||||
|
return len(d) != 0, true
|
||||||
|
case map[string]any:
|
||||||
|
return len(d) != 0, true
|
||||||
|
case nil:
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// String tries to cast the given value to a string.
|
||||||
|
//
|
||||||
|
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
|
||||||
|
// Value casting is described at [doc].
|
||||||
|
//
|
||||||
|
// [doc]: https://www.php.net/manual/en/language.types.string.php#language.types.string.casting
|
||||||
|
func String(value any) (s string, ok bool) {
|
||||||
|
switch d := value.(type) {
|
||||||
|
case bool:
|
||||||
|
if d {
|
||||||
|
return "1", true
|
||||||
|
}
|
||||||
|
return "", true
|
||||||
|
case float64:
|
||||||
|
if d == float64(int64(d)) {
|
||||||
|
return strconv.FormatInt(int64(d), 10), true
|
||||||
|
}
|
||||||
|
// TODO: not sure this is entirely correct
|
||||||
|
// and we should handle ints here!
|
||||||
|
return strconv.FormatFloat(d, 'E', 1, 64), true
|
||||||
|
case string:
|
||||||
|
return d, true
|
||||||
|
case []any, map[string]any:
|
||||||
|
return "Array", true
|
||||||
|
case nil:
|
||||||
|
return "", true
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer tries to cast the given value to an integer.
|
||||||
|
//
|
||||||
|
// It is able to handle any value that would be [json.Unmarshaled] from a corresponding PHP value.
|
||||||
|
// Value casting is described at [doc].
|
||||||
|
//
|
||||||
|
// [doc]: https://www.php.net/manual/en/language.types.integer.php#language.types.integer.casting
|
||||||
|
func Integer(value any) (i int64, ok bool) {
|
||||||
|
str, ok := String(value)
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to parse the "leading" string, by successively cutting off parts of the tail
|
||||||
|
// once we have a valid number, return it.
|
||||||
|
for l := 0; l < len(str); l++ {
|
||||||
|
i64, err := strconv.ParseInt(str[:len(str)-l], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return i64, true
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeInt represents a time value in PHP, represented as an integer
|
||||||
|
type TimeInt time.Time
|
||||||
|
|
||||||
|
func (ts TimeInt) Time() time.Time {
|
||||||
|
return time.Time(ts)
|
||||||
|
}
|
||||||
|
func (ts TimeInt) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(strconv.FormatInt(ts.Time().Unix(), 10)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *TimeInt) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
var value any
|
||||||
|
if err := json.Unmarshal(data, &value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
unix, _ := Integer(value)
|
||||||
|
*ts = TimeInt(time.Unix(unix, 0))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -84,14 +84,31 @@ type BundleStatistics struct {
|
||||||
|
|
||||||
Count int `json:"entities"`
|
Count int `json:"entities"`
|
||||||
|
|
||||||
LastEdit int `json:"lastEdit"`
|
LastEdit phpx.TimeInt `json:"lastEdit"`
|
||||||
|
|
||||||
MainBundle phpx.BooleanIsh `json:"mainBundle"`
|
MainBundle phpx.PHPBoolean `json:"mainBundle"`
|
||||||
} `json:"bundleStatistics"`
|
} `json:"bundleStatistics"`
|
||||||
TotalBundles int `json:"totalBundles"`
|
TotalBundles int `json:"totalBundles"`
|
||||||
TotalMainBundles int `json:"totalMainBundles"`
|
TotalMainBundles int `json:"totalMainBundles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LastEdit struct {
|
||||||
|
Time time.Time
|
||||||
|
Valid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastEdit returns the last time any bundle was edited, and if any edit was bigger than the reference time
|
||||||
|
func (bs BundleStatistics) LastEdit() (le LastEdit) {
|
||||||
|
for _, bundle := range bs.Bundles {
|
||||||
|
time := bundle.LastEdit.Time()
|
||||||
|
if time.After(le.Time) {
|
||||||
|
le.Valid = true
|
||||||
|
le.Time = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (bs BundleStatistics) Summary() string {
|
func (bs BundleStatistics) Summary() string {
|
||||||
var totalCount int
|
var totalCount int
|
||||||
for _, bundle := range bs.Bundles {
|
for _, bundle := range bs.Bundles {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue