fields.go 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. package widget
  2. import (
  3. "fmt"
  4. "html/template"
  5. "os"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "gopkg.in/yaml.v3"
  11. )
  12. var HSLColorPattern = regexp.MustCompile(`^(?:hsla?\()?(\d{1,3})(?: |,)+(\d{1,3})%?(?: |,)+(\d{1,3})%?\)?$`)
  13. var EnvFieldPattern = regexp.MustCompile(`^\${([A-Z_]+)}$`)
  14. const (
  15. HSLHueMax = 360
  16. HSLSaturationMax = 100
  17. HSLLightnessMax = 100
  18. )
  19. type HSLColorField struct {
  20. Hue uint16
  21. Saturation uint8
  22. Lightness uint8
  23. }
  24. type IconSource uint8
  25. const (
  26. LocalFile IconSource = iota
  27. SimpleIcon
  28. DashboardIcon
  29. )
  30. func (c *HSLColorField) String() string {
  31. return fmt.Sprintf("hsl(%d, %d%%, %d%%)", c.Hue, c.Saturation, c.Lightness)
  32. }
  33. func (c *HSLColorField) AsCSSValue() template.CSS {
  34. return template.CSS(c.String())
  35. }
  36. func (c *HSLColorField) UnmarshalYAML(node *yaml.Node) error {
  37. var value string
  38. if err := node.Decode(&value); err != nil {
  39. return err
  40. }
  41. matches := HSLColorPattern.FindStringSubmatch(value)
  42. if len(matches) != 4 {
  43. return fmt.Errorf("invalid HSL color format: %s", value)
  44. }
  45. hue, err := strconv.ParseUint(matches[1], 10, 16)
  46. if err != nil {
  47. return err
  48. }
  49. if hue > HSLHueMax {
  50. return fmt.Errorf("HSL hue must be between 0 and %d", HSLHueMax)
  51. }
  52. saturation, err := strconv.ParseUint(matches[2], 10, 8)
  53. if err != nil {
  54. return err
  55. }
  56. if saturation > HSLSaturationMax {
  57. return fmt.Errorf("HSL saturation must be between 0 and %d", HSLSaturationMax)
  58. }
  59. lightness, err := strconv.ParseUint(matches[3], 10, 8)
  60. if err != nil {
  61. return err
  62. }
  63. if lightness > HSLLightnessMax {
  64. return fmt.Errorf("HSL lightness must be between 0 and %d", HSLLightnessMax)
  65. }
  66. c.Hue = uint16(hue)
  67. c.Saturation = uint8(saturation)
  68. c.Lightness = uint8(lightness)
  69. return nil
  70. }
  71. var DurationPattern = regexp.MustCompile(`^(\d+)(s|m|h|d)$`)
  72. type DurationField time.Duration
  73. func (d *DurationField) UnmarshalYAML(node *yaml.Node) error {
  74. var value string
  75. if err := node.Decode(&value); err != nil {
  76. return err
  77. }
  78. matches := DurationPattern.FindStringSubmatch(value)
  79. if len(matches) != 3 {
  80. return fmt.Errorf("invalid duration format: %s", value)
  81. }
  82. duration, err := strconv.Atoi(matches[1])
  83. if err != nil {
  84. return err
  85. }
  86. switch matches[2] {
  87. case "s":
  88. *d = DurationField(time.Duration(duration) * time.Second)
  89. case "m":
  90. *d = DurationField(time.Duration(duration) * time.Minute)
  91. case "h":
  92. *d = DurationField(time.Duration(duration) * time.Hour)
  93. case "d":
  94. *d = DurationField(time.Duration(duration) * 24 * time.Hour)
  95. }
  96. return nil
  97. }
  98. type OptionalEnvString string
  99. func (f *OptionalEnvString) UnmarshalYAML(node *yaml.Node) error {
  100. var value string
  101. err := node.Decode(&value)
  102. if err != nil {
  103. return err
  104. }
  105. matches := EnvFieldPattern.FindStringSubmatch(value)
  106. if len(matches) != 2 {
  107. *f = OptionalEnvString(value)
  108. return nil
  109. }
  110. value, found := os.LookupEnv(matches[1])
  111. if !found {
  112. return fmt.Errorf("environment variable %s not found", matches[1])
  113. }
  114. *f = OptionalEnvString(value)
  115. return nil
  116. }
  117. func (f *OptionalEnvString) String() string {
  118. return string(*f)
  119. }
  120. func toRemoteResourceIconIfPrefixed(icon string) (string, IconSource) {
  121. var prefix, iconstr string
  122. prefix, iconstr, found := strings.Cut(icon, ":")
  123. if !found {
  124. return icon, LocalFile
  125. }
  126. if prefix == "si" {
  127. icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + iconstr + ".svg"
  128. return icon, SimpleIcon
  129. }
  130. if prefix == "di" {
  131. icon = "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/svg/" + iconstr + ".svg"
  132. return icon, DashboardIcon
  133. }
  134. return icon, LocalFile
  135. }