• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

yast / d-installer / 4512499803

pending completion
4512499803

Pull #501

github

Unknown Committer
Unknown Commit Message
Pull Request #501: [web] Add DASD UI - first version

462 of 863 branches covered (53.53%)

Branch coverage included in aggregate %.

64 of 235 new or added lines in 9 files covered. (27.23%)

14 existing lines in 1 file now uncovered.

4668 of 6129 relevant lines covered (76.16%)

10.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

4.26
/web/src/components/storage/DASDTable.jsx
1
/*
2
 * Copyright (c) [2023] SUSE LLC
3
 *
4
 * All Rights Reserved.
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of version 2 of the GNU General Public License as published
8
 * by the Free Software Foundation.
9
 *
10
 * This program is distributed in the hope that it will be useful, but WITHOUT
11
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13
 * more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, contact SUSE LLC.
17
 *
18
 * To contact SUSE LLC about this file by physical or electronic mail, you may
19
 * find current contact information at www.suse.com.
20
 */
21

22
import React, { useState } from "react";
23
import {
24
  Button,
25
  Dropdown, DropdownToggle, DropdownItem, DropdownSeparator,
26
  TextInputGroup, TextInputGroupMain, TextInputGroupUtilities,
27
  Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem
28
} from '@patternfly/react-core';
29
import { TableComposable, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
30
import { sort } from 'fast-sort';
31

32
import { Icon } from "~/components/layout";
33
import { If, SectionSkeleton } from "~/components/core";
34
import { hex } from "~/utils";
35
import { useInstallerClient } from "~/context/installer";
36

37
// FIXME: please, note that this file still requiring refinements until reach a
38
//   reasonable stable version
39
const columnData = (device, column) => {
14✔
NEW
40
  let data = device[column.id];
×
41

NEW
42
  switch (column.id) {
×
43
    case 'formatted':
44
    case 'diag':
NEW
45
      if (!device.enabled)
×
NEW
46
        data = "";
×
NEW
47
      break;
×
48
    case 'partitionInfo':
NEW
49
      data = data.split(",").map(d => <div key={d}>{d}</div>);
×
NEW
50
      break;
×
51
  }
52

NEW
53
  if (typeof data === "boolean") {
×
NEW
54
    return data ? "Yes" : "No";
×
55
  }
56

NEW
57
  return data;
×
58
};
59

60
const columns = [
14✔
61
  { id: "channelId", sortId: "hexId", label: "Channel ID" },
62
  { id: "status", label: "Status" },
63
  { id: "name", label: "Device" },
64
  { id: "type", label: "Type" },
65
  { id: "diag", label: "Diag" },
66
  { id: "formatted", label: "Formatted" },
67
  { id: "partitionInfo", label: "Partition Info" }
68
];
69

70
const Actions = ({ devices, isDisabled }) => {
14✔
NEW
71
  const { storage: client } = useInstallerClient();
×
NEW
72
  const [isOpen, setIsOpen] = useState(false);
×
73

NEW
74
  const onToggle = (status) => setIsOpen(status);
×
NEW
75
  const onSelect = () => setIsOpen(false);
×
76

NEW
77
  const activate = () => client.dasd.enableDevices(devices);
×
NEW
78
  const deactivate = () => client.dasd.disableDevices(devices);
×
NEW
79
  const setDiagOn = () => client.dasd.setDIAG(devices, true);
×
NEW
80
  const setDiagOff = () => client.dasd.setDIAG(devices, false);
×
NEW
81
  const format = () => {
×
NEW
82
    const offline = devices.filter(d => !d.enabled);
×
83

NEW
84
    if (offline.length > 0) {
×
NEW
85
      return false;
×
86
    }
87

NEW
88
    return client.dasd.format(devices);
×
89
  };
90

NEW
91
  const Action = ({ children, ...props }) => (
×
NEW
92
    <DropdownItem component="button" {...props}>{children}</DropdownItem>
×
93
  );
94

NEW
95
  return (
×
96
    <Dropdown
97
      isOpen={isOpen}
98
      onSelect={onSelect}
99
      dropdownItems={[
100
        <Action key="activate" onClick={activate}>Activate</Action>,
101
        <Action key="deactivate" onClick={deactivate}>Deactivate</Action>,
102
        <DropdownSeparator key="first-separator" />,
103
        <Action key="set_diag_on" onClick={setDiagOn}>Set DIAG On</Action>,
104
        <Action key="set_diag_off" onClick={setDiagOff}>Set DIAG Off</Action>,
105
        <DropdownSeparator key="second-separator" />,
106
        <Action key="format" onClick={format}>Format</Action>
107
      ]}
108
      toggle={
109
        <DropdownToggle toggleVariant="primary" isDisabled={isDisabled} onToggle={onToggle}>
110
          Perform an action
111
        </DropdownToggle>
112
      }
113
    />
114
  );
115
};
116

117
const filterDevices = (devices, from, to) => {
14✔
NEW
118
  const allChannels = devices.map(d => d.hexId);
×
NEW
119
  const min = hex(from) || Math.min(...allChannels);
×
NEW
120
  const max = hex(to) || Math.max(...allChannels);
×
121

NEW
122
  return devices.filter(d => d.hexId >= min && d.hexId <= max);
×
123
};
124

125
export default function DASDTable({ state, dispatch }) {
NEW
126
  const [sortingColumn, setSortingColumn] = useState(columns[0]);
×
NEW
127
  const [sortDirection, setSortDirection] = useState('asc');
×
128

NEW
129
  const sortColumnIndex = () => columns.findIndex(c => c.id === sortingColumn.id);
×
NEW
130
  const filteredDevices = filterDevices(state.devices, state.minChannel, state.maxChannel);
×
NEW
131
  const selectedDevicesIds = state.selectedDevices.map(d => d.id);
×
132

133
  // Selecting
NEW
134
  const selectAll = (isSelecting = true) => {
×
NEW
135
    const type = isSelecting ? "SELECT_ALL_DEVICES" : "UNSELECT_ALL_DEVICES";
×
NEW
136
    dispatch({ type, payload: { devices: filteredDevices } });
×
137
  };
138

NEW
139
  const selectDevice = (device, isSelecting = true) => {
×
NEW
140
    const type = isSelecting ? "SELECT_DEVICE" : "UNSELECT_DEVICE";
×
NEW
141
    dispatch({ type, payload: { device } });
×
142
  };
143

144
  // Sorting
145
  // See https://github.com/snovakovic/fast-sort
NEW
146
  const sortBy = sortingColumn.sortBy || sortingColumn.id;
×
NEW
147
  const sortedDevices = sort(filteredDevices)[sortDirection](d => d[sortBy]);
×
148

149
  // FIXME: this can be improved and even extracted to be used with other tables.
NEW
150
  const getSortParams = (columnIndex) => {
×
NEW
151
    return {
×
152
      sortBy: { index: sortColumnIndex(), direction: sortDirection },
153
      onSort: (_event, index, direction) => {
NEW
154
        setSortingColumn(columns[index]);
×
NEW
155
        setSortDirection(direction);
×
156
      },
157
      columnIndex
158
    };
159
  };
160

161
  // Filtering
NEW
162
  const onMinChannelFilterChange = (_event, value) => {
×
NEW
163
    dispatch({ type: "SET_MIN_CHANNEL", payload: { minChannel: value } });
×
164
  };
165

NEW
166
  const onMaxChannelFilterChange = (_event, value) => {
×
NEW
167
    dispatch({ type: "SET_MAX_CHANNEL", payload: { maxChannel: value } });
×
168
  };
169

NEW
170
  const removeMinChannelFilter = () => {
×
NEW
171
    dispatch({ type: "SET_MIN_CHANNEL", payload: { minChannel: "" } });
×
172
  };
173

NEW
174
  const removeMaxChannelFilter = () => {
×
NEW
175
    dispatch({ type: "SET_MAX_CHANNEL", payload: { maxChannel: "" } });
×
176
  };
177

NEW
178
  return (
×
179
    <>
180
      <Toolbar>
181
        <ToolbarContent>
182
          <ToolbarGroup>
183
            <ToolbarItem>
184
              <TextInputGroup>
185
                <TextInputGroupMain
186
                  value={state.minChannel}
187
                  type="text"
188
                  aria-label="Filter by min channel"
189
                  placeholder="Filter by min channel"
190
                  onChange={onMinChannelFilterChange}
191
                />
192
                { state.minChannel !== "" &&
×
193
                  <TextInputGroupUtilities>
194
                    <Button
195
                      variant="plain"
196
                      aria-label="Remove min channel filter"
197
                      onClick={removeMinChannelFilter}
198
                    >
199
                      <Icon name="backspace" size="24" />
200
                    </Button>
201
                  </TextInputGroupUtilities> }
202
              </TextInputGroup>
203
            </ToolbarItem>
204
            <ToolbarItem>
205
              <TextInputGroup>
206
                <TextInputGroupMain
207
                  value={state.maxChannel}
208
                  type="text"
209
                  aria-label="Filter by max channel"
210
                  placeholder="Filter by max channel"
211
                  onChange={onMaxChannelFilterChange}
212
                />
213
                { state.maxChannel !== "" &&
×
214
                  <TextInputGroupUtilities>
215
                    <Button
216
                      variant="plain"
217
                      aria-label="Remove max channel filter"
218
                      onClick={removeMaxChannelFilter}
219
                    >
220
                      <Icon name="backspace" size="24" />
221
                    </Button>
222
                  </TextInputGroupUtilities> }
223
              </TextInputGroup>
224
            </ToolbarItem>
225

226
            <ToolbarItem variant="separator" />
227

228
            <ToolbarItem>
229
              <Actions devices={state.selectedDevices} isDisabled={state.selectedDevices.length === 0} />
230
            </ToolbarItem>
231
          </ToolbarGroup>
232
        </ToolbarContent>
233
      </Toolbar>
234

235
      <If
236
        condition={state.isLoading}
237
        then={<SectionSkeleton />}
238
        else={
239
          <TableComposable variant="compact">
240
            <Thead>
241
              <Tr>
NEW
242
                <Th select={{ onSelect: (_event, isSelecting) => selectAll(isSelecting), isSelected: filteredDevices.length === state.selectedDevices.length }} />
×
NEW
243
                { columns.map((column, index) => <Th key={column.id} sort={getSortParams(index)}>{column.label}</Th>) }
×
244
              </Tr>
245
            </Thead>
246
            <Tbody>
247
              { sortedDevices.map((device, rowIndex) => (
NEW
248
                <Tr key={device.id}>
×
NEW
249
                  <Td select={{ rowIndex, onSelect: (_event, isSelecting) => selectDevice(device, isSelecting), isSelected: selectedDevicesIds.includes(device.id), disable: false }} />
×
NEW
250
                  { columns.map(column => <Td key={column.id} dataLabel={column.label}>{columnData(device, column)}</Td>) }
×
251
                </Tr>
252
              ))}
253
            </Tbody>
254
          </TableComposable>
255
        }
256
      />
257
    </>
258
  );
259
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc