base.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package options
  15. import (
  16. "fmt"
  17. "reflect"
  18. "strings"
  19. "time"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/pkg/errors"
  22. "yunion.io/x/pkg/gotypes"
  23. "yunion.io/x/pkg/util/reflectutils"
  24. dbapi "yunion.io/x/onecloud/pkg/apis/cloudcommon/db"
  25. "yunion.io/x/onecloud/pkg/util/tagutils"
  26. )
  27. // Int returns a pointer to int type with the same value as the argument. This
  28. // is intended to be used for literal initialization of options
  29. func Int(v int) *int {
  30. return &v
  31. }
  32. // Bool returns a pointer to bool type with the same value as the argument.
  33. // This is intended to be used for literal initialization of options
  34. func Bool(v bool) *bool {
  35. return &v
  36. }
  37. func String(v string) *string {
  38. return &v
  39. }
  40. // IntV returns the integer value as pointed to by the argument if it's
  41. // non-nil, return 0 otherwise
  42. func IntV(p *int) int {
  43. if p != nil {
  44. return *p
  45. }
  46. return 0
  47. }
  48. // BoolV returns the bool value as pointed to by the argument if it's non-nil,
  49. // return false otherwise
  50. func BoolV(p *bool) bool {
  51. if p != nil {
  52. return *p
  53. }
  54. return false
  55. }
  56. func StringV(p *string) string {
  57. if p != nil {
  58. return *p
  59. }
  60. return ""
  61. }
  62. type IParamsOptions interface {
  63. Params() (*jsonutils.JSONDict, error)
  64. }
  65. var BaseListOptionsType = reflect.TypeOf((*BaseListOptions)(nil)).Elem()
  66. func optionsStructRvToParams(rv reflect.Value) (*jsonutils.JSONDict, error) {
  67. p := jsonutils.NewDict()
  68. rvType := rv.Type()
  69. for i := 0; i < rvType.NumField(); i++ {
  70. ft := rvType.Field(i)
  71. jsonInfo := reflectutils.ParseStructFieldJsonInfo(ft)
  72. name := jsonInfo.MarshalName()
  73. if name == "" {
  74. continue
  75. }
  76. if jsonInfo.Ignore {
  77. continue
  78. }
  79. f := rv.Field(i)
  80. begin:
  81. switch f.Kind() {
  82. case reflect.Ptr:
  83. if f.IsNil() {
  84. continue
  85. }
  86. f = f.Elem()
  87. goto begin
  88. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  89. rv64 := f.Convert(gotypes.Int64Type)
  90. i64 := rv64.Interface().(int64)
  91. if i64 != 0 || !jsonInfo.OmitZero {
  92. p.Set(name, jsonutils.NewInt(i64))
  93. }
  94. case reflect.Bool:
  95. b := f.Interface().(bool)
  96. if b || !jsonInfo.OmitFalse {
  97. p.Set(name, jsonutils.NewBool(b))
  98. }
  99. case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  100. // NOTE uint64 converted to int64
  101. rv64 := f.Convert(gotypes.Uint64Type)
  102. i64 := rv64.Interface().(uint64)
  103. if i64 != 0 || !jsonInfo.OmitZero {
  104. p.Set(name, jsonutils.NewInt(int64(i64)))
  105. }
  106. case reflect.Float32, reflect.Float64:
  107. rv64 := f.Convert(gotypes.Float64Type)
  108. f64 := rv64.Interface().(float64)
  109. if f64 != 0 || !jsonInfo.OmitZero {
  110. p.Set(name, jsonutils.NewFloat64(f64))
  111. }
  112. case reflect.String:
  113. s := f.Interface().(string)
  114. if len(s) > 0 || !jsonInfo.OmitEmpty {
  115. p.Set(name, jsonutils.NewString(s))
  116. }
  117. case reflect.Struct:
  118. if ft.Anonymous {
  119. continue
  120. }
  121. if f.Type() == gotypes.TimeType {
  122. t := f.Interface().(time.Time)
  123. p.Set(name, jsonutils.NewTimeString(t))
  124. continue
  125. }
  126. // TODO
  127. msg := fmt.Sprintf("do not know what to do with non-anonymous struct field: %s", ft.Name)
  128. panic(msg)
  129. case reflect.Map:
  130. p.Set(name, jsonutils.Marshal(f.Interface()))
  131. case reflect.Slice, reflect.Array:
  132. l := f.Len()
  133. for i := 0; i < l; i++ {
  134. namei := fmt.Sprintf("%s.%d", name, i)
  135. vali := jsonutils.Marshal(f.Index(i).Interface())
  136. p.Set(namei, vali)
  137. }
  138. default:
  139. msg := fmt.Sprintf("unsupported field type %s: %s", ft.Name, ft.Type)
  140. panic(msg)
  141. }
  142. }
  143. return p, nil
  144. }
  145. func optionsStructToParams(v interface{}) (*jsonutils.JSONDict, error) {
  146. rv := reflect.Indirect(reflect.ValueOf(v))
  147. return optionsStructRvToParams(rv)
  148. }
  149. // StructToParams converts the struct as pointed to by the argument to JSON
  150. // dict params, ignoring any embedded in struct
  151. func StructToParams(v interface{}) (*jsonutils.JSONDict, error) {
  152. return optionsStructToParams(v)
  153. }
  154. // ListStructToParams converts the struct as pointed to by the argument to JSON
  155. // dict params, taking into account .BaseListOptions.Params() if it exists
  156. func ListStructToParams(v interface{}) (*jsonutils.JSONDict, error) {
  157. rv := reflect.Indirect(reflect.ValueOf(v))
  158. params, err := optionsStructRvToParams(rv)
  159. if err != nil {
  160. return nil, err
  161. }
  162. {
  163. for _, oName := range []string{"BaseListOptions", "MultiArchListOptions"} {
  164. f := rv.FieldByName(oName)
  165. if f.IsValid() {
  166. listOpts, ok := f.Addr().Interface().(IParamsOptions)
  167. if ok {
  168. listParams, err := listOpts.Params()
  169. if err != nil {
  170. return nil, err
  171. }
  172. params.Update(listParams)
  173. }
  174. }
  175. }
  176. }
  177. return params, nil
  178. }
  179. const (
  180. ListOrderAsc = "asc"
  181. ListOrderDesc = "desc"
  182. )
  183. type BaseListOptions struct {
  184. Limit *int `default:"20" help:"Page limit"`
  185. Offset *int `default:"0" help:"Page offset"`
  186. OrderBy []string `help:"Name of the field to be ordered by"`
  187. Order string `help:"List order" choices:"desc|asc"`
  188. Details *bool `help:"Show more details" default:"false"`
  189. ShowFailReason *bool `help:"show fail reason fields"`
  190. Search string `help:"Filter results by a simple keyword search"`
  191. Meta *bool `help:"Piggyback metadata information" json:"with_meta" token:"meta"`
  192. Filter []string `help:"Filters"`
  193. JointFilter []string `help:"Filters with joint table col; joint_tbl.related_key(origin_key).filter_col.filter_cond(filters)"`
  194. FilterAny *bool `help:"If true, match if any of the filters matches; otherwise, match if all of the filters match"`
  195. Admin *bool `help:"Is an admin call?"`
  196. Tenant string `help:"Tenant ID or Name" alias:"project"`
  197. ProjectDomain string `help:"Project domain filter"`
  198. User string `help:"User ID or Name"`
  199. Field []string `help:"Show only specified fields"`
  200. Scope string `help:"resource scope" choices:"system|domain|project|user"`
  201. System *bool `help:"Show system resource"`
  202. PendingDelete *bool `help:"Show only pending deleted resources"`
  203. PendingDeleteAll *bool `help:"Show also pending-deleted resources" json:"-"`
  204. DeleteAll *bool `help:"Show also deleted resources" json:"-"`
  205. ShowEmulated *bool `help:"Show all resources including the emulated resources"`
  206. ExportKeys string `help:"Export field keys"`
  207. ExtraListOptions
  208. Tags []string `help:"filter by Tags, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: \"hypervisor=aliyun;os_type=Linux;os_version\"" json:"-"`
  209. NoTags []string `help:"List resources without this tags, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: os_type=Linux, os_version" json:"-"`
  210. UserTags []string `help:"UserTags info, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: group=rd" json:"-"`
  211. CloudTags []string `help:"CloudTags info, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: price_key=cn-beijing" json:"-"`
  212. NoUserTags []string `help:"filter by without these UserTags, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: group=rd" json:"-"`
  213. NoCloudTags []string `help:"filter by no these CloudTags, key and value separated by \"=\", keyvalue pairs separated by \";\", eg: price_key=cn-beijing" json:"-"`
  214. ProjectTags []string `help:"filter by project tags, key and value separated by \"=\", keyvalue pairs separated by \";\"" json:"-"`
  215. NoProjectTags []string `help:"filter by no these project tags, key and value separated by \"=\", keyvalue pairs separated by \";\"" json:"-"`
  216. DomainTags []string `help:"filter by domain project tags, key and value separated by \"=\", keyvalue pairs separated by \";\"" json:"-"`
  217. NoDomainTags []string `help:"filter by no these domain tags, key and value separated by \"=\", keyvalue pairs separated by \";\"" json:"-"`
  218. ProjectOrganizations []string `help:"filter by projects of specified organizations"`
  219. DomainOrganizations []string `help:"filter by domains of specified organizations"`
  220. Manager []string `help:"List objects belonging to the cloud provider" json:"manager,omitempty"`
  221. Account string `help:"List objects belonging to the cloud account" json:"account,omitempty"`
  222. Provider []string `help:"List objects from the provider" choices:"OneCloud|VMware|Aliyun|Apsara|Qcloud|Azure|Aws|Huawei|OpenStack|Ucloud|VolcEngine|ZStack|Google|Ctyun|Cloudpods|Nutanix|BingoCloud|IncloudSphere|JDcloud|Proxmox|Ceph|CephFS|Ecloud|HCSO|HCS|HCSOP|H3C|S3|RemoteFile|Ksyun|Baidu|QingCloud|OracleCloud|SangFor|ZettaKit|UIS|CNWare" json:"provider,omitempty"`
  223. Brand []string `help:"List objects belonging to a special brand"`
  224. CloudEnv string `help:"Cloud environment" choices:"public|private|onpremise|private_or_onpremise" json:"cloud_env,omitempty"`
  225. PublicCloud *bool `help:"List objects belonging to public cloud" json:"public_cloud"`
  226. PrivateCloud *bool `help:"List objects belonging to private cloud" json:"private_cloud"`
  227. IsOnPremise *bool `help:"List objects belonging to on premise infrastructures" token:"on-premise" json:"is_on_premise"`
  228. IsManaged *bool `help:"List objects managed by external providers" token:"managed" json:"is_managed"`
  229. PagingMarker string `help:"Marker for pagination" json:"paging_marker"`
  230. PagingOrder string `help:"paging order" choices:"DESC|ASC"`
  231. OrderByTag string `help:"Order results by tag values, composed by a tag key and order, e.g user:部门:ASC"`
  232. Delete string `help:"show deleted records"`
  233. Id []string `help:"filter by id"`
  234. // Name []string `help:"fitler by name"`
  235. Status []string `help:"filter by status"`
  236. SummaryStats bool `help:"show summary stats" json:"summary_stats"`
  237. }
  238. func (opts *BaseListOptions) addTag(keyPrefix, tagstr string, idx int, params *jsonutils.JSONDict) error {
  239. return opts.addTagInternal(keyPrefix, "obj_tags", tagstr, idx, params)
  240. }
  241. func (opts *BaseListOptions) addNoTag(keyPrefix, tagstr string, idx int, params *jsonutils.JSONDict) error {
  242. return opts.addTagInternal(keyPrefix, "no_obj_tags", tagstr, idx, params)
  243. }
  244. func (opts *BaseListOptions) addTagInternal(keyPrefix, tagName, tagstr string, idx int, params *jsonutils.JSONDict) error {
  245. tags := SplitTag(tagstr)
  246. if len(tags) == 0 {
  247. return fmt.Errorf("empty tags")
  248. }
  249. for i, tag := range tags {
  250. params.Add(jsonutils.NewString(keyPrefix+tag.Key), fmt.Sprintf("%s.%d.%d.key", tagName, idx, i))
  251. params.Add(jsonutils.NewString(tag.Value), fmt.Sprintf("%s.%d.%d.value", tagName, idx, i))
  252. }
  253. return nil
  254. }
  255. func SplitTag(tag string) tagutils.TTagSet {
  256. tags := make(tagutils.TTagSet, 0)
  257. tagInfoList := strings.Split(tag, ";")
  258. for _, tagInfo := range tagInfoList {
  259. if len(tagInfo) == 0 {
  260. continue
  261. }
  262. tagInfo := strings.Split(tagInfo, "=")
  263. tag := tagutils.STag{Key: tagInfo[0]}
  264. if len(tagInfo) > 1 {
  265. tag.Value = tagInfo[1]
  266. }
  267. tags = append(tags, tag)
  268. }
  269. return tags
  270. }
  271. func (opts *BaseListOptions) Params() (*jsonutils.JSONDict, error) {
  272. params, err := optionsStructToParams(opts)
  273. if err != nil {
  274. return nil, err
  275. }
  276. if len(opts.Filter) == 0 {
  277. params.Remove("filter_any")
  278. }
  279. if BoolV(opts.DeleteAll) {
  280. params.Set("delete", jsonutils.NewString("all"))
  281. }
  282. if BoolV(opts.PendingDeleteAll) {
  283. params.Set("pending_delete", jsonutils.NewString("all"))
  284. }
  285. /*if opts.Admin == nil {
  286. requiresSystem := len(opts.Tenant) > 0 ||
  287. len(opts.ProjectDomain) > 0 ||
  288. BoolV(opts.System) ||
  289. BoolV(opts.PendingDelete) ||
  290. BoolV(opts.PendingDeleteAll)
  291. if requiresSystem {
  292. params.Set("admin", jsonutils.JSONTrue)
  293. }
  294. }*/
  295. tagIdx, noTagIdx := 0, 0
  296. for _, tag := range opts.Tags {
  297. err = opts.addTag("", tag, tagIdx, params)
  298. if err != nil {
  299. return nil, errors.Wrap(err, "Tags")
  300. }
  301. tagIdx++
  302. }
  303. for _, tag := range opts.NoTags {
  304. err = opts.addNoTag("", tag, noTagIdx, params)
  305. if err != nil {
  306. return nil, errors.Wrap(err, "NoTags")
  307. }
  308. noTagIdx++
  309. }
  310. for _, tag := range opts.UserTags {
  311. err = opts.addTag(dbapi.USER_TAG_PREFIX, tag, tagIdx, params)
  312. if err != nil {
  313. return nil, errors.Wrap(err, "UserTags")
  314. }
  315. tagIdx++
  316. }
  317. for _, tag := range opts.NoUserTags {
  318. err = opts.addNoTag(dbapi.USER_TAG_PREFIX, tag, noTagIdx, params)
  319. if err != nil {
  320. return nil, errors.Wrap(err, "NoUserTags")
  321. }
  322. noTagIdx++
  323. }
  324. for _, tag := range opts.CloudTags {
  325. err = opts.addTag(dbapi.CLOUD_TAG_PREFIX, tag, tagIdx, params)
  326. if err != nil {
  327. return nil, errors.Wrap(err, "CloudTags")
  328. }
  329. tagIdx++
  330. }
  331. for _, tag := range opts.NoCloudTags {
  332. err = opts.addNoTag(dbapi.CLOUD_TAG_PREFIX, tag, noTagIdx, params)
  333. if err != nil {
  334. return nil, errors.Wrap(err, "NoCloudTags")
  335. }
  336. noTagIdx++
  337. }
  338. projTagIdx := 0
  339. for _, tag := range opts.ProjectTags {
  340. err := opts.addTagInternal("", "project_tags", tag, projTagIdx, params)
  341. if err != nil {
  342. return nil, errors.Wrap(err, "ProjectTags")
  343. }
  344. projTagIdx++
  345. }
  346. noProjTagIdx := 0
  347. for _, tag := range opts.NoProjectTags {
  348. err := opts.addTagInternal("", "no_project_tags", tag, noProjTagIdx, params)
  349. if err != nil {
  350. return nil, errors.Wrap(err, "NoProjectTags")
  351. }
  352. noProjTagIdx++
  353. }
  354. for i, orgId := range opts.ProjectOrganizations {
  355. params.Add(jsonutils.NewString(orgId), fmt.Sprintf("project_organizations.%d", i))
  356. }
  357. domainTagIdx := 0
  358. for _, tag := range opts.DomainTags {
  359. err := opts.addTagInternal("", "domain_tags", tag, domainTagIdx, params)
  360. if err != nil {
  361. return nil, errors.Wrap(err, "DomainTags")
  362. }
  363. domainTagIdx++
  364. }
  365. noDomainTagIdx := 0
  366. for _, tag := range opts.NoDomainTags {
  367. err := opts.addTagInternal("", "no_domain_tags", tag, noDomainTagIdx, params)
  368. if err != nil {
  369. return nil, errors.Wrap(err, "NoDomainTags")
  370. }
  371. noDomainTagIdx++
  372. }
  373. for i, orgId := range opts.DomainOrganizations {
  374. params.Add(jsonutils.NewString(orgId), fmt.Sprintf("domain_organizations.%d", i))
  375. }
  376. return params, nil
  377. }
  378. func (o *BaseListOptions) GetExportKeys() string {
  379. return o.ExportKeys
  380. }
  381. type ExtraListOptions struct {
  382. ExportFile string `help:"Export to file" metavar:"<EXPORT_FILE_PATH>" json:"-"`
  383. ExportTexts string `help:"Export field displayname texts" json:"-"`
  384. }
  385. func (o ExtraListOptions) GetExportFile() string {
  386. return o.ExportFile
  387. }
  388. func (o ExtraListOptions) GetExportTexts() string {
  389. return o.ExportTexts
  390. }
  391. func (o ExtraListOptions) GetContextId() string {
  392. return ""
  393. }
  394. type ScopedResourceListOptions struct {
  395. BelongScope string `help:"Filter by resource belong scope" choices:"system|domain|project"`
  396. }
  397. func (o *ScopedResourceListOptions) Params() (*jsonutils.JSONDict, error) {
  398. return optionsStructToParams(o)
  399. }
  400. type MultiArchListOptions struct {
  401. OsArch string `help:"Filter resource by arch" choices:"i386|x86|x86_32|x86_64|arm|aarch32|aarch64|riscv|riscv32|riscv64"`
  402. }
  403. func (o *MultiArchListOptions) Params() (*jsonutils.JSONDict, error) {
  404. return optionsStructToParams(o)
  405. }
  406. type BaseUpdateOptions struct {
  407. ID string `help:"ID or Name of resource to update" json:"-"`
  408. Name string `help:"Name of resource to update" json:"name"`
  409. Desc string `metavar:"<DESCRIPTION>" help:"Description" json:"description"`
  410. }
  411. func (opts *BaseUpdateOptions) GetId() string {
  412. return opts.ID
  413. }
  414. func (opts *BaseUpdateOptions) Params() (jsonutils.JSONObject, error) {
  415. params := jsonutils.NewDict()
  416. if len(opts.Name) > 0 {
  417. params.Add(jsonutils.NewString(opts.Name), "name")
  418. }
  419. if len(opts.Desc) > 0 {
  420. params.Add(jsonutils.NewString(opts.Desc), "description")
  421. }
  422. return params, nil
  423. }
  424. type BasePublicOptions struct {
  425. ID string `help:"ID or name of resource" json:"-"`
  426. Scope string `help:"sharing scope" choices:"system|domain|project"`
  427. SharedDomains []string `help:"share to domains"`
  428. SharedProjects []string `help:"share to projects"`
  429. }
  430. func (opts *BasePublicOptions) GetId() string {
  431. return opts.ID
  432. }
  433. func (opts *BasePublicOptions) Params() (jsonutils.JSONObject, error) {
  434. params := jsonutils.Marshal(opts).(*jsonutils.JSONDict)
  435. params.Remove("id")
  436. return params, nil
  437. }
  438. type BaseCreateOptions struct {
  439. NAME string `json:"name" help:"Resource Name"`
  440. Desc string `metavar:"<DESCRIPTION>" help:"Description" json:"description"`
  441. }
  442. func (opts *BaseCreateOptions) Params() (jsonutils.JSONObject, error) {
  443. return jsonutils.Marshal(opts), nil
  444. }
  445. type EnabledStatusCreateOptions struct {
  446. BaseCreateOptions
  447. Status string
  448. Enabled *bool `help:"turn on enabled flag"`
  449. }
  450. type BaseIdOptions struct {
  451. ID string `json:"-"`
  452. }
  453. func (o *BaseIdOptions) GetId() string {
  454. return o.ID
  455. }
  456. func (o *BaseIdOptions) Params() (jsonutils.JSONObject, error) {
  457. return nil, nil
  458. }
  459. type BaseIdsOptions struct {
  460. ID []string `json:"-"`
  461. }
  462. func (o *BaseIdsOptions) GetIds() []string {
  463. return o.ID
  464. }
  465. func (o *BaseIdsOptions) Params() (jsonutils.JSONObject, error) {
  466. return nil, nil
  467. }
  468. type BaseShowOptions struct {
  469. BaseIdOptions
  470. WithMeta *bool `help:"With meta data"`
  471. ShowFailReason *bool `help:"show fail reason fields"`
  472. }
  473. func (o BaseShowOptions) Params() (jsonutils.JSONObject, error) {
  474. return StructToParams(o)
  475. }
  476. type ChangeOwnerOptions struct {
  477. BaseIdOptions
  478. ProjectDomain string `json:"project_domain" help:"target domain"`
  479. }
  480. func (o ChangeOwnerOptions) Params() (jsonutils.JSONObject, error) {
  481. if len(o.ProjectDomain) == 0 {
  482. return nil, fmt.Errorf("empty project_domain")
  483. }
  484. return jsonutils.Marshal(map[string]string{"project_domain": o.ProjectDomain}), nil
  485. }
  486. type StatusStatisticsOptions struct {
  487. }
  488. func (o StatusStatisticsOptions) Property() string {
  489. return "statistics"
  490. }
  491. type ProjectStatisticsOptions struct {
  492. }
  493. func (o ProjectStatisticsOptions) Property() string {
  494. return "project-statistics"
  495. }
  496. type DomainStatisticsOptions struct {
  497. }
  498. func (o DomainStatisticsOptions) Property() string {
  499. return "domain-statistics"
  500. }