|
1 | | -import { ClusterInfo } from '@/api/model' |
2 | | -import { loadI18n } from '@i18n-macro' |
| 1 | +import { useEffect, useMemo, useState } from 'react' |
| 2 | +import { ProColumns } from '@ant-design/pro-table' |
| 3 | +import { |
| 4 | + Button, |
| 5 | + Form, |
| 6 | + FormInstance, |
| 7 | + message, |
| 8 | + Modal, |
| 9 | + Space, |
| 10 | + Table, |
| 11 | + TableColumnsType, |
| 12 | +} from 'antd' |
| 13 | +import { |
| 14 | + // EditOutlined, |
| 15 | + ExclamationCircleOutlined, |
| 16 | + QuestionCircleOutlined, |
| 17 | + RollbackOutlined, |
| 18 | + SaveOutlined, |
| 19 | +} from '@ant-design/icons' |
| 20 | +import HeavyTable from '@/components/HeavyTable' |
| 21 | +import { ClusterInfo, ClusterParamItem } from '@/api/model' |
| 22 | +import { |
| 23 | + invalidateClusterParams, |
| 24 | + useQueryClusterParams, |
| 25 | + useUpdateClusterParams, |
| 26 | +} from '@/api/hooks/cluster' |
| 27 | +import IntlPopConfirm from '@/components/IntlPopConfirm' |
| 28 | +import { useQueryClient } from 'react-query' |
| 29 | +import { TFunction } from 'react-i18next' |
| 30 | +import { loadI18n, useI18n } from '@i18n-macro' |
| 31 | +import { Link } from 'react-router-dom' |
| 32 | +import { resolveRoute } from '@pages-macro' |
| 33 | +import { isArray } from '@/utils/types' |
| 34 | +import { renderRange } from './helper' |
| 35 | +// import { usePagination } from '@hooks/usePagination' |
3 | 36 |
|
4 | 37 | loadI18n() |
5 | 38 |
|
6 | 39 | export interface ParamsTableProps { |
7 | 40 | cluster: ClusterInfo |
8 | 41 | } |
9 | 42 |
|
| 43 | +const getRowKey = (record: ClusterParamItem) => record.paramId! |
| 44 | + |
10 | 45 | export function ParamsTable({ cluster }: ParamsTableProps) { |
11 | | - return <>{cluster.clusterId}</> |
| 46 | + const { t, i18n } = useI18n() |
| 47 | + const { |
| 48 | + paramGroupId, |
| 49 | + paramList: originalData, |
| 50 | + refetch, |
| 51 | + isLoading, |
| 52 | + } = useFetchParamsData(cluster.clusterId!) |
| 53 | + |
| 54 | + const [tableData, setTableData] = useState<ClusterParamItem[]>([]) |
| 55 | + |
| 56 | + useEffect(() => { |
| 57 | + !isLoading && setTableData(originalData!) |
| 58 | + }, [isLoading, originalData]) |
| 59 | + |
| 60 | + const [form] = Form.useForm() |
| 61 | + |
| 62 | + const queryClient = useQueryClient() |
| 63 | + const updateParams = useUpdateClusterParams() |
| 64 | + |
| 65 | + const handleSave = () => { |
| 66 | + const changes = findParamsChanges(originalData!, tableData) |
| 67 | + if (changes.length === 0) { |
| 68 | + message.info(t('save.nochanges')) |
| 69 | + } else { |
| 70 | + const rebootNeeded = changes.some((change) => change.reboot) |
| 71 | + |
| 72 | + Modal.confirm({ |
| 73 | + title: t('save.confirm'), |
| 74 | + icon: <ExclamationCircleOutlined />, |
| 75 | + width: 600, |
| 76 | + content: <ConfirmTable changes={changes} />, |
| 77 | + okText: rebootNeeded |
| 78 | + ? t('save.okText.reboot') |
| 79 | + : t('save.okText.normal'), |
| 80 | + async onOk() { |
| 81 | + updateParams.mutateAsync( |
| 82 | + { |
| 83 | + payload: { |
| 84 | + clusterId: cluster.clusterId!, |
| 85 | + params: changes.map((change) => ({ |
| 86 | + paramId: change.paramId, |
| 87 | + realValue: { |
| 88 | + clusterValue: change.change[1], |
| 89 | + }, |
| 90 | + })), |
| 91 | + // TODO |
| 92 | + reboot: rebootNeeded, |
| 93 | + }, |
| 94 | + options: { |
| 95 | + actionName: t('save.name'), |
| 96 | + }, |
| 97 | + }, |
| 98 | + { |
| 99 | + onSettled() { |
| 100 | + invalidateClusterParams(queryClient, cluster.clusterId!) |
| 101 | + }, |
| 102 | + } |
| 103 | + ) |
| 104 | + }, |
| 105 | + }) |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + const toolbar = () => [ |
| 110 | + <IntlPopConfirm |
| 111 | + key="reset" |
| 112 | + title={t('reset.confirm')} |
| 113 | + icon={<QuestionCircleOutlined style={{ color: 'red' }} />} |
| 114 | + onConfirm={() => { |
| 115 | + setTableData(originalData!) |
| 116 | + }} |
| 117 | + > |
| 118 | + <Button> |
| 119 | + <RollbackOutlined /> {t('actions.reset')} |
| 120 | + </Button> |
| 121 | + </IntlPopConfirm>, |
| 122 | + <Button type="primary" key="submit" onClick={handleSave}> |
| 123 | + <SaveOutlined /> {t('actions.save')} |
| 124 | + </Button>, |
| 125 | + ] |
| 126 | + |
| 127 | + const columns = useMemo(() => getColumns(t, form), [form, i18n.language]) |
| 128 | + |
| 129 | + return ( |
| 130 | + <HeavyTable |
| 131 | + loading={isLoading} |
| 132 | + dataSource={tableData} |
| 133 | + headerTitle={ |
| 134 | + paramGroupId ? ( |
| 135 | + <Space size="middle"> |
| 136 | + <span>{t('header.title')}</span> |
| 137 | + <Link |
| 138 | + to={`${resolveRoute('../../../')}/param-group/${paramGroupId}`} |
| 139 | + > |
| 140 | + {paramGroupId} |
| 141 | + </Link> |
| 142 | + </Space> |
| 143 | + ) : ( |
| 144 | + '' |
| 145 | + ) |
| 146 | + } |
| 147 | + tooltip={false} |
| 148 | + columns={columns} |
| 149 | + pagination={false} |
| 150 | + scroll={{ x: 1600, y: 700 }} |
| 151 | + options={{ |
| 152 | + density: false, |
| 153 | + fullScreen: false, |
| 154 | + setting: false, |
| 155 | + reload: () => refetch(), |
| 156 | + }} |
| 157 | + rowKey={getRowKey} |
| 158 | + toolBarRender={toolbar} |
| 159 | + editable={{ |
| 160 | + type: 'single', |
| 161 | + form, |
| 162 | + saveText: t('actions.confirm'), |
| 163 | + actionRender: (_, __, dom) => [dom.save, dom.cancel], |
| 164 | + onSave: async (paramId, editedRow) => { |
| 165 | + setTableData((prev) => |
| 166 | + prev.map((row) => { |
| 167 | + if (row.paramId !== paramId) return row |
| 168 | + |
| 169 | + return { |
| 170 | + ...row, |
| 171 | + realValue: { |
| 172 | + ...row.realValue, |
| 173 | + clusterValue: |
| 174 | + editedRow.realValue?.clusterValue || row.defaultValue, |
| 175 | + }, |
| 176 | + } |
| 177 | + }) |
| 178 | + ) |
| 179 | + }, |
| 180 | + }} |
| 181 | + /> |
| 182 | + ) |
| 183 | +} |
| 184 | + |
| 185 | +function getColumns(t: TFunction<''>, form: FormInstance) { |
| 186 | + const columns: ProColumns<ClusterParamItem>[] = [ |
| 187 | + { |
| 188 | + title: t('model:clusterParam.property.component'), |
| 189 | + width: 100, |
| 190 | + fixed: 'left', |
| 191 | + dataIndex: 'instanceType', |
| 192 | + editable: false, |
| 193 | + }, |
| 194 | + { |
| 195 | + title: t('model:clusterParam.property.category'), |
| 196 | + width: 100, |
| 197 | + fixed: 'left', |
| 198 | + dataIndex: 'category', |
| 199 | + editable: false, |
| 200 | + }, |
| 201 | + { |
| 202 | + title: t('model:clusterParam.property.name'), |
| 203 | + width: 160, |
| 204 | + fixed: 'left', |
| 205 | + dataIndex: 'name', |
| 206 | + editable: false, |
| 207 | + }, |
| 208 | + { |
| 209 | + title: t('model:clusterParam.property.reboot'), |
| 210 | + width: 80, |
| 211 | + key: 'hasReboot', |
| 212 | + render(_, record) { |
| 213 | + return record.hasReboot! |
| 214 | + ? t('model:clusterParam.reboot.true') |
| 215 | + : t('model:clusterParam.reboot.false') |
| 216 | + }, |
| 217 | + editable: false, |
| 218 | + }, |
| 219 | + { |
| 220 | + title: t('model:clusterParam.property.range'), |
| 221 | + width: 180, |
| 222 | + key: 'range', |
| 223 | + renderText(_, record) { |
| 224 | + return isArray(record.range) && record.range.length > 0 |
| 225 | + ? renderRange(record.type!, record.range) |
| 226 | + : null |
| 227 | + }, |
| 228 | + editable: false, |
| 229 | + }, |
| 230 | + { |
| 231 | + title: t('model:clusterParam.property.default'), |
| 232 | + width: 120, |
| 233 | + dataIndex: 'defaultValue', |
| 234 | + editable: false, |
| 235 | + }, |
| 236 | + { |
| 237 | + title: t('model:clusterParam.property.current'), |
| 238 | + width: 120, |
| 239 | + dataIndex: ['realValue', 'clusterValue'], |
| 240 | + // render(_, record, __, action) { |
| 241 | + // return ( |
| 242 | + // <span |
| 243 | + // onClick={() => { |
| 244 | + // form.resetFields() |
| 245 | + // action?.startEditable(record.paramId!) |
| 246 | + // }} |
| 247 | + // style={{ cursor: 'pointer' }} |
| 248 | + // > |
| 249 | + // <EditOutlined /> {record.realValue?.clusterValue} |
| 250 | + // </span> |
| 251 | + // ) |
| 252 | + // }, |
| 253 | + }, |
| 254 | + { |
| 255 | + title: t('columns.actions'), |
| 256 | + valueType: 'option', |
| 257 | + width: 100, |
| 258 | + render: (_, record, __, action) => { |
| 259 | + return ( |
| 260 | + <a |
| 261 | + key="editable" |
| 262 | + onClick={() => { |
| 263 | + form.resetFields() |
| 264 | + action?.startEditable?.(record.paramId!) |
| 265 | + }} |
| 266 | + > |
| 267 | + {t('actions.edit')} |
| 268 | + </a> |
| 269 | + ) |
| 270 | + }, |
| 271 | + }, |
| 272 | + { |
| 273 | + title: t('model:clusterParam.property.desc'), |
| 274 | + dataIndex: 'description', |
| 275 | + editable: false, |
| 276 | + }, |
| 277 | + ] |
| 278 | + return columns |
| 279 | +} |
| 280 | + |
| 281 | +function useFetchParamsData(id: string) { |
| 282 | + // const [pagination, setPagination] = usePagination(40) |
| 283 | + const { data, isLoading, refetch } = useQueryClusterParams( |
| 284 | + { |
| 285 | + id, |
| 286 | + }, |
| 287 | + { refetchOnWindowFocus: false } |
| 288 | + ) |
| 289 | + const result = data?.data.data |
| 290 | + |
| 291 | + return { |
| 292 | + // pagination, |
| 293 | + // setPagination, |
| 294 | + paramGroupId: result?.paramGroupId, |
| 295 | + paramList: result?.params, |
| 296 | + isLoading, |
| 297 | + refetch, |
| 298 | + } |
| 299 | +} |
| 300 | + |
| 301 | +type Change = { |
| 302 | + paramId: string |
| 303 | + component: string |
| 304 | + name: string |
| 305 | + change: [string, string] |
| 306 | + reboot: number |
| 307 | +} |
| 308 | + |
| 309 | +function findParamsChanges( |
| 310 | + origin: ClusterParamItem[], |
| 311 | + table: ClusterParamItem[] |
| 312 | +) { |
| 313 | + const changes: Change[] = [] |
| 314 | + origin.forEach((raw, i) => { |
| 315 | + if (raw.realValue?.clusterValue !== table[i].realValue?.clusterValue) { |
| 316 | + changes.push({ |
| 317 | + paramId: raw.paramId!, |
| 318 | + component: raw.instanceType!, |
| 319 | + name: raw.name!, |
| 320 | + change: [ |
| 321 | + raw.realValue!.clusterValue!, |
| 322 | + table[i].realValue!.clusterValue!, |
| 323 | + ], |
| 324 | + reboot: raw.hasReboot!, |
| 325 | + }) |
| 326 | + } |
| 327 | + }) |
| 328 | + return changes |
| 329 | +} |
| 330 | + |
| 331 | +function ConfirmTable({ changes }: { changes: Change[] }) { |
| 332 | + const { t, i18n } = useI18n() |
| 333 | + const columns: TableColumnsType<Change> = useMemo( |
| 334 | + () => [ |
| 335 | + { |
| 336 | + title: t('save.fields.component'), |
| 337 | + dataIndex: 'component', |
| 338 | + key: 'component', |
| 339 | + }, |
| 340 | + { |
| 341 | + title: t('save.fields.name'), |
| 342 | + dataIndex: 'name', |
| 343 | + key: 'name', |
| 344 | + }, |
| 345 | + { |
| 346 | + title: t('save.fields.before'), |
| 347 | + key: 'before', |
| 348 | + render: (_, record) => record.change[0], |
| 349 | + }, |
| 350 | + { |
| 351 | + title: t('save.fields.after'), |
| 352 | + key: 'after', |
| 353 | + render: (_, record) => record.change[1], |
| 354 | + }, |
| 355 | + { |
| 356 | + title: t('save.fields.reboot'), |
| 357 | + key: 'reboot', |
| 358 | + render(_, record) { |
| 359 | + return record.reboot! |
| 360 | + ? t('model:clusterParam.reboot.true') |
| 361 | + : t('model:clusterParam.reboot.false') |
| 362 | + }, |
| 363 | + }, |
| 364 | + ], |
| 365 | + [i18n.language] |
| 366 | + ) |
| 367 | + return ( |
| 368 | + <Table |
| 369 | + dataSource={changes} |
| 370 | + pagination={false} |
| 371 | + rowKey="name" |
| 372 | + size="small" |
| 373 | + columns={columns} |
| 374 | + scroll={{ y: 400 }} |
| 375 | + /> |
| 376 | + ) |
12 | 377 | } |
0 commit comments