# Service row

> A settings row for an MCP server, agent or skill: status, switch and menu. A Pyzed component for Python desktop GUIs, with examples and its API.

Source: https://albakore.github.io/pyzed/library/service-row/

:::tip[In plain words]
One line of a settings page for a tool the assistant uses: whether it works, a switch to turn it on or off, and a menu with more options.
:::

`ui.ServiceRow` is a component for one thing an AI agent uses (an MCP server, an external agent, a skill) on a settings page: an icon, the name with its `version` and a status dot, the `detail` (its command or URL, in a monospace font) and a `summary`; then a spinner while `busy`, a main button (`primary`, such as Update or Install), an on/off switch (`enabled`; `None` hides it) and a `⋯` menu with the `actions` (`ui.MenuItem`). `status` colors the dot: `running` and `installed` green, `starting` yellow, `update` in the accent color, `error` red, `off` and `missing` gray. `status_text` is the dot's tooltip, and it shows under the name for `error`, `missing` and `update`.

`ui.ServiceRow` · Category: [AI agents](https://albakore.github.io/pyzed/library/#ai-agents) · [Full reference →](https://albakore.github.io/pyzed/reference/ui/#servicerow)

**Code:**

```python
pz.column([
    ui.ServiceRow(name="github", detail="npx -y @modelcontextprotocol/server-github", icon_name="github",
                  status="running", enabled=True, on_toggle=on_toggle,
                  actions=[ui.MenuItem("Edit", on_edit, icon="pencil")]),
    ui.ServiceRow(name="filesystem", detail="uvx mcp-server-filesystem ~/notes", status="error",
                  status_text="Exited with code 1", enabled=True, on_toggle=on_toggle),
    ui.ServiceRow(name="Gemini CLI", version="0.64.0", icon_name="bot", status="update",
                  status_text="0.65.0 is available", primary="Update", on_primary=on_update),
], spacing=4, width=560)
```

:::note[New]
This component was added recently. Tell us how it works for you in the [issue tracker](https://github.com/albakore/pyzed/issues).
:::

## When to use

- In the settings of an app with AI agents: one row per MCP server, agent or skill.
- For any list of services that can be turned on and off, updated or removed.

## Usage

```python
import pyzed as pz
from pyzed import ui

class Settings(pz.App):
    def __init__(self):
        self.on = True

    def view(self):
        return ui.ServiceRow(name="github", detail="npx -y @modelcontextprotocol/server-github",
                             status="running" if self.on else "off", enabled=self.on,
                             on_toggle=lambda e: setattr(self, "on", e.value),
                             actions=[ui.MenuItem("Remove", self.remove, icon="trash-2", danger=True)])
```

## Variants

:::note[About these captures]
Each capture is recorded by pyzed itself, off-screen at 2x, in One Dark or One Light to match this site's theme, and cropped to the component so it shows at its real size. The pointer hovers, presses or types into it; a component that does not react to the pointer cycles through a few themes. With reduced motion you see the first frame. Open **Code** under a capture to see the snippet that draws it; free names such as `on_save` stand for your own callbacks.
:::

### State

`enabled=False` also dims the name and the icon.

```python
pz.container(ui.ServiceRow(name="github", detail="npx -y server-github", status="running",
                           enabled=True), width=420)
```

```python
pz.container(ui.ServiceRow(name="github", detail="npx -y server-github", status="starting",
                           enabled=True), width=420)
```

```python
pz.container(ui.ServiceRow(name="github", detail="npx -y server-github", status="error",
                           status_text="Exited with code 1", enabled=True), width=420)
```

```python
pz.container(ui.ServiceRow(name="github", detail="npx -y server-github", status="off",
                           enabled=False), width=420)
```

```python
pz.container(ui.ServiceRow(name="Gemini CLI", version="0.64.0", icon_name="bot",
                           status="installed"), width=420)
```

```python
pz.container(ui.ServiceRow(name="Codex CLI", icon_name="bot", status="missing",
                           status_text="Not installed yet", primary="Install"), width=420)
```

```python
pz.container(ui.ServiceRow(name="Gemini CLI", version="0.64.0", icon_name="bot", status="update",
                           status_text="0.65.0 is available", primary="Update"), width=420)
```

### Options

The `⋯` menu is drawn open, as after a click on it.

```python
pz.container(ui.ServiceRow(name="notes", icon_name="book-open", status="running",
                           summary="Reads and writes the notes folder."), width=420)
```

```python
pz.container(ui.ServiceRow(name="Gemini CLI", version="0.64.0", icon_name="bot", busy=True,
                           primary="Update"), width=420)
```

```python
pz.container(ui.ServiceRow(name="github", detail="npx -y server-github", status="running", enabled=True,
                           actions=[ui.MenuItem("Edit", on_edit, icon="pencil"),
                                    ui.MenuItem("Remove", on_remove, icon="trash-2", danger=True)]),
             width=420)
```

## Events

Service row has **2 events of its own**, listed below. Like every widget it also has the **18 common events** (`click`, `double_click`, `right_click`, `mouse_enter`, `drag`, `key_down`, `resize`…), attached with `.on(...)`: see [Events](https://albakore.github.io/pyzed/guides/events/) and [Events of every component](https://albakore.github.io/pyzed/library/events/).

It is a `pz.Component`, so it also raises `on_mount` and `on_unmount`, and its handlers get a `pz.Event` whose `value` holds the data.

| Event | Handler gets | When |
| --- | --- | --- |
| `on_toggle` | `(event)` | The switch was flipped. |
| `on_primary` | `(event)` | The main button was pressed. |

**Handler gets** is what Pyzed passes to your callable: `()` means nothing. Any handler can also be a message (a value that is not callable), which goes to `update()`.

### Handling the events

The same events written three ways. **Imperative**: methods of the app change `self`. **Declarative**: the handlers produce messages and one `update()` decides. **Functional**: a pure function draws an immutable state and asks for changes with `set_state(...)`. See [Imperative, declarative and functional](https://albakore.github.io/pyzed/guides/code-styles/).

**Imperative:**

```python
class Settings(pz.App):
    def __init__(self):
        self.on = True
        self.updates = 0

    def view(self):
        return ui.ServiceRow(name="github", status="running" if self.on else "off", enabled=self.on,
                             primary="Update",
                             on_toggle=lambda e: setattr(self, "on", e.value),     # the switch
                             on_primary=lambda e: setattr(self, "updates", self.updates + 1))
```

**Declarative:**

```python
from dataclasses import dataclass

@dataclass
class Enable:
    on: bool

class Settings(pz.App):
    on = True

    def update(self, message):
        match message:
            case Enable(on):
                self.on = on

    def view(self):
        return ui.ServiceRow(name="github", enabled=self.on, status="running" if self.on else "off",
                             on_toggle=lambda e: Enable(e.value))
```

**Functional:**

```python
from dataclasses import dataclass

@dataclass(frozen=True)
class State:
    on: bool = True

def view(state, set_state):
    return ui.ServiceRow(name="github", enabled=state.on, status="running" if state.on else "off",
                         on_toggle=lambda e: set_state(on=e.value))
```

## Properties and API

### `ui.ServiceRow`

```python
class ServiceRow
```

A component for one thing an agent uses (an MCP server, an external agent, a skill), like the rows of Zed's agent settings: a status dot, the name and command, an on/off switch, a main button and a `⋯` menu.

| Property | Default | Description |
| --- | --- | --- |
| `name` | `""` | Name. |
| `detail` | `""` | The command or URL, in a monospace font. |
| `summary` | `""` | A line about it. |
| `icon_name` | `"plug"` | The icon. |
| `status` | `""` | `running`, `starting`, `error`, `off`, `installed`, `missing` or `update` (the dot's color). |
| `status_text` | `""` | The dot's tooltip; shown under the name for errors and updates. |
| `version` | `""` | A version or a source. |
| `enabled` | `None` | The switch (`None` hides it). |
| `busy` | `False` | Shows a spinner. |
| `primary` | `""` | Text of the main button (Update, Install…). |
| `actions` | `None` | `ui.MenuItem` entries of the `⋯` menu. |
| `theme` | `None` | Theme. |

Events: `on_toggle`, `on_primary` ([events](#events)).

[Full reference →](https://albakore.github.io/pyzed/reference/ui/#servicerow)

## Related

- [Catalog](https://albakore.github.io/pyzed/library/catalog/): Things to install, with a search box and Install or Update.
- [Switch](https://albakore.github.io/pyzed/library/switch/): An on/off switch for settings that apply at once.
- [Menu](https://albakore.github.io/pyzed/library/menu/): A context menu with icons, shortcuts and checks.
- [Settings group](https://albakore.github.io/pyzed/library/settings-group/): A titled block of field rows, for settings screens.
