From 1a247deec903033b770fd48ff6b1d0b906b2c952 Mon Sep 17 00:00:00 2001 From: Marvin Charles Date: Wed, 26 Aug 2026 18:29:34 +0300 Subject: [PATCH 01/16] feat: add global miner search --- .../fleetmanagement/v1/fleetmanagement_pb.ts | 302 ++--- .../components/MinerSearchInput.tsx | 44 + .../components/MinerSelectionList.test.tsx | 30 +- .../components/MinerSelectionList.tsx | 52 +- .../MinerActionsMenu/useMinerActions.tsx | 5 +- .../components/MinerList/MinerList.tsx | 30 +- .../utils/filterUrlParams.test.ts | 12 + .../fleetManagement/utils/filterUrlParams.ts | 8 + .../fleetManagement/views/savedViews.ts | 1 + .../fleetManagement/views/viewSummary.ts | 3 + .../src/shared/components/Search/Search.tsx | 20 +- .../fleetmanagement/v1/fleetmanagement.proto | 6 + .../fleetmanagement/v1/fleetmanagement.pb.go | 1068 +++++++++-------- .../fleetmanagement/parse_filter_test.go | 15 + .../domain/fleetmanagement/service.go | 8 + .../domain/stores/interfaces/device.go | 3 + .../domain/stores/sqlstores/device.go | 18 +- .../domain/stores/sqlstores/device_filters.go | 26 + .../device_filters_integration_test.go | 52 + .../stores/sqlstores/device_filters_test.go | 27 + 20 files changed, 982 insertions(+), 748 deletions(-) create mode 100644 client/src/protoFleet/components/MinerSearchInput.tsx diff --git a/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts b/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts index 84a60e0e73..acc217a1dc 100644 --- a/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts +++ b/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts @@ -30,25 +30,8 @@ import type { Message } from "@bufbuild/protobuf"; /** * Describes the file fleetmanagement/v1/fleetmanagement.proto. */ -export const file_fleetmanagement_v1_fleetmanagement: GenFile = - /*@__PURE__*/ - fileDesc( - "CihmbGVldG1hbmFnZW1lbnQvdjEvZmxlZXRtYW5hZ2VtZW50LnByb3RvEhJmbGVldG1hbmFnZW1lbnQudjEi0AcKEk1pbmVyU3RhdGVTbmFwc2hvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC21hY19hZGRyZXNzGAMgASgJEhUKDXNlcmlhbF9udW1iZXIYBCABKAkSKwoLcG93ZXJfdXNhZ2UYBSADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKwoLdGVtcGVyYXR1cmUYBiADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKAoIaGFzaHJhdGUYByADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKgoKZWZmaWNpZW5jeRgIIAMoCzIWLmNvbW1vbi52MS5NZWFzdXJlbWVudBItCgl0aW1lc3RhbXAYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmlwX2FkZHJlc3MYCiABKAkSCwoDdXJsGAsgASgJEjcKDWRldmljZV9zdGF0dXMYDCABKA4yIC5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU3RhdHVzEjkKDnBhaXJpbmdfc3RhdHVzGA0gASgOMiEuZmxlZXRtYW5hZ2VtZW50LnYxLlBhaXJpbmdTdGF0dXMSDQoFbW9kZWwYDiABKAkSFAoMbWFudWZhY3R1cmVyGA8gASgJEjgKDGNhcGFiaWxpdGllcxgRIAEoCzIiLmNhcGFiaWxpdGllcy52MS5NaW5lckNhcGFiaWxpdGllcxI7ChJ0ZW1wZXJhdHVyZV9zdGF0dXMYEiABKA4yHy50ZWxlbWV0cnkudjEuVGVtcGVyYXR1cmVTdGF0dXMSGAoQZmlybXdhcmVfdmVyc2lvbhgTIAEoCRITCgtkcml2ZXJfbmFtZRgWIAEoCRITCgt3b3JrZXJfbmFtZRgXIAEoCRIVCg1yYWNrX3Bvc2l0aW9uGBggASgJEisKCXBsYWNlbWVudBgcIAEoCzIYLmNvbW1vbi52MS5QbGFjZW1lbnRSZWZzEiMKG2VtYmVkZGVkX3dlYl92aWV3X2F2YWlsYWJsZRgdIAEoCBI/Cg5vZmZsaW5lX3JlYXNvbhgeIAEoDjInLmZsZWV0bWFuYWdlbWVudC52MS5EZXZpY2VPZmZsaW5lUmVhc29uSgQIEBARSgQIFBAVSgQIFRAWSgQIGRAaSgQIGhAbSgQIGxAcUgR0eXBlUgxncm91cF9sYWJlbHNSCnJhY2tfbGFiZWxSB3NpdGVfaWRSCnNpdGVfbGFiZWxSDmJ1aWxkaW5nX2xhYmVsIqkBCh5MaXN0TWluZXJTdGF0ZVNuYXBzaG90c1JlcXVlc3QSHQoJcGFnZV9zaXplGAEgASgFQgq6SAcaBRjoBygAEg4KBmN1cnNvchgCIAEoCRIzCgZmaWx0ZXIYAyABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJMaXN0RmlsdGVyEiMKBHNvcnQYBCADKAsyFS5jb21tb24udjEuU29ydENvbmZpZyLGBAoPTWluZXJMaXN0RmlsdGVyEjcKDWRldmljZV9zdGF0dXMYAyADKA4yIC5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU3RhdHVzEjcKFWVycm9yX2NvbXBvbmVudF90eXBlcxgEIAMoDjIYLmVycm9ycy52MS5Db21wb25lbnRUeXBlEg4KBm1vZGVscxgFIAMoCRI7ChBwYWlyaW5nX3N0YXR1c2VzGAYgAygOMiEuZmxlZXRtYW5hZ2VtZW50LnYxLlBhaXJpbmdTdGF0dXMSEQoJZ3JvdXBfaWRzGAcgAygDEhAKCHJhY2tfaWRzGAggAygDEhkKEWZpcm13YXJlX3ZlcnNpb25zGAkgAygJEhEKBXpvbmVzGAogAygJQgIYARI+Cg5udW1lcmljX3JhbmdlcxgLIAMoCzImLmZsZWV0bWFuYWdlbWVudC52MS5OdW1lcmljUmFuZ2VGaWx0ZXISEAoIaXBfY2lkcnMYDCADKAkSEAoIc2l0ZV9pZHMYDSADKAMSGgoSaW5jbHVkZV91bmFzc2lnbmVkGA4gASgIEhQKDGJ1aWxkaW5nX2lkcxgPIAMoAxIbChNpbmNsdWRlX25vX2J1aWxkaW5nGBAgASgIEiUKCXpvbmVfa2V5cxgRIAMoCzISLmNvbW1vbi52MS5ab25lS2V5EhcKD2luY2x1ZGVfbm9fcmFjaxgSIAEoCBIuCglpcF9yYW5nZXMYEyADKAsyGy5mbGVldG1hbmFnZW1lbnQudjEuSXBSYW5nZSIrCgdJcFJhbmdlEhAKCHN0YXJ0X2lwGAEgASgJEg4KBmVuZF9pcBgCIAEoCSLJAQoSTnVtZXJpY1JhbmdlRmlsdGVyEi8KBWZpZWxkGAEgASgOMiAuZmxlZXRtYW5hZ2VtZW50LnYxLk51bWVyaWNGaWVsZBIpCgNtaW4YAiABKAsyHC5nb29nbGUucHJvdG9idWYuRG91YmxlVmFsdWUSKQoDbWF4GAMgASgLMhwuZ29vZ2xlLnByb3RvYnVmLkRvdWJsZVZhbHVlEhUKDW1pbl9pbmNsdXNpdmUYBCABKAgSFQoNbWF4X2luY2x1c2l2ZRgFIAEoCCLmAQofTGlzdE1pbmVyU3RhdGVTbmFwc2hvdHNSZXNwb25zZRI2CgZtaW5lcnMYASADKAsyJi5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJTdGF0ZVNuYXBzaG90Eg4KBmN1cnNvchgCIAEoCRIUCgx0b3RhbF9taW5lcnMYAyABKAUSOgoSdG90YWxfc3RhdGVfY291bnRzGAQgASgLMh4udGVsZW1ldHJ5LnYxLk1pbmVyU3RhdGVDb3VudHMSDgoGbW9kZWxzGAUgAygJEhkKEWZpcm13YXJlX3ZlcnNpb25zGAYgAygJIioKFFJlZnJlc2hNaW5lcnNSZXF1ZXN0EhIKCmRldmljZV9pZHMYASADKAkiyAEKFVJlZnJlc2hNaW5lcnNSZXNwb25zZRI5CglzbmFwc2hvdHMYASADKAsyJi5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJTdGF0ZVNuYXBzaG90EkUKBmVycm9ycxgCIAMoCzI1LmZsZWV0bWFuYWdlbWVudC52MS5SZWZyZXNoTWluZXJzUmVzcG9uc2UuRXJyb3JzRW50cnkaLQoLRXJyb3JzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKPAQoeTG9va3VwTWluZXJCeUlkZW50aWZpZXJSZXF1ZXN0EiEKCmlkZW50aWZpZXIYASABKAlCDbpICsgBAXIFEAEY/wESSgoPaWRlbnRpZmllcl90eXBlGAIgASgOMicuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVySWRlbnRpZmllclR5cGVCCLpIBYIBAhABIlsKH0xvb2t1cE1pbmVyQnlJZGVudGlmaWVyUmVzcG9uc2USOAoIc25hcHNob3QYASABKAsyJi5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJTdGF0ZVNuYXBzaG90IpIBChlFeHBvcnRNaW5lckxpc3RDc3ZSZXF1ZXN0EjMKBmZpbHRlchgBIAEoCzIjLmZsZWV0bWFuYWdlbWVudC52MS5NaW5lckxpc3RGaWx0ZXISQAoQdGVtcGVyYXR1cmVfdW5pdBgCIAEoDjImLmZsZWV0bWFuYWdlbWVudC52MS5Dc3ZUZW1wZXJhdHVyZVVuaXQiLgoaRXhwb3J0TWluZXJMaXN0Q3N2UmVzcG9uc2USEAoIY3N2X2RhdGEYASABKAwiSgoaR2V0TWluZXJTdGF0ZUNvdW50c1JlcXVlc3QSEAoIc2l0ZV9pZHMYASADKAMSGgoSaW5jbHVkZV91bmFzc2lnbmVkGAIgASgIImkKG0dldE1pbmVyU3RhdGVDb3VudHNSZXNwb25zZRIUCgx0b3RhbF9taW5lcnMYASABKAUSNAoMc3RhdGVfY291bnRzGAIgASgLMh4udGVsZW1ldHJ5LnYxLk1pbmVyU3RhdGVDb3VudHMiQwoeR2V0TWluZXJQb29sQXNzaWdubWVudHNSZXF1ZXN0EiEKEWRldmljZV9pZGVudGlmaWVyGAEgASgJQga6SAPIAQEiUQoOUG9vbEFzc2lnbm1lbnQSFAoHcG9vbF9pZBgBIAEoA0gAiAEBEgsKA3VybBgCIAEoCRIQCgh1c2VybmFtZRgDIAEoCUIKCghfcG9vbF9pZCJUCh9HZXRNaW5lclBvb2xBc3NpZ25tZW50c1Jlc3BvbnNlEjEKBXBvb2xzGAEgAygLMiIuZmxlZXRtYW5hZ2VtZW50LnYxLlBvb2xBc3NpZ25tZW50IkUKD01pbmVyTW9kZWxHcm91cBINCgVtb2RlbBgBIAEoCRIUCgxtYW51ZmFjdHVyZXIYAiABKAkSDQoFY291bnQYAyABKAUiUQoaR2V0TWluZXJNb2RlbEdyb3Vwc1JlcXVlc3QSMwoGZmlsdGVyGAEgASgLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVyTGlzdEZpbHRlciJSChtHZXRNaW5lck1vZGVsR3JvdXBzUmVzcG9uc2USMwoGZ3JvdXBzGAEgAygLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVyTW9kZWxHcm91cCI/ChpHZXRNaW5lckNvb2xpbmdNb2RlUmVxdWVzdBIhChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCUIGukgDyAEBIksKG0dldE1pbmVyQ29vbGluZ01vZGVSZXNwb25zZRIsCgxjb29saW5nX21vZGUYASABKA4yFi5jb21tb24udjEuQ29vbGluZ01vZGUimgEKDkRldmljZVNlbGVjdG9yEjoKC2FsbF9kZXZpY2VzGAEgASgLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVyTGlzdEZpbHRlckgAEjoKD2luY2x1ZGVfZGV2aWNlcxgCIAEoCzIfLmNvbW1vbi52MS5EZXZpY2VJZGVudGlmaWVyTGlzdEgAQhAKDnNlbGVjdGlvbl90eXBlIlIKE0RlbGV0ZU1pbmVyc1JlcXVlc3QSOwoPZGV2aWNlX3NlbGVjdG9yGAEgASgLMiIuZmxlZXRtYW5hZ2VtZW50LnYxLkRldmljZVNlbGVjdG9yIi0KFERlbGV0ZU1pbmVyc1Jlc3BvbnNlEhUKDWRlbGV0ZWRfY291bnQYASABKAUiwQEKE1JlbmFtZU1pbmVyc1JlcXVlc3QSQwoPZGV2aWNlX3NlbGVjdG9yGAEgASgLMiIuZmxlZXRtYW5hZ2VtZW50LnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESQAoLbmFtZV9jb25maWcYAiABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJOYW1lQ29uZmlnQga6SAPIAQESIwoEc29ydBgDIAMoCzIVLmNvbW1vbi52MS5Tb3J0Q29uZmlnIlwKFFJlbmFtZU1pbmVyc1Jlc3BvbnNlEhUKDXJlbmFtZWRfY291bnQYASABKAUSFwoPdW5jaGFuZ2VkX2NvdW50GAIgASgFEhQKDGZhaWxlZF9jb3VudBgDIAEoBSKGAgoYVXBkYXRlV29ya2VyTmFtZXNSZXF1ZXN0EkMKD2RldmljZV9zZWxlY3RvchgBIAEoCzIiLmZsZWV0bWFuYWdlbWVudC52MS5EZXZpY2VTZWxlY3RvckIGukgDyAEBEkAKC25hbWVfY29uZmlnGAIgASgLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVyTmFtZUNvbmZpZ0IGukgDyAEBEiMKBHNvcnQYAyADKAsyFS5jb21tb24udjEuU29ydENvbmZpZxIeCg11c2VyX3VzZXJuYW1lGAQgASgJQge6SARyAhABEh4KDXVzZXJfcGFzc3dvcmQYBSABKAlCB7pIBHICEAEiewoZVXBkYXRlV29ya2VyTmFtZXNSZXNwb25zZRIVCg11cGRhdGVkX2NvdW50GAEgASgFEhcKD3VuY2hhbmdlZF9jb3VudBgCIAEoBRIUCgxmYWlsZWRfY291bnQYAyABKAUSGAoQYmF0Y2hfaWRlbnRpZmllchgEIAEoCSJ2Cg9NaW5lck5hbWVDb25maWcSPgoKcHJvcGVydGllcxgBIAMoCzIgLmZsZWV0bWFuYWdlbWVudC52MS5OYW1lUHJvcGVydHlCCLpIBZIBAggBEiMKCXNlcGFyYXRvchgCIAEoCUIQukgNcgtSAS1SAV9SAS5SACLRAgoMTmFtZVByb3BlcnR5EkoKEnN0cmluZ19hbmRfY291bnRlchgBIAEoCzIsLmZsZWV0bWFuYWdlbWVudC52MS5TdHJpbmdBbmRDb3VudGVyUHJvcGVydHlIABI2Cgdjb3VudGVyGAIgASgLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLkNvdW50ZXJQcm9wZXJ0eUgAEjoKDHN0cmluZ192YWx1ZRgDIAEoCzIiLmZsZWV0bWFuYWdlbWVudC52MS5TdHJpbmdQcm9wZXJ0eUgAEj0KC2ZpeGVkX3ZhbHVlGAQgASgLMiYuZmxlZXRtYW5hZ2VtZW50LnYxLkZpeGVkVmFsdWVQcm9wZXJ0eUgAEjoKCXF1YWxpZmllchgFIAEoCzIlLmZsZWV0bWFuYWdlbWVudC52MS5RdWFsaWZpZXJQcm9wZXJ0eUgAQgYKBGtpbmQifAoYU3RyaW5nQW5kQ291bnRlclByb3BlcnR5Eg4KBnByZWZpeBgBIAEoCRIOCgZzdWZmaXgYAiABKAkSHgoNY291bnRlcl9zdGFydBgDIAEoBUIHukgEGgIoABIgCg1jb3VudGVyX3NjYWxlGAQgASgFQgm6SAYaBBgGKAEiUwoPQ291bnRlclByb3BlcnR5Eh4KDWNvdW50ZXJfc3RhcnQYASABKAVCB7pIBBoCKAASIAoNY291bnRlcl9zY2FsZRgCIAEoBUIJukgGGgQYBigBIigKDlN0cmluZ1Byb3BlcnR5EhYKBXZhbHVlGAEgASgJQge6SARyAhABIvICChJGaXhlZFZhbHVlUHJvcGVydHkSOgoEdHlwZRgBIAEoDjIiLmZsZWV0bWFuYWdlbWVudC52MS5GaXhlZFZhbHVlVHlwZUIIukgFggECEAESJwoPY2hhcmFjdGVyX2NvdW50GAIgASgFQgm6SAYaBBgGKAFIAIgBARJECgdzZWN0aW9uGAMgASgOMiQuZmxlZXRtYW5hZ2VtZW50LnYxLkNoYXJhY3RlclNlY3Rpb25CCLpIBYIBAhABSAGIAQE6kAG6SIwBGokBCiVzZWN0aW9uX3JlcXVpcmVkX3dpdGhfY2hhcmFjdGVyX2NvdW50Ei9zZWN0aW9uIGlzIHJlcXVpcmVkIHdoZW4gY2hhcmFjdGVyX2NvdW50IGlzIHNldBovIWhhcyh0aGlzLmNoYXJhY3Rlcl9jb3VudCkgfHwgaGFzKHRoaXMuc2VjdGlvbilCEgoQX2NoYXJhY3Rlcl9jb3VudEIKCghfc2VjdGlvbiJuChFRdWFsaWZpZXJQcm9wZXJ0eRI5CgR0eXBlGAEgASgOMiEuZmxlZXRtYW5hZ2VtZW50LnYxLlF1YWxpZmllclR5cGVCCLpIBYIBAhABEg4KBnByZWZpeBgCIAEoCRIOCgZzdWZmaXgYAyABKAkqmQEKH0ZsZWV0TWFuYWdlbWVudFNlcnZpY2VFcnJvckNvZGUSMwovRkxFRVRfTUFOQUdFTUVOVF9TRVJWSUNFX0VSUk9SX0NPREVfVU5TUEVDSUZJRUQQABJBCj1GTEVFVF9NQU5BR0VNRU5UX1NFUlZJQ0VfRVJST1JfQ09ERV9JTlZBTElEX1BBR0lOQVRJT05fQ1VSU09SEAEqmgIKDERldmljZVN0YXR1cxIdChlERVZJQ0VfU1RBVFVTX1VOU1BFQ0lGSUVEEAASGAoUREVWSUNFX1NUQVRVU19PTkxJTkUQARIZChVERVZJQ0VfU1RBVFVTX09GRkxJTkUQAhIdChlERVZJQ0VfU1RBVFVTX01BSU5URU5BTkNFEAMSFwoTREVWSUNFX1NUQVRVU19FUlJPUhAEEhoKFkRFVklDRV9TVEFUVVNfSU5BQ1RJVkUQBRIjCh9ERVZJQ0VfU1RBVFVTX05FRURTX01JTklOR19QT09MEAYSGgoWREVWSUNFX1NUQVRVU19VUERBVElORxAHEiEKHURFVklDRV9TVEFUVVNfUkVCT09UX1JFUVVJUkVEEAgqbgoTRGV2aWNlT2ZmbGluZVJlYXNvbhIlCiFERVZJQ0VfT0ZGTElORV9SRUFTT05fVU5TUEVDSUZJRUQQABIwCixERVZJQ0VfT0ZGTElORV9SRUFTT05fRkxFRVRfTk9ERV9VTkFWQUlMQUJMRRABKu0BCg1QYWlyaW5nU3RhdHVzEh4KGlBBSVJJTkdfU1RBVFVTX1VOU1BFQ0lGSUVEEAASGQoVUEFJUklOR19TVEFUVVNfUEFJUkVEEAESGwoXUEFJUklOR19TVEFUVVNfVU5QQUlSRUQQAhIoCiRQQUlSSU5HX1NUQVRVU19BVVRIRU5USUNBVElPTl9ORUVERUQQAxIaChZQQUlSSU5HX1NUQVRVU19QRU5ESU5HEAQSGQoVUEFJUklOR19TVEFUVVNfRkFJTEVEEAUSIwofUEFJUklOR19TVEFUVVNfREVGQVVMVF9QQVNTV09SRBAGKuYBCgxOdW1lcmljRmllbGQSHQoZTlVNRVJJQ19GSUVMRF9VTlNQRUNJRklFRBAAEh4KGk5VTUVSSUNfRklFTERfSEFTSFJBVEVfVEhTEAESIAocTlVNRVJJQ19GSUVMRF9FRkZJQ0lFTkNZX0pUSBACEhoKFk5VTUVSSUNfRklFTERfUE9XRVJfS1cQAxIfChtOVU1FUklDX0ZJRUxEX1RFTVBFUkFUVVJFX0MQBBIbChdOVU1FUklDX0ZJRUxEX1ZPTFRBR0VfVhAFEhsKF05VTUVSSUNfRklFTERfQ1VSUkVOVF9BEAYqjAEKE01pbmVySWRlbnRpZmllclR5cGUSJQohTUlORVJfSURFTlRJRklFUl9UWVBFX1VOU1BFQ0lGSUVEEAASJQohTUlORVJfSURFTlRJRklFUl9UWVBFX01BQ19BRERSRVNTEAESJwojTUlORVJfSURFTlRJRklFUl9UWVBFX1NFUklBTF9OVU1CRVIQAiqBAQoSQ3N2VGVtcGVyYXR1cmVVbml0EiQKIENTVl9URU1QRVJBVFVSRV9VTklUX1VOU1BFQ0lGSUVEEAASIAocQ1NWX1RFTVBFUkFUVVJFX1VOSVRfQ0VMU0lVUxABEiMKH0NTVl9URU1QRVJBVFVSRV9VTklUX0ZBSFJFTkhFSVQQAiqZAgoORml4ZWRWYWx1ZVR5cGUSIAocRklYRURfVkFMVUVfVFlQRV9VTlNQRUNJRklFRBAAEiAKHEZJWEVEX1ZBTFVFX1RZUEVfTUFDX0FERFJFU1MQARIiCh5GSVhFRF9WQUxVRV9UWVBFX1NFUklBTF9OVU1CRVIQAhIgChxGSVhFRF9WQUxVRV9UWVBFX1dPUktFUl9OQU1FEAMSGgoWRklYRURfVkFMVUVfVFlQRV9NT0RFTBAEEiEKHUZJWEVEX1ZBTFVFX1RZUEVfTUFOVUZBQ1RVUkVSEAUSHQoZRklYRURfVkFMVUVfVFlQRV9MT0NBVElPThAGEh8KG0ZJWEVEX1ZBTFVFX1RZUEVfTUlORVJfTkFNRRAHKm4KEENoYXJhY3RlclNlY3Rpb24SIQodQ0hBUkFDVEVSX1NFQ1RJT05fVU5TUEVDSUZJRUQQABIbChdDSEFSQUNURVJfU0VDVElPTl9GSVJTVBABEhoKFkNIQVJBQ1RFUl9TRUNUSU9OX0xBU1QQAiqHAQoNUXVhbGlmaWVyVHlwZRIeChpRVUFMSUZJRVJfVFlQRV9VTlNQRUNJRklFRBAAEhsKF1FVQUxJRklFUl9UWVBFX0JVSUxESU5HEAESFwoTUVVBTElGSUVSX1RZUEVfUkFDSxACEiAKHFFVQUxJRklFUl9UWVBFX1JBQ0tfUE9TSVRJT04QAzKkCgoWRmxlZXRNYW5hZ2VtZW50U2VydmljZRKCAQoXTGlzdE1pbmVyU3RhdGVTbmFwc2hvdHMSMi5mbGVldG1hbmFnZW1lbnQudjEuTGlzdE1pbmVyU3RhdGVTbmFwc2hvdHNSZXF1ZXN0GjMuZmxlZXRtYW5hZ2VtZW50LnYxLkxpc3RNaW5lclN0YXRlU25hcHNob3RzUmVzcG9uc2USZAoNUmVmcmVzaE1pbmVycxIoLmZsZWV0bWFuYWdlbWVudC52MS5SZWZyZXNoTWluZXJzUmVxdWVzdBopLmZsZWV0bWFuYWdlbWVudC52MS5SZWZyZXNoTWluZXJzUmVzcG9uc2USggEKF0xvb2t1cE1pbmVyQnlJZGVudGlmaWVyEjIuZmxlZXRtYW5hZ2VtZW50LnYxLkxvb2t1cE1pbmVyQnlJZGVudGlmaWVyUmVxdWVzdBozLmZsZWV0bWFuYWdlbWVudC52MS5Mb29rdXBNaW5lckJ5SWRlbnRpZmllclJlc3BvbnNlEnUKEkV4cG9ydE1pbmVyTGlzdENzdhItLmZsZWV0bWFuYWdlbWVudC52MS5FeHBvcnRNaW5lckxpc3RDc3ZSZXF1ZXN0Gi4uZmxlZXRtYW5hZ2VtZW50LnYxLkV4cG9ydE1pbmVyTGlzdENzdlJlc3BvbnNlMAESdgoTR2V0TWluZXJTdGF0ZUNvdW50cxIuLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lclN0YXRlQ291bnRzUmVxdWVzdBovLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lclN0YXRlQ291bnRzUmVzcG9uc2USggEKF0dldE1pbmVyUG9vbEFzc2lnbm1lbnRzEjIuZmxlZXRtYW5hZ2VtZW50LnYxLkdldE1pbmVyUG9vbEFzc2lnbm1lbnRzUmVxdWVzdBozLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lclBvb2xBc3NpZ25tZW50c1Jlc3BvbnNlEnYKE0dldE1pbmVyQ29vbGluZ01vZGUSLi5mbGVldG1hbmFnZW1lbnQudjEuR2V0TWluZXJDb29saW5nTW9kZVJlcXVlc3QaLy5mbGVldG1hbmFnZW1lbnQudjEuR2V0TWluZXJDb29saW5nTW9kZVJlc3BvbnNlEmEKDERlbGV0ZU1pbmVycxInLmZsZWV0bWFuYWdlbWVudC52MS5EZWxldGVNaW5lcnNSZXF1ZXN0GiguZmxlZXRtYW5hZ2VtZW50LnYxLkRlbGV0ZU1pbmVyc1Jlc3BvbnNlEnYKE0dldE1pbmVyTW9kZWxHcm91cHMSLi5mbGVldG1hbmFnZW1lbnQudjEuR2V0TWluZXJNb2RlbEdyb3Vwc1JlcXVlc3QaLy5mbGVldG1hbmFnZW1lbnQudjEuR2V0TWluZXJNb2RlbEdyb3Vwc1Jlc3BvbnNlEmEKDFJlbmFtZU1pbmVycxInLmZsZWV0bWFuYWdlbWVudC52MS5SZW5hbWVNaW5lcnNSZXF1ZXN0GiguZmxlZXRtYW5hZ2VtZW50LnYxLlJlbmFtZU1pbmVyc1Jlc3BvbnNlEnAKEVVwZGF0ZVdvcmtlck5hbWVzEiwuZmxlZXRtYW5hZ2VtZW50LnYxLlVwZGF0ZVdvcmtlck5hbWVzUmVxdWVzdBotLmZsZWV0bWFuYWdlbWVudC52MS5VcGRhdGVXb3JrZXJOYW1lc1Jlc3BvbnNlQvABChZjb20uZmxlZXRtYW5hZ2VtZW50LnYxQhRGbGVldG1hbmFnZW1lbnRQcm90b1ABWldnaXRodWIuY29tL2Jsb2NrL3Byb3RvLWZsZWV0L3NlcnZlci9nZW5lcmF0ZWQvZ3JwYy9mbGVldG1hbmFnZW1lbnQvdjE7ZmxlZXRtYW5hZ2VtZW50djGiAgNGWFiqAhJGbGVldG1hbmFnZW1lbnQuVjHKAhJGbGVldG1hbmFnZW1lbnRcVjHiAh5GbGVldG1hbmFnZW1lbnRcVjFcR1BCTWV0YWRhdGHqAhNGbGVldG1hbmFnZW1lbnQ6OlYxYgZwcm90bzM", - [ - file_google_protobuf_timestamp, - file_google_protobuf_wrappers, - file_common_v1_measurement, - file_common_v1_cooling, - file_telemetry_v1_telemetry, - file_capabilities_v1_capabilities, - file_errors_v1_errors, - file_common_v1_device_selector, - file_common_v1_common, - file_common_v1_sort, - file_common_v1_zone, - file_buf_validate_validate, - ], - ); +export const file_fleetmanagement_v1_fleetmanagement: GenFile = /*@__PURE__*/ + fileDesc("CihmbGVldG1hbmFnZW1lbnQvdjEvZmxlZXRtYW5hZ2VtZW50LnByb3RvEhJmbGVldG1hbmFnZW1lbnQudjEi0AcKEk1pbmVyU3RhdGVTbmFwc2hvdBIZChFkZXZpY2VfaWRlbnRpZmllchgBIAEoCRIMCgRuYW1lGAIgASgJEhMKC21hY19hZGRyZXNzGAMgASgJEhUKDXNlcmlhbF9udW1iZXIYBCABKAkSKwoLcG93ZXJfdXNhZ2UYBSADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKwoLdGVtcGVyYXR1cmUYBiADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKAoIaGFzaHJhdGUYByADKAsyFi5jb21tb24udjEuTWVhc3VyZW1lbnQSKgoKZWZmaWNpZW5jeRgIIAMoCzIWLmNvbW1vbi52MS5NZWFzdXJlbWVudBItCgl0aW1lc3RhbXAYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKCmlwX2FkZHJlc3MYCiABKAkSCwoDdXJsGAsgASgJEjcKDWRldmljZV9zdGF0dXMYDCABKA4yIC5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU3RhdHVzEjkKDnBhaXJpbmdfc3RhdHVzGA0gASgOMiEuZmxlZXRtYW5hZ2VtZW50LnYxLlBhaXJpbmdTdGF0dXMSDQoFbW9kZWwYDiABKAkSFAoMbWFudWZhY3R1cmVyGA8gASgJEjgKDGNhcGFiaWxpdGllcxgRIAEoCzIiLmNhcGFiaWxpdGllcy52MS5NaW5lckNhcGFiaWxpdGllcxI7ChJ0ZW1wZXJhdHVyZV9zdGF0dXMYEiABKA4yHy50ZWxlbWV0cnkudjEuVGVtcGVyYXR1cmVTdGF0dXMSGAoQZmlybXdhcmVfdmVyc2lvbhgTIAEoCRITCgtkcml2ZXJfbmFtZRgWIAEoCRITCgt3b3JrZXJfbmFtZRgXIAEoCRIVCg1yYWNrX3Bvc2l0aW9uGBggASgJEisKCXBsYWNlbWVudBgcIAEoCzIYLmNvbW1vbi52MS5QbGFjZW1lbnRSZWZzEiMKG2VtYmVkZGVkX3dlYl92aWV3X2F2YWlsYWJsZRgdIAEoCBI/Cg5vZmZsaW5lX3JlYXNvbhgeIAEoDjInLmZsZWV0bWFuYWdlbWVudC52MS5EZXZpY2VPZmZsaW5lUmVhc29uSgQIEBARSgQIFBAVSgQIFRAWSgQIGRAaSgQIGhAbSgQIGxAcUgR0eXBlUgxncm91cF9sYWJlbHNSCnJhY2tfbGFiZWxSB3NpdGVfaWRSCnNpdGVfbGFiZWxSDmJ1aWxkaW5nX2xhYmVsIqkBCh5MaXN0TWluZXJTdGF0ZVNuYXBzaG90c1JlcXVlc3QSHQoJcGFnZV9zaXplGAEgASgFQgq6SAcaBRjoBygAEg4KBmN1cnNvchgCIAEoCRIzCgZmaWx0ZXIYAyABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJMaXN0RmlsdGVyEiMKBHNvcnQYBCADKAsyFS5jb21tb24udjEuU29ydENvbmZpZyLmBAoPTWluZXJMaXN0RmlsdGVyEjcKDWRldmljZV9zdGF0dXMYAyADKA4yIC5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU3RhdHVzEjcKFWVycm9yX2NvbXBvbmVudF90eXBlcxgEIAMoDjIYLmVycm9ycy52MS5Db21wb25lbnRUeXBlEg4KBm1vZGVscxgFIAMoCRI7ChBwYWlyaW5nX3N0YXR1c2VzGAYgAygOMiEuZmxlZXRtYW5hZ2VtZW50LnYxLlBhaXJpbmdTdGF0dXMSEQoJZ3JvdXBfaWRzGAcgAygDEhAKCHJhY2tfaWRzGAggAygDEhkKEWZpcm13YXJlX3ZlcnNpb25zGAkgAygJEhEKBXpvbmVzGAogAygJQgIYARI+Cg5udW1lcmljX3JhbmdlcxgLIAMoCzImLmZsZWV0bWFuYWdlbWVudC52MS5OdW1lcmljUmFuZ2VGaWx0ZXISEAoIaXBfY2lkcnMYDCADKAkSEAoIc2l0ZV9pZHMYDSADKAMSGgoSaW5jbHVkZV91bmFzc2lnbmVkGA4gASgIEhQKDGJ1aWxkaW5nX2lkcxgPIAMoAxIbChNpbmNsdWRlX25vX2J1aWxkaW5nGBAgASgIEiUKCXpvbmVfa2V5cxgRIAMoCzISLmNvbW1vbi52MS5ab25lS2V5EhcKD2luY2x1ZGVfbm9fcmFjaxgSIAEoCBIuCglpcF9yYW5nZXMYEyADKAsyGy5mbGVldG1hbmFnZW1lbnQudjEuSXBSYW5nZRIeCgxzZWFyY2hfcXVlcnkYFCABKAlCCLpIBXIDGP8BIisKB0lwUmFuZ2USEAoIc3RhcnRfaXAYASABKAkSDgoGZW5kX2lwGAIgASgJIskBChJOdW1lcmljUmFuZ2VGaWx0ZXISLwoFZmllbGQYASABKA4yIC5mbGVldG1hbmFnZW1lbnQudjEuTnVtZXJpY0ZpZWxkEikKA21pbhgCIAEoCzIcLmdvb2dsZS5wcm90b2J1Zi5Eb3VibGVWYWx1ZRIpCgNtYXgYAyABKAsyHC5nb29nbGUucHJvdG9idWYuRG91YmxlVmFsdWUSFQoNbWluX2luY2x1c2l2ZRgEIAEoCBIVCg1tYXhfaW5jbHVzaXZlGAUgASgIIuYBCh9MaXN0TWluZXJTdGF0ZVNuYXBzaG90c1Jlc3BvbnNlEjYKBm1pbmVycxgBIAMoCzImLmZsZWV0bWFuYWdlbWVudC52MS5NaW5lclN0YXRlU25hcHNob3QSDgoGY3Vyc29yGAIgASgJEhQKDHRvdGFsX21pbmVycxgDIAEoBRI6ChJ0b3RhbF9zdGF0ZV9jb3VudHMYBCABKAsyHi50ZWxlbWV0cnkudjEuTWluZXJTdGF0ZUNvdW50cxIOCgZtb2RlbHMYBSADKAkSGQoRZmlybXdhcmVfdmVyc2lvbnMYBiADKAkiKgoUUmVmcmVzaE1pbmVyc1JlcXVlc3QSEgoKZGV2aWNlX2lkcxgBIAMoCSLIAQoVUmVmcmVzaE1pbmVyc1Jlc3BvbnNlEjkKCXNuYXBzaG90cxgBIAMoCzImLmZsZWV0bWFuYWdlbWVudC52MS5NaW5lclN0YXRlU25hcHNob3QSRQoGZXJyb3JzGAIgAygLMjUuZmxlZXRtYW5hZ2VtZW50LnYxLlJlZnJlc2hNaW5lcnNSZXNwb25zZS5FcnJvcnNFbnRyeRotCgtFcnJvcnNFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIo8BCh5Mb29rdXBNaW5lckJ5SWRlbnRpZmllclJlcXVlc3QSIQoKaWRlbnRpZmllchgBIAEoCUINukgKyAEBcgUQARj/ARJKCg9pZGVudGlmaWVyX3R5cGUYAiABKA4yJy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJJZGVudGlmaWVyVHlwZUIIukgFggECEAEiWwofTG9va3VwTWluZXJCeUlkZW50aWZpZXJSZXNwb25zZRI4CghzbmFwc2hvdBgBIAEoCzImLmZsZWV0bWFuYWdlbWVudC52MS5NaW5lclN0YXRlU25hcHNob3QikgEKGUV4cG9ydE1pbmVyTGlzdENzdlJlcXVlc3QSMwoGZmlsdGVyGAEgASgLMiMuZmxlZXRtYW5hZ2VtZW50LnYxLk1pbmVyTGlzdEZpbHRlchJAChB0ZW1wZXJhdHVyZV91bml0GAIgASgOMiYuZmxlZXRtYW5hZ2VtZW50LnYxLkNzdlRlbXBlcmF0dXJlVW5pdCIuChpFeHBvcnRNaW5lckxpc3RDc3ZSZXNwb25zZRIQCghjc3ZfZGF0YRgBIAEoDCJKChpHZXRNaW5lclN0YXRlQ291bnRzUmVxdWVzdBIQCghzaXRlX2lkcxgBIAMoAxIaChJpbmNsdWRlX3VuYXNzaWduZWQYAiABKAgiaQobR2V0TWluZXJTdGF0ZUNvdW50c1Jlc3BvbnNlEhQKDHRvdGFsX21pbmVycxgBIAEoBRI0CgxzdGF0ZV9jb3VudHMYAiABKAsyHi50ZWxlbWV0cnkudjEuTWluZXJTdGF0ZUNvdW50cyJDCh5HZXRNaW5lclBvb2xBc3NpZ25tZW50c1JlcXVlc3QSIQoRZGV2aWNlX2lkZW50aWZpZXIYASABKAlCBrpIA8gBASJRCg5Qb29sQXNzaWdubWVudBIUCgdwb29sX2lkGAEgASgDSACIAQESCwoDdXJsGAIgASgJEhAKCHVzZXJuYW1lGAMgASgJQgoKCF9wb29sX2lkIlQKH0dldE1pbmVyUG9vbEFzc2lnbm1lbnRzUmVzcG9uc2USMQoFcG9vbHMYASADKAsyIi5mbGVldG1hbmFnZW1lbnQudjEuUG9vbEFzc2lnbm1lbnQiRQoPTWluZXJNb2RlbEdyb3VwEg0KBW1vZGVsGAEgASgJEhQKDG1hbnVmYWN0dXJlchgCIAEoCRINCgVjb3VudBgDIAEoBSJRChpHZXRNaW5lck1vZGVsR3JvdXBzUmVxdWVzdBIzCgZmaWx0ZXIYASABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJMaXN0RmlsdGVyIlIKG0dldE1pbmVyTW9kZWxHcm91cHNSZXNwb25zZRIzCgZncm91cHMYASADKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJNb2RlbEdyb3VwIj8KGkdldE1pbmVyQ29vbGluZ01vZGVSZXF1ZXN0EiEKEWRldmljZV9pZGVudGlmaWVyGAEgASgJQga6SAPIAQEiSwobR2V0TWluZXJDb29saW5nTW9kZVJlc3BvbnNlEiwKDGNvb2xpbmdfbW9kZRgBIAEoDjIWLmNvbW1vbi52MS5Db29saW5nTW9kZSKaAQoORGV2aWNlU2VsZWN0b3ISOgoLYWxsX2RldmljZXMYASABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJMaXN0RmlsdGVySAASOgoPaW5jbHVkZV9kZXZpY2VzGAIgASgLMh8uY29tbW9uLnYxLkRldmljZUlkZW50aWZpZXJMaXN0SABCEAoOc2VsZWN0aW9uX3R5cGUiUgoTRGVsZXRlTWluZXJzUmVxdWVzdBI7Cg9kZXZpY2Vfc2VsZWN0b3IYASABKAsyIi5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU2VsZWN0b3IiLQoURGVsZXRlTWluZXJzUmVzcG9uc2USFQoNZGVsZXRlZF9jb3VudBgBIAEoBSLBAQoTUmVuYW1lTWluZXJzUmVxdWVzdBJDCg9kZXZpY2Vfc2VsZWN0b3IYASABKAsyIi5mbGVldG1hbmFnZW1lbnQudjEuRGV2aWNlU2VsZWN0b3JCBrpIA8gBARJACgtuYW1lX2NvbmZpZxgCIAEoCzIjLmZsZWV0bWFuYWdlbWVudC52MS5NaW5lck5hbWVDb25maWdCBrpIA8gBARIjCgRzb3J0GAMgAygLMhUuY29tbW9uLnYxLlNvcnRDb25maWciXAoUUmVuYW1lTWluZXJzUmVzcG9uc2USFQoNcmVuYW1lZF9jb3VudBgBIAEoBRIXCg91bmNoYW5nZWRfY291bnQYAiABKAUSFAoMZmFpbGVkX2NvdW50GAMgASgFIoYCChhVcGRhdGVXb3JrZXJOYW1lc1JlcXVlc3QSQwoPZGV2aWNlX3NlbGVjdG9yGAEgASgLMiIuZmxlZXRtYW5hZ2VtZW50LnYxLkRldmljZVNlbGVjdG9yQga6SAPIAQESQAoLbmFtZV9jb25maWcYAiABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuTWluZXJOYW1lQ29uZmlnQga6SAPIAQESIwoEc29ydBgDIAMoCzIVLmNvbW1vbi52MS5Tb3J0Q29uZmlnEh4KDXVzZXJfdXNlcm5hbWUYBCABKAlCB7pIBHICEAESHgoNdXNlcl9wYXNzd29yZBgFIAEoCUIHukgEcgIQASJ7ChlVcGRhdGVXb3JrZXJOYW1lc1Jlc3BvbnNlEhUKDXVwZGF0ZWRfY291bnQYASABKAUSFwoPdW5jaGFuZ2VkX2NvdW50GAIgASgFEhQKDGZhaWxlZF9jb3VudBgDIAEoBRIYChBiYXRjaF9pZGVudGlmaWVyGAQgASgJInYKD01pbmVyTmFtZUNvbmZpZxI+Cgpwcm9wZXJ0aWVzGAEgAygLMiAuZmxlZXRtYW5hZ2VtZW50LnYxLk5hbWVQcm9wZXJ0eUIIukgFkgECCAESIwoJc2VwYXJhdG9yGAIgASgJQhC6SA1yC1IBLVIBX1IBLlIAItECCgxOYW1lUHJvcGVydHkSSgoSc3RyaW5nX2FuZF9jb3VudGVyGAEgASgLMiwuZmxlZXRtYW5hZ2VtZW50LnYxLlN0cmluZ0FuZENvdW50ZXJQcm9wZXJ0eUgAEjYKB2NvdW50ZXIYAiABKAsyIy5mbGVldG1hbmFnZW1lbnQudjEuQ291bnRlclByb3BlcnR5SAASOgoMc3RyaW5nX3ZhbHVlGAMgASgLMiIuZmxlZXRtYW5hZ2VtZW50LnYxLlN0cmluZ1Byb3BlcnR5SAASPQoLZml4ZWRfdmFsdWUYBCABKAsyJi5mbGVldG1hbmFnZW1lbnQudjEuRml4ZWRWYWx1ZVByb3BlcnR5SAASOgoJcXVhbGlmaWVyGAUgASgLMiUuZmxlZXRtYW5hZ2VtZW50LnYxLlF1YWxpZmllclByb3BlcnR5SABCBgoEa2luZCJ8ChhTdHJpbmdBbmRDb3VudGVyUHJvcGVydHkSDgoGcHJlZml4GAEgASgJEg4KBnN1ZmZpeBgCIAEoCRIeCg1jb3VudGVyX3N0YXJ0GAMgASgFQge6SAQaAigAEiAKDWNvdW50ZXJfc2NhbGUYBCABKAVCCbpIBhoEGAYoASJTCg9Db3VudGVyUHJvcGVydHkSHgoNY291bnRlcl9zdGFydBgBIAEoBUIHukgEGgIoABIgCg1jb3VudGVyX3NjYWxlGAIgASgFQgm6SAYaBBgGKAEiKAoOU3RyaW5nUHJvcGVydHkSFgoFdmFsdWUYASABKAlCB7pIBHICEAEi8gIKEkZpeGVkVmFsdWVQcm9wZXJ0eRI6CgR0eXBlGAEgASgOMiIuZmxlZXRtYW5hZ2VtZW50LnYxLkZpeGVkVmFsdWVUeXBlQgi6SAWCAQIQARInCg9jaGFyYWN0ZXJfY291bnQYAiABKAVCCbpIBhoEGAYoAUgAiAEBEkQKB3NlY3Rpb24YAyABKA4yJC5mbGVldG1hbmFnZW1lbnQudjEuQ2hhcmFjdGVyU2VjdGlvbkIIukgFggECEAFIAYgBATqQAbpIjAEaiQEKJXNlY3Rpb25fcmVxdWlyZWRfd2l0aF9jaGFyYWN0ZXJfY291bnQSL3NlY3Rpb24gaXMgcmVxdWlyZWQgd2hlbiBjaGFyYWN0ZXJfY291bnQgaXMgc2V0Gi8haGFzKHRoaXMuY2hhcmFjdGVyX2NvdW50KSB8fCBoYXModGhpcy5zZWN0aW9uKUISChBfY2hhcmFjdGVyX2NvdW50QgoKCF9zZWN0aW9uIm4KEVF1YWxpZmllclByb3BlcnR5EjkKBHR5cGUYASABKA4yIS5mbGVldG1hbmFnZW1lbnQudjEuUXVhbGlmaWVyVHlwZUIIukgFggECEAESDgoGcHJlZml4GAIgASgJEg4KBnN1ZmZpeBgDIAEoCSqZAQofRmxlZXRNYW5hZ2VtZW50U2VydmljZUVycm9yQ29kZRIzCi9GTEVFVF9NQU5BR0VNRU5UX1NFUlZJQ0VfRVJST1JfQ09ERV9VTlNQRUNJRklFRBAAEkEKPUZMRUVUX01BTkFHRU1FTlRfU0VSVklDRV9FUlJPUl9DT0RFX0lOVkFMSURfUEFHSU5BVElPTl9DVVJTT1IQASqaAgoMRGV2aWNlU3RhdHVzEh0KGURFVklDRV9TVEFUVVNfVU5TUEVDSUZJRUQQABIYChRERVZJQ0VfU1RBVFVTX09OTElORRABEhkKFURFVklDRV9TVEFUVVNfT0ZGTElORRACEh0KGURFVklDRV9TVEFUVVNfTUFJTlRFTkFOQ0UQAxIXChNERVZJQ0VfU1RBVFVTX0VSUk9SEAQSGgoWREVWSUNFX1NUQVRVU19JTkFDVElWRRAFEiMKH0RFVklDRV9TVEFUVVNfTkVFRFNfTUlOSU5HX1BPT0wQBhIaChZERVZJQ0VfU1RBVFVTX1VQREFUSU5HEAcSIQodREVWSUNFX1NUQVRVU19SRUJPT1RfUkVRVUlSRUQQCCpuChNEZXZpY2VPZmZsaW5lUmVhc29uEiUKIURFVklDRV9PRkZMSU5FX1JFQVNPTl9VTlNQRUNJRklFRBAAEjAKLERFVklDRV9PRkZMSU5FX1JFQVNPTl9GTEVFVF9OT0RFX1VOQVZBSUxBQkxFEAEq7QEKDVBhaXJpbmdTdGF0dXMSHgoaUEFJUklOR19TVEFUVVNfVU5TUEVDSUZJRUQQABIZChVQQUlSSU5HX1NUQVRVU19QQUlSRUQQARIbChdQQUlSSU5HX1NUQVRVU19VTlBBSVJFRBACEigKJFBBSVJJTkdfU1RBVFVTX0FVVEhFTlRJQ0FUSU9OX05FRURFRBADEhoKFlBBSVJJTkdfU1RBVFVTX1BFTkRJTkcQBBIZChVQQUlSSU5HX1NUQVRVU19GQUlMRUQQBRIjCh9QQUlSSU5HX1NUQVRVU19ERUZBVUxUX1BBU1NXT1JEEAYq5gEKDE51bWVyaWNGaWVsZBIdChlOVU1FUklDX0ZJRUxEX1VOU1BFQ0lGSUVEEAASHgoaTlVNRVJJQ19GSUVMRF9IQVNIUkFURV9USFMQARIgChxOVU1FUklDX0ZJRUxEX0VGRklDSUVOQ1lfSlRIEAISGgoWTlVNRVJJQ19GSUVMRF9QT1dFUl9LVxADEh8KG05VTUVSSUNfRklFTERfVEVNUEVSQVRVUkVfQxAEEhsKF05VTUVSSUNfRklFTERfVk9MVEFHRV9WEAUSGwoXTlVNRVJJQ19GSUVMRF9DVVJSRU5UX0EQBiqMAQoTTWluZXJJZGVudGlmaWVyVHlwZRIlCiFNSU5FUl9JREVOVElGSUVSX1RZUEVfVU5TUEVDSUZJRUQQABIlCiFNSU5FUl9JREVOVElGSUVSX1RZUEVfTUFDX0FERFJFU1MQARInCiNNSU5FUl9JREVOVElGSUVSX1RZUEVfU0VSSUFMX05VTUJFUhACKoEBChJDc3ZUZW1wZXJhdHVyZVVuaXQSJAogQ1NWX1RFTVBFUkFUVVJFX1VOSVRfVU5TUEVDSUZJRUQQABIgChxDU1ZfVEVNUEVSQVRVUkVfVU5JVF9DRUxTSVVTEAESIwofQ1NWX1RFTVBFUkFUVVJFX1VOSVRfRkFIUkVOSEVJVBACKpkCCg5GaXhlZFZhbHVlVHlwZRIgChxGSVhFRF9WQUxVRV9UWVBFX1VOU1BFQ0lGSUVEEAASIAocRklYRURfVkFMVUVfVFlQRV9NQUNfQUREUkVTUxABEiIKHkZJWEVEX1ZBTFVFX1RZUEVfU0VSSUFMX05VTUJFUhACEiAKHEZJWEVEX1ZBTFVFX1RZUEVfV09SS0VSX05BTUUQAxIaChZGSVhFRF9WQUxVRV9UWVBFX01PREVMEAQSIQodRklYRURfVkFMVUVfVFlQRV9NQU5VRkFDVFVSRVIQBRIdChlGSVhFRF9WQUxVRV9UWVBFX0xPQ0FUSU9OEAYSHwobRklYRURfVkFMVUVfVFlQRV9NSU5FUl9OQU1FEAcqbgoQQ2hhcmFjdGVyU2VjdGlvbhIhCh1DSEFSQUNURVJfU0VDVElPTl9VTlNQRUNJRklFRBAAEhsKF0NIQVJBQ1RFUl9TRUNUSU9OX0ZJUlNUEAESGgoWQ0hBUkFDVEVSX1NFQ1RJT05fTEFTVBACKocBCg1RdWFsaWZpZXJUeXBlEh4KGlFVQUxJRklFUl9UWVBFX1VOU1BFQ0lGSUVEEAASGwoXUVVBTElGSUVSX1RZUEVfQlVJTERJTkcQARIXChNRVUFMSUZJRVJfVFlQRV9SQUNLEAISIAocUVVBTElGSUVSX1RZUEVfUkFDS19QT1NJVElPThADMqQKChZGbGVldE1hbmFnZW1lbnRTZXJ2aWNlEoIBChdMaXN0TWluZXJTdGF0ZVNuYXBzaG90cxIyLmZsZWV0bWFuYWdlbWVudC52MS5MaXN0TWluZXJTdGF0ZVNuYXBzaG90c1JlcXVlc3QaMy5mbGVldG1hbmFnZW1lbnQudjEuTGlzdE1pbmVyU3RhdGVTbmFwc2hvdHNSZXNwb25zZRJkCg1SZWZyZXNoTWluZXJzEiguZmxlZXRtYW5hZ2VtZW50LnYxLlJlZnJlc2hNaW5lcnNSZXF1ZXN0GikuZmxlZXRtYW5hZ2VtZW50LnYxLlJlZnJlc2hNaW5lcnNSZXNwb25zZRKCAQoXTG9va3VwTWluZXJCeUlkZW50aWZpZXISMi5mbGVldG1hbmFnZW1lbnQudjEuTG9va3VwTWluZXJCeUlkZW50aWZpZXJSZXF1ZXN0GjMuZmxlZXRtYW5hZ2VtZW50LnYxLkxvb2t1cE1pbmVyQnlJZGVudGlmaWVyUmVzcG9uc2USdQoSRXhwb3J0TWluZXJMaXN0Q3N2Ei0uZmxlZXRtYW5hZ2VtZW50LnYxLkV4cG9ydE1pbmVyTGlzdENzdlJlcXVlc3QaLi5mbGVldG1hbmFnZW1lbnQudjEuRXhwb3J0TWluZXJMaXN0Q3N2UmVzcG9uc2UwARJ2ChNHZXRNaW5lclN0YXRlQ291bnRzEi4uZmxlZXRtYW5hZ2VtZW50LnYxLkdldE1pbmVyU3RhdGVDb3VudHNSZXF1ZXN0Gi8uZmxlZXRtYW5hZ2VtZW50LnYxLkdldE1pbmVyU3RhdGVDb3VudHNSZXNwb25zZRKCAQoXR2V0TWluZXJQb29sQXNzaWdubWVudHMSMi5mbGVldG1hbmFnZW1lbnQudjEuR2V0TWluZXJQb29sQXNzaWdubWVudHNSZXF1ZXN0GjMuZmxlZXRtYW5hZ2VtZW50LnYxLkdldE1pbmVyUG9vbEFzc2lnbm1lbnRzUmVzcG9uc2USdgoTR2V0TWluZXJDb29saW5nTW9kZRIuLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lckNvb2xpbmdNb2RlUmVxdWVzdBovLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lckNvb2xpbmdNb2RlUmVzcG9uc2USYQoMRGVsZXRlTWluZXJzEicuZmxlZXRtYW5hZ2VtZW50LnYxLkRlbGV0ZU1pbmVyc1JlcXVlc3QaKC5mbGVldG1hbmFnZW1lbnQudjEuRGVsZXRlTWluZXJzUmVzcG9uc2USdgoTR2V0TWluZXJNb2RlbEdyb3VwcxIuLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lck1vZGVsR3JvdXBzUmVxdWVzdBovLmZsZWV0bWFuYWdlbWVudC52MS5HZXRNaW5lck1vZGVsR3JvdXBzUmVzcG9uc2USYQoMUmVuYW1lTWluZXJzEicuZmxlZXRtYW5hZ2VtZW50LnYxLlJlbmFtZU1pbmVyc1JlcXVlc3QaKC5mbGVldG1hbmFnZW1lbnQudjEuUmVuYW1lTWluZXJzUmVzcG9uc2UScAoRVXBkYXRlV29ya2VyTmFtZXMSLC5mbGVldG1hbmFnZW1lbnQudjEuVXBkYXRlV29ya2VyTmFtZXNSZXF1ZXN0Gi0uZmxlZXRtYW5hZ2VtZW50LnYxLlVwZGF0ZVdvcmtlck5hbWVzUmVzcG9uc2VC8AEKFmNvbS5mbGVldG1hbmFnZW1lbnQudjFCFEZsZWV0bWFuYWdlbWVudFByb3RvUAFaV2dpdGh1Yi5jb20vYmxvY2svcHJvdG8tZmxlZXQvc2VydmVyL2dlbmVyYXRlZC9ncnBjL2ZsZWV0bWFuYWdlbWVudC92MTtmbGVldG1hbmFnZW1lbnR2MaICA0ZYWKoCEkZsZWV0bWFuYWdlbWVudC5WMcoCEkZsZWV0bWFuYWdlbWVudFxWMeICHkZsZWV0bWFuYWdlbWVudFxWMVxHUEJNZXRhZGF0YeoCE0ZsZWV0bWFuYWdlbWVudDo6VjFiBnByb3RvMw", [file_google_protobuf_timestamp, file_google_protobuf_wrappers, file_common_v1_measurement, file_common_v1_cooling, file_telemetry_v1_telemetry, file_capabilities_v1_capabilities, file_errors_v1_errors, file_common_v1_device_selector, file_common_v1_common, file_common_v1_sort, file_common_v1_zone, file_buf_validate_validate]); /** * MinerStateSnapshot represents the operational state of a mining device @@ -244,8 +227,7 @@ export type MinerStateSnapshot = Message<"fleetmanagement.v1.MinerStateSnapshot" * Describes the message fleetmanagement.v1.MinerStateSnapshot. * Use `create(MinerStateSnapshotSchema)` to create a new message. */ -export const MinerStateSnapshotSchema: GenMessage = - /*@__PURE__*/ +export const MinerStateSnapshotSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 0); /** @@ -296,8 +278,7 @@ export type ListMinerStateSnapshotsRequest = Message<"fleetmanagement.v1.ListMin * Describes the message fleetmanagement.v1.ListMinerStateSnapshotsRequest. * Use `create(ListMinerStateSnapshotsRequestSchema)` to create a new message. */ -export const ListMinerStateSnapshotsRequestSchema: GenMessage = - /*@__PURE__*/ +export const ListMinerStateSnapshotsRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 1); /** @@ -478,14 +459,23 @@ export type MinerListFilter = Message<"fleetmanagement.v1.MinerListFilter"> & { * @generated from field: repeated fleetmanagement.v1.IpRange ip_ranges = 19; */ ipRanges: IpRange[]; + + /** + * Case-insensitive substring search across the miner's display name, device + * identifier, serial number, MAC address, IP address, and worker name. + * When combined with other filters, the search predicate is AND-ed with + * those filters. + * + * @generated from field: string search_query = 20; + */ + searchQuery: string; }; /** * Describes the message fleetmanagement.v1.MinerListFilter. * Use `create(MinerListFilterSchema)` to create a new message. */ -export const MinerListFilterSchema: GenMessage = - /*@__PURE__*/ +export const MinerListFilterSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 2); /** @@ -513,7 +503,8 @@ export type IpRange = Message<"fleetmanagement.v1.IpRange"> & { * Describes the message fleetmanagement.v1.IpRange. * Use `create(IpRangeSchema)` to create a new message. */ -export const IpRangeSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 3); +export const IpRangeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_fleetmanagement_v1_fleetmanagement, 3); /** * Range predicate on a single telemetry field. At least one of min/max must be @@ -553,8 +544,7 @@ export type NumericRangeFilter = Message<"fleetmanagement.v1.NumericRangeFilter" * Describes the message fleetmanagement.v1.NumericRangeFilter. * Use `create(NumericRangeFilterSchema)` to create a new message. */ -export const NumericRangeFilterSchema: GenMessage = - /*@__PURE__*/ +export const NumericRangeFilterSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 4); /** @@ -620,8 +610,7 @@ export type ListMinerStateSnapshotsResponse = Message<"fleetmanagement.v1.ListMi * Describes the message fleetmanagement.v1.ListMinerStateSnapshotsResponse. * Use `create(ListMinerStateSnapshotsResponseSchema)` to create a new message. */ -export const ListMinerStateSnapshotsResponseSchema: GenMessage = - /*@__PURE__*/ +export const ListMinerStateSnapshotsResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 5); /** @@ -640,8 +629,7 @@ export type RefreshMinersRequest = Message<"fleetmanagement.v1.RefreshMinersRequ * Describes the message fleetmanagement.v1.RefreshMinersRequest. * Use `create(RefreshMinersRequestSchema)` to create a new message. */ -export const RefreshMinersRequestSchema: GenMessage = - /*@__PURE__*/ +export const RefreshMinersRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 6); /** @@ -667,8 +655,7 @@ export type RefreshMinersResponse = Message<"fleetmanagement.v1.RefreshMinersRes * Describes the message fleetmanagement.v1.RefreshMinersResponse. * Use `create(RefreshMinersResponseSchema)` to create a new message. */ -export const RefreshMinersResponseSchema: GenMessage = - /*@__PURE__*/ +export const RefreshMinersResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 7); /** @@ -700,8 +687,7 @@ export type LookupMinerByIdentifierRequest = Message<"fleetmanagement.v1.LookupM * Describes the message fleetmanagement.v1.LookupMinerByIdentifierRequest. * Use `create(LookupMinerByIdentifierRequestSchema)` to create a new message. */ -export const LookupMinerByIdentifierRequestSchema: GenMessage = - /*@__PURE__*/ +export const LookupMinerByIdentifierRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 8); /** @@ -725,8 +711,7 @@ export type LookupMinerByIdentifierResponse = Message<"fleetmanagement.v1.Lookup * Describes the message fleetmanagement.v1.LookupMinerByIdentifierResponse. * Use `create(LookupMinerByIdentifierResponseSchema)` to create a new message. */ -export const LookupMinerByIdentifierResponseSchema: GenMessage = - /*@__PURE__*/ +export const LookupMinerByIdentifierResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 9); /** @@ -752,8 +737,7 @@ export type ExportMinerListCsvRequest = Message<"fleetmanagement.v1.ExportMinerL * Describes the message fleetmanagement.v1.ExportMinerListCsvRequest. * Use `create(ExportMinerListCsvRequestSchema)` to create a new message. */ -export const ExportMinerListCsvRequestSchema: GenMessage = - /*@__PURE__*/ +export const ExportMinerListCsvRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 10); /** @@ -772,8 +756,7 @@ export type ExportMinerListCsvResponse = Message<"fleetmanagement.v1.ExportMiner * Describes the message fleetmanagement.v1.ExportMinerListCsvResponse. * Use `create(ExportMinerListCsvResponseSchema)` to create a new message. */ -export const ExportMinerListCsvResponseSchema: GenMessage = - /*@__PURE__*/ +export const ExportMinerListCsvResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 11); /** @@ -805,8 +788,7 @@ export type GetMinerStateCountsRequest = Message<"fleetmanagement.v1.GetMinerSta * Describes the message fleetmanagement.v1.GetMinerStateCountsRequest. * Use `create(GetMinerStateCountsRequestSchema)` to create a new message. */ -export const GetMinerStateCountsRequestSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerStateCountsRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 12); /** @@ -834,8 +816,7 @@ export type GetMinerStateCountsResponse = Message<"fleetmanagement.v1.GetMinerSt * Describes the message fleetmanagement.v1.GetMinerStateCountsResponse. * Use `create(GetMinerStateCountsResponseSchema)` to create a new message. */ -export const GetMinerStateCountsResponseSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerStateCountsResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 13); /** @@ -856,8 +837,7 @@ export type GetMinerPoolAssignmentsRequest = Message<"fleetmanagement.v1.GetMine * Describes the message fleetmanagement.v1.GetMinerPoolAssignmentsRequest. * Use `create(GetMinerPoolAssignmentsRequestSchema)` to create a new message. */ -export const GetMinerPoolAssignmentsRequestSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerPoolAssignmentsRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 14); /** @@ -894,8 +874,7 @@ export type PoolAssignment = Message<"fleetmanagement.v1.PoolAssignment"> & { * Describes the message fleetmanagement.v1.PoolAssignment. * Use `create(PoolAssignmentSchema)` to create a new message. */ -export const PoolAssignmentSchema: GenMessage = - /*@__PURE__*/ +export const PoolAssignmentSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 15); /** @@ -919,8 +898,7 @@ export type GetMinerPoolAssignmentsResponse = Message<"fleetmanagement.v1.GetMin * Describes the message fleetmanagement.v1.GetMinerPoolAssignmentsResponse. * Use `create(GetMinerPoolAssignmentsResponseSchema)` to create a new message. */ -export const GetMinerPoolAssignmentsResponseSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerPoolAssignmentsResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 16); /** @@ -949,8 +927,7 @@ export type MinerModelGroup = Message<"fleetmanagement.v1.MinerModelGroup"> & { * Describes the message fleetmanagement.v1.MinerModelGroup. * Use `create(MinerModelGroupSchema)` to create a new message. */ -export const MinerModelGroupSchema: GenMessage = - /*@__PURE__*/ +export const MinerModelGroupSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 17); /** @@ -969,8 +946,7 @@ export type GetMinerModelGroupsRequest = Message<"fleetmanagement.v1.GetMinerMod * Describes the message fleetmanagement.v1.GetMinerModelGroupsRequest. * Use `create(GetMinerModelGroupsRequestSchema)` to create a new message. */ -export const GetMinerModelGroupsRequestSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerModelGroupsRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 18); /** @@ -989,8 +965,7 @@ export type GetMinerModelGroupsResponse = Message<"fleetmanagement.v1.GetMinerMo * Describes the message fleetmanagement.v1.GetMinerModelGroupsResponse. * Use `create(GetMinerModelGroupsResponseSchema)` to create a new message. */ -export const GetMinerModelGroupsResponseSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerModelGroupsResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 19); /** @@ -1011,8 +986,7 @@ export type GetMinerCoolingModeRequest = Message<"fleetmanagement.v1.GetMinerCoo * Describes the message fleetmanagement.v1.GetMinerCoolingModeRequest. * Use `create(GetMinerCoolingModeRequestSchema)` to create a new message. */ -export const GetMinerCoolingModeRequestSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerCoolingModeRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 20); /** @@ -1035,8 +1009,7 @@ export type GetMinerCoolingModeResponse = Message<"fleetmanagement.v1.GetMinerCo * Describes the message fleetmanagement.v1.GetMinerCoolingModeResponse. * Use `create(GetMinerCoolingModeResponseSchema)` to create a new message. */ -export const GetMinerCoolingModeResponseSchema: GenMessage = - /*@__PURE__*/ +export const GetMinerCoolingModeResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 21); /** @@ -1049,35 +1022,31 @@ export type DeviceSelector = Message<"fleetmanagement.v1.DeviceSelector"> & { /** * @generated from oneof fleetmanagement.v1.DeviceSelector.selection_type */ - selectionType: - | { - /** - * Select all paired devices, optionally filtered. - * An empty filter selects all paired devices in the org. - * - * @generated from field: fleetmanagement.v1.MinerListFilter all_devices = 1; - */ - value: MinerListFilter; - case: "allDevices"; - } - | { - /** - * Select specific devices by identifier. - * - * @generated from field: common.v1.DeviceIdentifierList include_devices = 2; - */ - value: DeviceIdentifierList; - case: "includeDevices"; - } - | { case: undefined; value?: undefined }; + selectionType: { + /** + * Select all paired devices, optionally filtered. + * An empty filter selects all paired devices in the org. + * + * @generated from field: fleetmanagement.v1.MinerListFilter all_devices = 1; + */ + value: MinerListFilter; + case: "allDevices"; + } | { + /** + * Select specific devices by identifier. + * + * @generated from field: common.v1.DeviceIdentifierList include_devices = 2; + */ + value: DeviceIdentifierList; + case: "includeDevices"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message fleetmanagement.v1.DeviceSelector. * Use `create(DeviceSelectorSchema)` to create a new message. */ -export const DeviceSelectorSchema: GenMessage = - /*@__PURE__*/ +export const DeviceSelectorSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 22); /** @@ -1096,8 +1065,7 @@ export type DeleteMinersRequest = Message<"fleetmanagement.v1.DeleteMinersReques * Describes the message fleetmanagement.v1.DeleteMinersRequest. * Use `create(DeleteMinersRequestSchema)` to create a new message. */ -export const DeleteMinersRequestSchema: GenMessage = - /*@__PURE__*/ +export const DeleteMinersRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 23); /** @@ -1118,8 +1086,7 @@ export type DeleteMinersResponse = Message<"fleetmanagement.v1.DeleteMinersRespo * Describes the message fleetmanagement.v1.DeleteMinersResponse. * Use `create(DeleteMinersResponseSchema)` to create a new message. */ -export const DeleteMinersResponseSchema: GenMessage = - /*@__PURE__*/ +export const DeleteMinersResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 24); /** @@ -1155,8 +1122,7 @@ export type RenameMinersRequest = Message<"fleetmanagement.v1.RenameMinersReques * Describes the message fleetmanagement.v1.RenameMinersRequest. * Use `create(RenameMinersRequestSchema)` to create a new message. */ -export const RenameMinersRequestSchema: GenMessage = - /*@__PURE__*/ +export const RenameMinersRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 25); /** @@ -1185,8 +1151,7 @@ export type RenameMinersResponse = Message<"fleetmanagement.v1.RenameMinersRespo * Describes the message fleetmanagement.v1.RenameMinersResponse. * Use `create(RenameMinersResponseSchema)` to create a new message. */ -export const RenameMinersResponseSchema: GenMessage = - /*@__PURE__*/ +export const RenameMinersResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 26); /** @@ -1237,8 +1202,7 @@ export type UpdateWorkerNamesRequest = Message<"fleetmanagement.v1.UpdateWorkerN * Describes the message fleetmanagement.v1.UpdateWorkerNamesRequest. * Use `create(UpdateWorkerNamesRequestSchema)` to create a new message. */ -export const UpdateWorkerNamesRequestSchema: GenMessage = - /*@__PURE__*/ +export const UpdateWorkerNamesRequestSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 27); /** @@ -1272,8 +1236,7 @@ export type UpdateWorkerNamesResponse = Message<"fleetmanagement.v1.UpdateWorker * Describes the message fleetmanagement.v1.UpdateWorkerNamesResponse. * Use `create(UpdateWorkerNamesResponseSchema)` to create a new message. */ -export const UpdateWorkerNamesResponseSchema: GenMessage = - /*@__PURE__*/ +export const UpdateWorkerNamesResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 28); /** @@ -1301,8 +1264,7 @@ export type MinerNameConfig = Message<"fleetmanagement.v1.MinerNameConfig"> & { * Describes the message fleetmanagement.v1.MinerNameConfig. * Use `create(MinerNameConfigSchema)` to create a new message. */ -export const MinerNameConfigSchema: GenMessage = - /*@__PURE__*/ +export const MinerNameConfigSchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 29); /** @@ -1314,51 +1276,44 @@ export type NameProperty = Message<"fleetmanagement.v1.NameProperty"> & { /** * @generated from oneof fleetmanagement.v1.NameProperty.kind */ - kind: - | { - /** - * @generated from field: fleetmanagement.v1.StringAndCounterProperty string_and_counter = 1; - */ - value: StringAndCounterProperty; - case: "stringAndCounter"; - } - | { - /** - * @generated from field: fleetmanagement.v1.CounterProperty counter = 2; - */ - value: CounterProperty; - case: "counter"; - } - | { - /** - * @generated from field: fleetmanagement.v1.StringProperty string_value = 3; - */ - value: StringProperty; - case: "stringValue"; - } - | { - /** - * @generated from field: fleetmanagement.v1.FixedValueProperty fixed_value = 4; - */ - value: FixedValueProperty; - case: "fixedValue"; - } - | { - /** - * @generated from field: fleetmanagement.v1.QualifierProperty qualifier = 5; - */ - value: QualifierProperty; - case: "qualifier"; - } - | { case: undefined; value?: undefined }; + kind: { + /** + * @generated from field: fleetmanagement.v1.StringAndCounterProperty string_and_counter = 1; + */ + value: StringAndCounterProperty; + case: "stringAndCounter"; + } | { + /** + * @generated from field: fleetmanagement.v1.CounterProperty counter = 2; + */ + value: CounterProperty; + case: "counter"; + } | { + /** + * @generated from field: fleetmanagement.v1.StringProperty string_value = 3; + */ + value: StringProperty; + case: "stringValue"; + } | { + /** + * @generated from field: fleetmanagement.v1.FixedValueProperty fixed_value = 4; + */ + value: FixedValueProperty; + case: "fixedValue"; + } | { + /** + * @generated from field: fleetmanagement.v1.QualifierProperty qualifier = 5; + */ + value: QualifierProperty; + case: "qualifier"; + } | { case: undefined; value?: undefined }; }; /** * Describes the message fleetmanagement.v1.NameProperty. * Use `create(NamePropertySchema)` to create a new message. */ -export const NamePropertySchema: GenMessage = - /*@__PURE__*/ +export const NamePropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 30); /** @@ -1400,8 +1355,7 @@ export type StringAndCounterProperty = Message<"fleetmanagement.v1.StringAndCoun * Describes the message fleetmanagement.v1.StringAndCounterProperty. * Use `create(StringAndCounterPropertySchema)` to create a new message. */ -export const StringAndCounterPropertySchema: GenMessage = - /*@__PURE__*/ +export const StringAndCounterPropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 31); /** @@ -1429,8 +1383,7 @@ export type CounterProperty = Message<"fleetmanagement.v1.CounterProperty"> & { * Describes the message fleetmanagement.v1.CounterProperty. * Use `create(CounterPropertySchema)` to create a new message. */ -export const CounterPropertySchema: GenMessage = - /*@__PURE__*/ +export const CounterPropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 32); /** @@ -1451,8 +1404,7 @@ export type StringProperty = Message<"fleetmanagement.v1.StringProperty"> & { * Describes the message fleetmanagement.v1.StringProperty. * Use `create(StringPropertySchema)` to create a new message. */ -export const StringPropertySchema: GenMessage = - /*@__PURE__*/ +export const StringPropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 33); /** @@ -1488,8 +1440,7 @@ export type FixedValueProperty = Message<"fleetmanagement.v1.FixedValueProperty" * Describes the message fleetmanagement.v1.FixedValueProperty. * Use `create(FixedValuePropertySchema)` to create a new message. */ -export const FixedValuePropertySchema: GenMessage = - /*@__PURE__*/ +export const FixedValuePropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 34); /** @@ -1524,8 +1475,7 @@ export type QualifierProperty = Message<"fleetmanagement.v1.QualifierProperty"> * Describes the message fleetmanagement.v1.QualifierProperty. * Use `create(QualifierPropertySchema)` to create a new message. */ -export const QualifierPropertySchema: GenMessage = - /*@__PURE__*/ +export const QualifierPropertySchema: GenMessage = /*@__PURE__*/ messageDesc(file_fleetmanagement_v1_fleetmanagement, 35); /** @@ -1546,8 +1496,7 @@ export enum FleetManagementServiceErrorCode { /** * Describes the enum fleetmanagement.v1.FleetManagementServiceErrorCode. */ -export const FleetManagementServiceErrorCodeSchema: GenEnum = - /*@__PURE__*/ +export const FleetManagementServiceErrorCodeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 0); /** @@ -1623,8 +1572,7 @@ export enum DeviceStatus { /** * Describes the enum fleetmanagement.v1.DeviceStatus. */ -export const DeviceStatusSchema: GenEnum = - /*@__PURE__*/ +export const DeviceStatusSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 1); /** @@ -1647,8 +1595,7 @@ export enum DeviceOfflineReason { /** * Describes the enum fleetmanagement.v1.DeviceOfflineReason. */ -export const DeviceOfflineReasonSchema: GenEnum = - /*@__PURE__*/ +export const DeviceOfflineReasonSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 2); /** @@ -1712,8 +1659,7 @@ export enum PairingStatus { /** * Describes the enum fleetmanagement.v1.PairingStatus. */ -export const PairingStatusSchema: GenEnum = - /*@__PURE__*/ +export const PairingStatusSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 3); /** @@ -1778,8 +1724,7 @@ export enum NumericField { /** * Describes the enum fleetmanagement.v1.NumericField. */ -export const NumericFieldSchema: GenEnum = - /*@__PURE__*/ +export const NumericFieldSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 4); /** @@ -1815,8 +1760,7 @@ export enum MinerIdentifierType { /** * Describes the enum fleetmanagement.v1.MinerIdentifierType. */ -export const MinerIdentifierTypeSchema: GenEnum = - /*@__PURE__*/ +export const MinerIdentifierTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 5); /** @@ -1842,8 +1786,7 @@ export enum CsvTemperatureUnit { /** * Describes the enum fleetmanagement.v1.CsvTemperatureUnit. */ -export const CsvTemperatureUnitSchema: GenEnum = - /*@__PURE__*/ +export const CsvTemperatureUnitSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 6); /** @@ -1902,8 +1845,7 @@ export enum FixedValueType { /** * Describes the enum fleetmanagement.v1.FixedValueType. */ -export const FixedValueTypeSchema: GenEnum = - /*@__PURE__*/ +export const FixedValueTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 7); /** @@ -1931,8 +1873,7 @@ export enum CharacterSection { /** * Describes the enum fleetmanagement.v1.CharacterSection. */ -export const CharacterSectionSchema: GenEnum = - /*@__PURE__*/ +export const CharacterSectionSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 8); /** @@ -1971,8 +1912,7 @@ export enum QualifierType { /** * Describes the enum fleetmanagement.v1.QualifierType. */ -export const QualifierTypeSchema: GenEnum = - /*@__PURE__*/ +export const QualifierTypeSchema: GenEnum = /*@__PURE__*/ enumDesc(file_fleetmanagement_v1_fleetmanagement, 9); /** @@ -1992,7 +1932,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof ListMinerStateSnapshotsRequestSchema; output: typeof ListMinerStateSnapshotsResponseSchema; - }; + }, /** * Force an immediate telemetry/status collection for explicit devices and return fresh snapshots. * @@ -2002,7 +1942,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof RefreshMinersRequestSchema; output: typeof RefreshMinersResponseSchema; - }; + }, /** * Resolve a single paired miner from a scanned identifier — either a MAC * address or a manufacturer serial number. Backs the rack-assignment QR @@ -2018,7 +1958,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof LookupMinerByIdentifierRequestSchema; output: typeof LookupMinerByIdentifierResponseSchema; - }; + }, /** * Export the paired miner list as a CSV snapshot using the provided filter. * Rows are always emitted in default name-ascending order for cross-page consistency. @@ -2030,7 +1970,7 @@ export const FleetManagementService: GenService<{ methodKind: "server_streaming"; input: typeof ExportMinerListCsvRequestSchema; output: typeof ExportMinerListCsvResponseSchema; - }; + }, /** * Get counts of miners in different states without fetching miner data * More efficient than ListMinerStateSnapshots when only counts are needed @@ -2041,7 +1981,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof GetMinerStateCountsRequestSchema; output: typeof GetMinerStateCountsResponseSchema; - }; + }, /** * Get the current pool assignments for a specific miner * Returns the fleet pool IDs that match the miner's currently configured pools @@ -2053,7 +1993,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof GetMinerPoolAssignmentsRequestSchema; output: typeof GetMinerPoolAssignmentsResponseSchema; - }; + }, /** * Get the current cooling mode for a specific miner * Returns the cooling mode configuration from the miner @@ -2065,7 +2005,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof GetMinerCoolingModeRequestSchema; output: typeof GetMinerCoolingModeResponseSchema; - }; + }, /** * Delete miners from the fleet by soft-deleting their database records. * Immediately removes devices from the fleet and telemetry collection. @@ -2077,7 +2017,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof DeleteMinersRequestSchema; output: typeof DeleteMinersResponseSchema; - }; + }, /** * Get miner model groups with counts, optionally filtered by the current fleet filter * Used for bulk password update to show accurate model groups across the full fleet @@ -2088,7 +2028,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof GetMinerModelGroupsRequestSchema; output: typeof GetMinerModelGroupsResponseSchema; - }; + }, /** * Rename miners by applying a name config to all selected devices. * Supports both single-miner and bulk rename via DeviceSelector. @@ -2100,7 +2040,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof RenameMinersRequestSchema; output: typeof RenameMinersResponseSchema; - }; + }, /** * Update worker names by applying a name config to all selected devices. * Reapplies the miners' current pool settings first, then persists worker names @@ -2112,5 +2052,7 @@ export const FleetManagementService: GenService<{ methodKind: "unary"; input: typeof UpdateWorkerNamesRequestSchema; output: typeof UpdateWorkerNamesResponseSchema; - }; -}> = /*@__PURE__*/ serviceDesc(file_fleetmanagement_v1_fleetmanagement, 0); + }, +}> = /*@__PURE__*/ + serviceDesc(file_fleetmanagement_v1_fleetmanagement, 0); + diff --git a/client/src/protoFleet/components/MinerSearchInput.tsx b/client/src/protoFleet/components/MinerSearchInput.tsx new file mode 100644 index 0000000000..57bccc58fb --- /dev/null +++ b/client/src/protoFleet/components/MinerSearchInput.tsx @@ -0,0 +1,44 @@ +import { useCallback, useEffect, useRef } from "react"; + +import Search from "@/shared/components/Search"; + +const SEARCH_DEBOUNCE_MS = 250; + +interface MinerSearchInputProps { + initialValue?: string; + onQueryChange: (query: string) => void; + compact?: boolean; + id?: string; +} + +/** Search control for miner lists. The visible input updates immediately while + * requests are debounced so typing does not issue one RPC per keystroke. */ +const MinerSearchInput = ({ + initialValue = "", + onQueryChange, + compact = true, + id = "miner-search", +}: MinerSearchInputProps) => { + const timeoutRef = useRef | null>(null); + + const handleChange = useCallback( + (value: string) => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + timeoutRef.current = setTimeout(() => { + onQueryChange(value.trim()); + }, SEARCH_DEBOUNCE_MS); + }, + [onQueryChange], + ); + + useEffect( + () => () => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); + }, + [onQueryChange], + ); + + return ; +}; + +export default MinerSearchInput; diff --git a/client/src/protoFleet/components/MinerSelectionList.test.tsx b/client/src/protoFleet/components/MinerSelectionList.test.tsx index d8f56eb22c..44723939eb 100644 --- a/client/src/protoFleet/components/MinerSelectionList.test.tsx +++ b/client/src/protoFleet/components/MinerSelectionList.test.tsx @@ -376,12 +376,38 @@ describe("MinerSelectionList eligibility", () => { expect(titles).not.toContain("Building"); }); - it("renders the assignable-only toggle only when eligibility is provided", () => { + it("renders miner search for every list and the assignable-only toggle only with eligibility", () => { const { rerender } = render(); - expect(lastListProps()?.headerControls).toBeFalsy(); + expect(lastListProps()?.headerControls).toBeTruthy(); + expect(screen.queryByLabelText("Show assigned miners")).not.toBeInTheDocument(); rerender(); expect(lastListProps()?.headerControls).toBeTruthy(); + expect(screen.getByLabelText("Show assigned miners")).toBeInTheDocument(); + }); + + it("debounces the search query before fetching", async () => { + vi.useFakeTimers(); + try { + render(); + const input = screen.getByLabelText("Search miners"); + + fireEvent.change(input, { target: { value: "worker-42" } }); + expect(lastFleetFilter().searchQuery).toBe(""); + + await act(async () => { + await vi.advanceTimersByTimeAsync(249); + }); + expect(lastFleetFilter().searchQuery).toBe(""); + + await act(async () => { + await vi.advanceTimersByTimeAsync(1); + }); + expect(lastFleetFilter().searchQuery).toBe("worker-42"); + expect(screen.queryByText("Select all")).not.toBeInTheDocument(); + } finally { + vi.useRealTimers(); + } }); it("applies eligibility server-side by default and drops it when 'Show assigned miners' is on", () => { diff --git a/client/src/protoFleet/components/MinerSelectionList.tsx b/client/src/protoFleet/components/MinerSelectionList.tsx index 149c43eaf8..94f95d59b4 100644 --- a/client/src/protoFleet/components/MinerSelectionList.tsx +++ b/client/src/protoFleet/components/MinerSelectionList.tsx @@ -18,6 +18,7 @@ import { import { useSites } from "@/protoFleet/api/sites"; import { useDeviceSets } from "@/protoFleet/api/useDeviceSets"; import useFleet from "@/protoFleet/api/useFleet"; +import MinerSearchInput from "@/protoFleet/components/MinerSearchInput"; import type { SiteFilterFields } from "@/protoFleet/components/PageHeader/SitePicker"; import { INACTIVE_PLACEHOLDER } from "@/protoFleet/features/fleetManagement/components/MinerList/constants"; import { FLEET_SELECTABLE_PAIRING_STATUSES } from "@/protoFleet/features/fleetManagement/utils/fleetVisiblePairingFilter"; @@ -221,7 +222,8 @@ const hasUnsupportedAllSelectionFilter = (filter: MinerListFilter): boolean => filter.buildingIds.length > 0 || filter.ipCidrs.length > 0 || filter.ipRanges.length > 0 || - filter.includeUnassigned; + filter.includeUnassigned || + filter.searchQuery.trim().length > 0; const toDeviceListItem = (miner: ProtoMinerStateSnapshot): DeviceListItem => ({ deviceIdentifier: miner.deviceIdentifier, @@ -366,6 +368,7 @@ const MinerSelectionList = forwardRef create(MinerListFilterSchema, {})); + const [searchQuery, setSearchQuery] = useState(baseFilter.searchQuery); const [selectedItems, setSelectedItems] = useState(initialSelectedItems ?? []); const [allSelected, setAllSelected] = useState(initialAllSelected && !singleSelect); const [availableGroups, setAvailableGroups] = useState([]); @@ -397,6 +400,7 @@ const MinerSelectionList = forwardRef { const merged = layerUserFilter(baseFilter, userFilter); + merged.searchQuery = searchQuery; // Site scope is the soft baseline; a user-selected Site facet // (userFilter.siteIds) is more specific and takes precedence. if (merged.siteIds.length === 0) { @@ -461,6 +465,7 @@ const MinerSelectionList = forwardRef - + + ) : null} + { + action("onDismiss")(); + setOpen(false); + }} + onSave={(selection) => { + action("onSave")(selection); + setOpen(false); + }} + /> + + ); +}; + export const WithPreselected = () => { const [open, setOpen] = useState(true); diff --git a/client/src/protoFleet/features/settings/components/Schedules/MinerSelectionModal.test.tsx b/client/src/protoFleet/features/settings/components/Schedules/MinerSelectionModal.test.tsx index 37f4843780..7e62ad3732 100644 --- a/client/src/protoFleet/features/settings/components/Schedules/MinerSelectionModal.test.tsx +++ b/client/src/protoFleet/features/settings/components/Schedules/MinerSelectionModal.test.tsx @@ -83,6 +83,17 @@ describe("MinerSelectionModal", () => { expect(mockMinerSelectionList).toHaveBeenCalledWith(expect.objectContaining({ scope })); }); + it("keeps the selection-change handler stable across parent re-renders", () => { + const { rerender } = render( + , + ); + const firstHandler = mockMinerSelectionList.mock.lastCall?.[0].onSelectionChange; + + rerender(); + + expect(mockMinerSelectionList.mock.lastCall?.[0].onSelectionChange).toBe(firstHandler); + }); + it("forwards drill-down ancestors as the initial miner filter", () => { const initialFilter = create(MinerListFilterSchema, { buildingIds: [11n], rackIds: [21n] }); render( diff --git a/client/src/protoFleet/stories/MockedMinerSelectionApis.ts b/client/src/protoFleet/stories/MockedMinerSelectionApis.ts index 34758b4b83..e27e2b9665 100644 --- a/client/src/protoFleet/stories/MockedMinerSelectionApis.ts +++ b/client/src/protoFleet/stories/MockedMinerSelectionApis.ts @@ -1,4 +1,4 @@ -import { createElement, Fragment, type ReactNode, useEffect } from "react"; +import { createElement, Fragment, type ReactNode, useLayoutEffect } from "react"; import { create } from "@bufbuild/protobuf"; import { curtailmentClient, deviceSetClient, fleetManagementClient } from "@/protoFleet/api/clients"; @@ -113,6 +113,19 @@ const mockMiners = [ ]; const mockMinerModels = Array.from(new Set(mockMiners.map((miner) => miner.model))); +const matchesMinerSearch = (query: string, miner: (typeof mockMiners)[number]) => { + const normalized = query.trim().toLocaleLowerCase(); + if (!normalized) return true; + + return [ + miner.name, + miner.deviceIdentifier, + miner.serialNumber, + miner.macAddress, + miner.ipAddress, + miner.workerName, + ].some((value) => value.toLocaleLowerCase().includes(normalized)); +}; const mockCurtailmentCandidates = mockMiners.map((miner, index) => create(CurtailmentCandidateSchema, { deviceIdentifier: miner.deviceIdentifier, @@ -123,7 +136,7 @@ const mockCurtailmentCandidates = mockMiners.map((miner, index) => ); export function MockedMinerSelectionApis({ children }: { children: ReactNode }) { - useEffect(() => { + useLayoutEffect(() => { return installMockedMinerSelectionApis(); }, []); @@ -149,13 +162,15 @@ const installMockedMinerSelectionApis = createRefCountedStoryMock(() => { }); }; - mutableFleetManagementClient.listMinerStateSnapshots = async () => - create(ListMinerStateSnapshotsResponseSchema, { - miners: mockMiners, + mutableFleetManagementClient.listMinerStateSnapshots = async (request) => { + const miners = mockMiners.filter((miner) => matchesMinerSearch(request.filter?.searchQuery ?? "", miner)); + return create(ListMinerStateSnapshotsResponseSchema, { + miners, cursor: "", - totalMiners: mockMiners.length, + totalMiners: miners.length, models: mockMinerModels, }); + }; mutableCurtailmentClient.previewCurtailmentPlan = async (request) => { const fixedKw = diff --git a/client/src/shared/assets/icons/Search.tsx b/client/src/shared/assets/icons/Search.tsx new file mode 100644 index 0000000000..ea7285dd7e --- /dev/null +++ b/client/src/shared/assets/icons/Search.tsx @@ -0,0 +1,30 @@ +import clsx from "clsx"; + +import { iconSizes } from "./constants"; +import { IconProps } from "./types"; + +/** + * Magnifying-glass glyph from Square Web's MarketMagnifyingGlassIcon, adapted + * to Proto Fleet's existing currentColor icon wrapper. + */ +const Search = ({ className, width = iconSizes.medium }: IconProps) => ( +
+ + + +
+); + +export default Search; diff --git a/client/src/shared/assets/icons/index.tsx b/client/src/shared/assets/icons/index.tsx index 0929a3c4a4..232d3e57b7 100644 --- a/client/src/shared/assets/icons/index.tsx +++ b/client/src/shared/assets/icons/index.tsx @@ -70,6 +70,7 @@ import Racks from "./Racks"; import Reboot from "./Reboot"; import Rectangle from "./Rectangle"; import Repair from "./Repair"; +import Search from "./Search"; import Settings from "./Settings"; import SettingsSolid from "./SettingsSolid"; import Site from "./Site"; @@ -160,6 +161,7 @@ export { Reboot, Rectangle, Repair, + Search, Settings, SettingsSolid, Site, diff --git a/client/src/shared/components/List/Filters/Filters.tsx b/client/src/shared/components/List/Filters/Filters.tsx index d63abef395..11150aef73 100644 --- a/client/src/shared/components/List/Filters/Filters.tsx +++ b/client/src/shared/components/List/Filters/Filters.tsx @@ -30,6 +30,7 @@ type FilterProps = { onFilter: (activeFilters: ActiveFilters) => void | Promise; isServerSide?: boolean; headerControls?: ReactNode; + leadingHeaderControls?: ReactNode; initialActiveFilters?: ActiveFilters; }; @@ -49,6 +50,7 @@ const Filters = ({ onFilter, isServerSide = false, headerControls, + leadingHeaderControls, initialActiveFilters, }: FilterProps) => { const defaultActiveFilters = useMemo( @@ -318,7 +320,8 @@ const Filters = ({ return (
-
+
+ {leadingHeaderControls}
{leadingFilters.map((filter) => { if (filter.type === "button") { diff --git a/client/src/shared/components/List/List.test.tsx b/client/src/shared/components/List/List.test.tsx index 9e28a1ea14..725635c5f9 100644 --- a/client/src/shared/components/List/List.test.tsx +++ b/client/src/shared/components/List/List.test.tsx @@ -427,6 +427,27 @@ describe("List", () => { expect(screen.queryByText("All Items")).not.toBeInTheDocument(); }); + it("renders leading controls before filter pills while retaining trailing actions", () => { + render( + + activeCols={activeCols} + colTitles={testColTitles} + colConfig={testColConfig} + filters={testFilters} + leadingHeaderControls={} + headerControls={} + items={testItems} + itemKey="id" + />, + ); + + const search = screen.getByRole("button", { name: "Search miners" }); + const filter = screen.getByText("All Items"); + const action = screen.getByRole("button", { name: "Add miner" }); + expect(search.compareDocumentPosition(filter) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect(filter.compareDocumentPosition(action) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + }); + it("keeps filter controls visible for a filtered no-data state", () => { render( diff --git a/client/src/shared/components/List/List.tsx b/client/src/shared/components/List/List.tsx index 870144c3ad..708f13c846 100644 --- a/client/src/shared/components/List/List.tsx +++ b/client/src/shared/components/List/List.tsx @@ -173,6 +173,8 @@ type ListProps Promise; filterSize?: keyof typeof sizes; headerControls?: ReactNode; + /** Controls rendered immediately before the filter pills. */ + leadingHeaderControls?: ReactNode; items: ListItem[]; itemKey: keyof ListItem; itemSelectable?: boolean; @@ -736,6 +738,7 @@ const List = ) : null; diff --git a/client/src/shared/components/Search/Search.tsx b/client/src/shared/components/Search/Search.tsx index b217531274..86f368bca6 100644 --- a/client/src/shared/components/Search/Search.tsx +++ b/client/src/shared/components/Search/Search.tsx @@ -21,6 +21,13 @@ interface SearchProps { variant?: SearchVariant; initValue?: string; onChange: (value: string, id: string) => void; + onBlur?: (value: string) => void; + /** Called after the clear affordance empties the field. Receives the value + * that was present before clearing so collapsible consumers can distinguish + * clearing a query from dismissing an already-empty field. */ + onClear?: (previousValue: string) => void; + /** Keeps the toolbar clear affordance visible when the field is empty. */ + showClearWhenEmpty?: boolean; shouldFocus?: boolean; id?: string; label?: string; @@ -36,6 +43,9 @@ const Search = ({ className, variant = "default", onChange, + onBlur, + onClear, + showClearWhenEmpty = false, initValue, shouldFocus, id = defaultId, @@ -77,11 +87,14 @@ const Search = ({ ); const clearValue = useCallback(() => { + const previousValue = value ?? ""; handleChange(""); + onClear?.(previousValue); // Clearing from the button would otherwise drop focus and force the user - // back to the mouse to start a new query. + // back to the mouse to start a new query. Collapsible consumers unmount the + // field when an already-empty clear is a dismiss action. inputRef.current?.focus(); - }, [handleChange]); + }, [handleChange, onClear, value]); const clearValueOnEscape = useCallback( (key: string) => { @@ -102,7 +115,7 @@ const Search = ({ // Input only renders its own clear button at the default height, so `toolbar` // supplies one to avoid a bordered field with no way out but the Escape key. - const showToolbarClear = isToolbar && Boolean(value); + const showToolbarClear = isToolbar && (showClearWhenEmpty || Boolean(value)); return (
@@ -127,6 +140,7 @@ const Search = ({ inputRef={inputRef} initValue={value} onKeyDown={clearValueOnEscape} + onBlur={() => onBlur?.(value ?? "")} compact={bareInput} sanitize={sanitize} /> From e0a643e4adf942633d5e111e5df6deff92a2fd80 Mon Sep 17 00:00:00 2001 From: Marvin Charles Date: Mon, 14 Sep 2026 15:59:42 +0300 Subject: [PATCH 09/16] chore(gen): apply goimports grouping to fleetmanagement bindings The committed bindings kept protoc-gen-go's raw import order, so CI's `just gen` (which ends in `goimports -w .`) regenerated a different file and failed the generated-code check. --- .../grpc/fleetmanagement/v1/fleetmanagement.pb.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go b/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go index ab093314f6..565398b602 100644 --- a/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go +++ b/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go @@ -7,6 +7,10 @@ package fleetmanagementv1 import ( + reflect "reflect" + sync "sync" + unsafe "unsafe" + _ "buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go/buf/validate" v11 "github.com/block/proto-fleet/server/generated/grpc/capabilities/v1" v1 "github.com/block/proto-fleet/server/generated/grpc/common/v1" @@ -16,9 +20,6 @@ import ( protoimpl "google.golang.org/protobuf/runtime/protoimpl" timestamppb "google.golang.org/protobuf/types/known/timestamppb" wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" - reflect "reflect" - sync "sync" - unsafe "unsafe" ) const ( From de45d21ff382d0927da36c6eff83f3c172fdb9ff Mon Sep 17 00:00:00 2001 From: Marvin Charles Date: Mon, 14 Sep 2026 17:10:10 +0300 Subject: [PATCH 10/16] refactor(client): drop the unreachable search branch from the select-all filter canSelectAll already withdraws select-all for any non-empty search before it consults hasUnsupportedAllSelectionFilter, so the search clause inside that helper could never decide anything. Record why the gate belongs at the call site instead: a substring match is unrepresentable for every caller, not only the ones that opt into disableFilteredSelectAll. --- client/src/protoFleet/components/MinerSelectionList.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/client/src/protoFleet/components/MinerSelectionList.tsx b/client/src/protoFleet/components/MinerSelectionList.tsx index eef605d3a9..89c12fa961 100644 --- a/client/src/protoFleet/components/MinerSelectionList.tsx +++ b/client/src/protoFleet/components/MinerSelectionList.tsx @@ -222,8 +222,7 @@ const hasUnsupportedAllSelectionFilter = (filter: MinerListFilter): boolean => filter.buildingIds.length > 0 || filter.ipCidrs.length > 0 || filter.ipRanges.length > 0 || - filter.includeUnassigned || - filter.searchQuery.trim().length > 0; + filter.includeUnassigned; const toDeviceListItem = (miner: ProtoMinerStateSnapshot): DeviceListItem => ({ deviceIdentifier: miner.deviceIdentifier, @@ -593,6 +592,12 @@ const MinerSelectionList = forwardRef Date: Mon, 14 Sep 2026 17:10:14 +0300 Subject: [PATCH 11/16] docs(fleetmanagement): note that search matches the display-name fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The name branch reuses the display-name sort expression, so an unnamed miner is matched on " " — searching "S21" finds unnamed S21s. The field contract listed only the literal columns. --- .../api/generated/fleetmanagement/v1/fleetmanagement_pb.ts | 4 ++++ proto/fleetmanagement/v1/fleetmanagement.proto | 4 ++++ .../generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go | 4 ++++ server/internal/domain/stores/interfaces/device.go | 2 ++ 4 files changed, 14 insertions(+) diff --git a/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts b/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts index 50cb3b341f..417583a667 100644 --- a/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts +++ b/client/src/protoFleet/api/generated/fleetmanagement/v1/fleetmanagement_pb.ts @@ -485,6 +485,10 @@ export type MinerListFilter = Message<"fleetmanagement.v1.MinerListFilter"> & { * When combined with other filters, the search predicate is AND-ed with * those filters. * + * Display name is matched as the list renders it, so a miner with no custom + * name is matched on its " " fallback — searching "S21" + * finds unnamed S21s, not just miners named after one. + * * @generated from field: string search_query = 20; */ searchQuery: string; diff --git a/proto/fleetmanagement/v1/fleetmanagement.proto b/proto/fleetmanagement/v1/fleetmanagement.proto index 03f6da81fb..ad04954b90 100644 --- a/proto/fleetmanagement/v1/fleetmanagement.proto +++ b/proto/fleetmanagement/v1/fleetmanagement.proto @@ -379,6 +379,10 @@ message MinerListFilter { // identifier, serial number, MAC address, IP address, and worker name. // When combined with other filters, the search predicate is AND-ed with // those filters. + // + // Display name is matched as the list renders it, so a miner with no custom + // name is matched on its " " fallback — searching "S21" + // finds unnamed S21s, not just miners named after one. string search_query = 20 [(buf.validate.field).string.max_len = 255]; } diff --git a/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go b/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go index 565398b602..12d06eb0f1 100644 --- a/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go +++ b/server/generated/grpc/fleetmanagement/v1/fleetmanagement.pb.go @@ -1063,6 +1063,10 @@ type MinerListFilter struct { // identifier, serial number, MAC address, IP address, and worker name. // When combined with other filters, the search predicate is AND-ed with // those filters. + // + // Display name is matched as the list renders it, so a miner with no custom + // name is matched on its " " fallback — searching "S21" + // finds unnamed S21s, not just miners named after one. SearchQuery string `protobuf:"bytes,20,opt,name=search_query,json=searchQuery,proto3" json:"search_query,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache diff --git a/server/internal/domain/stores/interfaces/device.go b/server/internal/domain/stores/interfaces/device.go index 97ca46ae47..e6c8bfc9b1 100644 --- a/server/internal/domain/stores/interfaces/device.go +++ b/server/internal/domain/stores/interfaces/device.go @@ -57,6 +57,8 @@ type ZoneKey struct { type MinerFilter struct { // SearchQuery is a case-insensitive substring matched against miner // identifying fields (name, device identifier, serial, MAC, IP, worker). + // Name reuses the display-name sort expression, so an unnamed miner matches + // on its manufacturer/model fallback. SearchQuery string PairingStatuses []fm.PairingStatus // Changed from single value to slice DeviceStatusFilter []mm.MinerStatus From 951521191e973da92c354f5fa526296aae3c42be Mon Sep 17 00:00:00 2001 From: Marvin Charles Date: Mon, 14 Sep 2026 17:51:55 +0300 Subject: [PATCH 12/16] fix(fleetmanagement): inset the expanded miner search on phones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The List filter row is padded on its left edge only, since until now nothing in it was full-width — content-sized pills never reached the right edge, so the missing right inset was invisible. The expanded search field is `phone:w-full`, so it ran flush to the viewport while every sibling stayed inset. Mirror the page inset with the same `--list-padding-phone` variable List already publishes for its left padding, and give the MinerList stories the Fleet page's layout props so the story reproduces the page's padding instead of rendering edge-to-edge. --- .../fleetManagement/components/MinerList/MinerList.tsx | 9 ++++++++- .../components/MinerList/stories/MinerList.stories.tsx | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/src/protoFleet/features/fleetManagement/components/MinerList/MinerList.tsx b/client/src/protoFleet/features/fleetManagement/components/MinerList/MinerList.tsx index 6c21b68e8c..d4ec241224 100644 --- a/client/src/protoFleet/features/fleetManagement/components/MinerList/MinerList.tsx +++ b/client/src/protoFleet/features/fleetManagement/components/MinerList/MinerList.tsx @@ -406,7 +406,14 @@ const ScopedMinerListBody = ({ leadingHeaderControls={
); From 5460d37859f3f5412908626179a125536193b1e6 Mon Sep 17 00:00:00 2001 From: Marvin Charles Date: Mon, 14 Sep 2026 18:04:55 +0300 Subject: [PATCH 13/16] test(client): query the miner search story through its label association The collapsible-search story failed on every open. The field autofocuses on expand, and Input hides its label on focus with `visibility: hidden`. Browsers keep a hidden `