Fleet UI: Handle long fleet names across the Fleets UI (#49216)
## Issue Closes #47290 Also implements the "Cap free-text `maxLength` to the backend column length" pattern established in [#49041 (patterns.md thread)](https://github.com/fleetdm/fleet/pull/49041/files#r3572648691). ## Description Fleet name inputs had no `maxLength` cap and no service-layer length check, so a name >255 chars failed with a raw MySQL `Data too long` error, and several UI surfaces didn't handle long names gracefully. This PR fixes all four manifestations called out in the bug, plus a related label-overflow case on the host details page, and hardens adjacent name inputs across the app. **Frontend fixes for #47290:** - Create/Rename fleet name inputs now cap at 255 characters (matches `teams.name varchar(255)`). - Fleets table Name column uses `LinkCell` with `tooltipTruncate` + `className="w400"` so long names truncate with an ellipsis and full-name tooltip instead of overflowing across the Hosts/Users columns. - Fleet-detail page header (`.team-details__team-header`): h1 gets `overflow: hidden; text-overflow: ellipsis; white-space: nowrap;`, `__team-details` gets `min-width: 0; flex: 1`, and `.action-buttons` gets `flex-shrink: 0` + `white-space: nowrap` on buttons so *Manage enroll secrets / Rename / Delete* no longer wrap to a second line when the fleet name is long. - Manage enroll secrets modal body — `__description` gets `overflow-wrap: anywhere; min-width: 0` so a long `<b>{fleet name}</b>` wraps within the modal instead of spilling out the right edge. **Backend fixes for #47290:** - New `fleet.MaxTeamNameLength = 255` constant. - `NewTeam`, `ModifyTeam`, and `ApplyTeamSpecs` now return `fleet.NewInvalidArgumentError("name", "may not exceed 255 characters")` instead of surfacing a raw `Data too long` MySQL error. Covers UI, API, and GitOps entry points. **Broader consistency pass (per [#49041 thread](https://github.com/fleetdm/fleet/pull/49041/files#r3572648691)):** - New shared `MAX_ENTITY_CHAR_LENGTH = 255` constant in `frontend/utilities/constants.tsx`. - Refactored 8 existing files that had ad-hoc `NAME_MAX_LENGTH = 255` / `MAX_LABEL_NAME_LENGTH = 255` locals to use it. - Slotted it into 16 additional `InputField` name/description inputs that were missing a cap (API user, custom variable, certificate, label name + description, pack name + description, and all 5 CA forms — CustomEST, CustomSCEP, Smallstep, Digicert, Hydrant). - Pruned dead FE length validators that can no longer fire now that the DOM cap enforces the limit (certificate modal, custom variable modal, both label helpers, both category modals). Unusual/shorter caps (e.g. `varchar(64)`, custom business rules) still keep their inline validators — silent truncation is only appropriate for the common 255-char norm. **Bonus:** fixed the long-label overflow on the host details Labels card by capping the pill button `max-width` at 300px. ## Screenrecording https://github.com/user-attachments/assets/b917b72e-7437-4d0c-a1a1-c49b4b1c28ba https://github.com/user-attachments/assets/3a3efbb2-09d8-4f47-9fd4-f158b3453b9e https://github.com/user-attachments/assets/73e5e022-dc93-4381-82b3-be9549d050e6 Latest - max width 300px long label: <img width="1106" height="262" alt="Screenshot 2026-07-23 at 11 29 24 AM" src="https://github.com/user-attachments/assets/741fddbd-f78d-4578-a025-bddf64a81c25" /> ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually Test coverage: - `CreateFleetModal.tests.tsx`, `RenameFleetModal.tests.tsx` — new case per file asserting the name input's `maxLength === 255`. - `AddCertificateModal.tests.tsx`, `Variables.tests.tsx` — the existing "shows too-long error when pasting 256 chars" tests are now unreachable via the DOM cap; converted to `maxLength === 255` assertions. - `ee/server/service/teams_test.go` — `TestNewTeamNameValidation`, `TestModifyTeamNameValidation`, and `TestApplyTeamSpecsNameValidation` each get two new cases (accepts at the limit, rejects one over with the expected error message). <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Limited fleet, team, and other user-entered names and descriptions to 255 characters. * Replaced database errors for oversized names with clear validation messages. * Prevented long fleet and label names from overflowing tables, headers, modals, and host details. * Improved modal and dropdown layouts for long text. * **Tests** * Added coverage for character limits, boundary values, and multibyte names. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+3
-5
@@ -2,13 +2,13 @@ import React, { useState } from "react";
|
||||
|
||||
import selfServiceCategoriesAPI from "services/entities/self_service_categories";
|
||||
import { hasStatusKey } from "interfaces/errors";
|
||||
import { MAX_ENTITY_CHAR_LENGTH } from "utilities/constants";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Modal from "components/Modal";
|
||||
|
||||
const baseClass = "add-category-modal";
|
||||
const NAME_MAX_LENGTH = 255;
|
||||
|
||||
interface IAddCategoryModalProps {
|
||||
fleetId: number;
|
||||
@@ -26,9 +26,7 @@ const AddCategoryModal = ({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const trimmedName = name.trim();
|
||||
const isInvalid =
|
||||
trimmedName.length === 0 || trimmedName.length > NAME_MAX_LENGTH;
|
||||
const isDisabled = isInvalid || isSubmitting;
|
||||
const isDisabled = trimmedName.length === 0 || isSubmitting;
|
||||
|
||||
const onNameChange = (value: string) => {
|
||||
setName(value);
|
||||
@@ -74,7 +72,7 @@ const AddCategoryModal = ({
|
||||
error={error}
|
||||
autofocus
|
||||
ignore1password
|
||||
inputOptions={{ maxLength: NAME_MAX_LENGTH }}
|
||||
inputOptions={{ maxLength: MAX_ENTITY_CHAR_LENGTH }}
|
||||
/>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button type="submit" disabled={isDisabled} isLoading={isSubmitting}>
|
||||
|
||||
+3
-5
@@ -3,13 +3,13 @@ import React, { useState } from "react";
|
||||
import selfServiceCategoriesAPI from "services/entities/self_service_categories";
|
||||
import { hasStatusKey } from "interfaces/errors";
|
||||
import { ISelfServiceCategory } from "interfaces/self_service_category";
|
||||
import { MAX_ENTITY_CHAR_LENGTH } from "utilities/constants";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Modal from "components/Modal";
|
||||
|
||||
const baseClass = "edit-category-modal";
|
||||
const NAME_MAX_LENGTH = 255;
|
||||
|
||||
interface IEditCategoryModalProps {
|
||||
category: ISelfServiceCategory;
|
||||
@@ -27,10 +27,8 @@ const EditCategoryModal = ({
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const trimmedName = name.trim();
|
||||
const isInvalid =
|
||||
trimmedName.length === 0 || trimmedName.length > NAME_MAX_LENGTH;
|
||||
const isUnchanged = trimmedName === category.name;
|
||||
const isDisabled = isInvalid || isUnchanged || isSubmitting;
|
||||
const isDisabled = trimmedName.length === 0 || isUnchanged || isSubmitting;
|
||||
|
||||
const onNameChange = (value: string) => {
|
||||
setName(value);
|
||||
@@ -75,7 +73,7 @@ const EditCategoryModal = ({
|
||||
error={error}
|
||||
autofocus
|
||||
ignore1password
|
||||
inputOptions={{ maxLength: NAME_MAX_LENGTH }}
|
||||
inputOptions={{ maxLength: MAX_ENTITY_CHAR_LENGTH }}
|
||||
/>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button type="submit" disabled={isDisabled} isLoading={isSubmitting}>
|
||||
|
||||
Reference in New Issue
Block a user