Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitc1a74fe

Browse files
committed
refactor AppsTab component
1 parentd08218f commitc1a74fe

File tree

2 files changed

+300
-8
lines changed

2 files changed

+300
-8
lines changed

‎client/packages/lowcoder/src/pages/setting/environments/WorkspaceDetail.tsx‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { appsConfig } from "./config/apps.config";
3434
import{dataSourcesConfig}from"./config/data-sources.config";
3535
import{queryConfig}from"./config/query.config";
3636

37+
importAppsTabfrom"./components/AppsTab";
3738
const{ Title, Text}=Typography;
3839
const{ TabPane}=Tabs;
3940

@@ -160,14 +161,13 @@ const WorkspaceDetail: React.FC = () => {
160161

161162
{/* Tabs for Apps, Data Sources, and Queries */}
162163
<TabsdefaultActiveKey="apps">
163-
<TabPanetab={<span><AppstoreOutlined/> Apps</span>}key="apps">
164-
<DeployableItemsTab
165-
environment={environment}
166-
config={appsConfig}
167-
additionalParams={{workspaceId:workspace.id}}
168-
title="Apps in this Workspace"
169-
/>
170-
</TabPane>
164+
// Replace the Apps TabPane in WorkspaceDetail.tsx with this:
165+
<TabPanetab={<span><AppstoreOutlined/> Apps</span>}key="apps">
166+
<AppsTab
167+
environment={environment}
168+
workspace={workspace}
169+
/>
170+
</TabPane>
171171

172172
<TabPanetab={<span><DatabaseOutlined/> Data Sources</span>}key="dataSources">
173173
<DeployableItemsTab
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
importReact,{useState,useEffect}from'react';
2+
import{Card,Button,Divider,Alert,message,Table,Tag,Input,Space,Tooltip}from'antd';
3+
import{SyncOutlined,CloudUploadOutlined,SearchOutlined}from'@ant-design/icons';
4+
importTitlefrom'antd/lib/typography/Title';
5+
import{Environment}from'../types/environment.types';
6+
import{Workspace}from'../types/workspace.types';
7+
import{App,AppStats}from'../types/app.types';
8+
import{getMergedWorkspaceApps}from'../services/apps.service';
9+
import{Switch,Spin,Empty}from'antd';
10+
import{ManagedObjectType,setManagedObject,unsetManagedObject}from'../services/managed-objects.service';
11+
import{useDeployModal}from'../context/DeployModalContext';
12+
import{appsConfig}from'../config/apps.config';
13+
14+
const{ Search}=Input;
15+
16+
interfaceAppsTabProps{
17+
environment:Environment;
18+
workspace:Workspace;
19+
}
20+
21+
constAppsTab:React.FC<AppsTabProps>=({ environment, workspace})=>{
22+
const[apps,setApps]=useState<App[]>([]);
23+
const[stats,setStats]=useState<AppStats>({
24+
total:0,
25+
published:0,
26+
managed:0,
27+
unmanaged:0
28+
});
29+
const[loading,setLoading]=useState(false);
30+
const[refreshing,setRefreshing]=useState(false);
31+
const[error,setError]=useState<string|null>(null);
32+
const[searchText,setSearchText]=useState('');
33+
const{ openDeployModal}=useDeployModal();
34+
35+
// Fetch apps
36+
constfetchApps=async()=>{
37+
if(!workspace.id||!environment)return;
38+
39+
setLoading(true);
40+
setError(null);
41+
42+
try{
43+
constresult=awaitgetMergedWorkspaceApps(
44+
workspace.id,
45+
environment.environmentId,
46+
environment.environmentApikey,
47+
environment.environmentApiServiceUrl!
48+
);
49+
50+
setApps(result.apps);
51+
52+
// Calculate stats
53+
consttotal=result.apps.length;
54+
constpublished=result.apps.filter(app=>app.published).length;
55+
constmanaged=result.apps.filter(app=>app.managed).length;
56+
57+
setStats({
58+
total,
59+
published,
60+
managed,
61+
unmanaged:total-managed
62+
});
63+
}catch(err){
64+
setError(errinstanceofError ?err.message :"Failed to fetch apps");
65+
}finally{
66+
setLoading(false);
67+
setRefreshing(false);
68+
}
69+
};
70+
71+
useEffect(()=>{
72+
fetchApps();
73+
},[environment,workspace]);
74+
75+
// Handle refresh
76+
consthandleRefresh=()=>{
77+
setRefreshing(true);
78+
fetchApps();
79+
};
80+
81+
// Toggle managed status
82+
consthandleToggleManaged=async(app:App,checked:boolean)=>{
83+
setRefreshing(true);
84+
try{
85+
if(checked){
86+
awaitsetManagedObject(
87+
app.applicationGid,
88+
environment.environmentId,
89+
ManagedObjectType.APP,
90+
app.name
91+
);
92+
}else{
93+
awaitunsetManagedObject(
94+
app.applicationGid,
95+
environment.environmentId,
96+
ManagedObjectType.APP
97+
);
98+
}
99+
100+
// Update the app in state
101+
constupdatedApps=apps.map(item=>{
102+
if(item.applicationId===app.applicationId){
103+
return{ ...item,managed:checked};
104+
}
105+
returnitem;
106+
});
107+
108+
setApps(updatedApps);
109+
110+
// Update stats
111+
constmanaged=updatedApps.filter(app=>app.managed).length;
112+
setStats(prev=>({
113+
...prev,
114+
managed,
115+
unmanaged:prev.total-managed
116+
}));
117+
118+
message.success(`${app.name} is now${checked ?'Managed' :'Unmanaged'}`);
119+
returntrue;
120+
}catch(error){
121+
message.error(`Failed to change managed status for${app.name}`);
122+
returnfalse;
123+
}finally{
124+
setRefreshing(false);
125+
}
126+
};
127+
128+
// Filter apps based on search
129+
constfilteredApps=searchText
130+
?apps.filter(app=>
131+
app.name.toLowerCase().includes(searchText.toLowerCase())||
132+
app.applicationId.toLowerCase().includes(searchText.toLowerCase()))
133+
:apps;
134+
135+
// Table columns
136+
constcolumns=[
137+
{
138+
title:'Name',
139+
dataIndex:'name',
140+
key:'name',
141+
render:(text:string)=><spanclassName="app-name">{text}</span>
142+
},
143+
{
144+
title:'ID',
145+
dataIndex:'applicationId',
146+
key:'applicationId',
147+
ellipsis:true,
148+
},
149+
{
150+
title:'Published',
151+
dataIndex:'published',
152+
key:'published',
153+
render:(published:boolean)=>(
154+
<Tagcolor={published ?'green' :'default'}>
155+
{published ?'Published' :'Draft'}
156+
</Tag>
157+
),
158+
},
159+
{
160+
title:'Managed',
161+
key:'managed',
162+
render:(_:any,app:App)=>(
163+
<Switch
164+
checked={!!app.managed}
165+
onChange={(checked:boolean)=>handleToggleManaged(app,checked)}
166+
loading={refreshing}
167+
size="small"
168+
/>
169+
),
170+
},
171+
{
172+
title:'Actions',
173+
key:'actions',
174+
render:(_:any,app:App)=>(
175+
<SpaceonClick={(e)=>e.stopPropagation()}>
176+
<Tooltiptitle={!app.managed ?"App must be managed before it can be deployed" :"Deploy this app to another environment"}>
177+
<Button
178+
type="primary"
179+
size="small"
180+
icon={<CloudUploadOutlined/>}
181+
onClick={()=>openDeployModal(app,appsConfig,environment)}
182+
disabled={!app.managed}
183+
>
184+
Deploy
185+
</Button>
186+
</Tooltip>
187+
</Space>
188+
),
189+
}
190+
];
191+
192+
return(
193+
<Card>
194+
{/* Header with refresh button */}
195+
<divstyle={{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:"16px"}}>
196+
<Titlelevel={5}>Apps in this Workspace</Title>
197+
<Button
198+
icon={<SyncOutlinedspin={refreshing}/>}
199+
onClick={handleRefresh}
200+
loading={loading}
201+
>
202+
Refresh
203+
</Button>
204+
</div>
205+
206+
{/* Stats display */}
207+
<divstyle={{display:'flex',flexWrap:'wrap',gap:'24px',marginBottom:'16px'}}>
208+
<div>
209+
<divstyle={{fontSize:'14px',color:'#8c8c8c'}}>Total Apps</div>
210+
<divstyle={{fontSize:'24px',fontWeight:600}}>{stats.total}</div>
211+
</div>
212+
<div>
213+
<divstyle={{fontSize:'14px',color:'#8c8c8c'}}>Published Apps</div>
214+
<divstyle={{fontSize:'24px',fontWeight:600}}>{stats.published}</div>
215+
</div>
216+
<div>
217+
<divstyle={{fontSize:'14px',color:'#8c8c8c'}}>Managed Apps</div>
218+
<divstyle={{fontSize:'24px',fontWeight:600}}>{stats.managed}</div>
219+
</div>
220+
<div>
221+
<divstyle={{fontSize:'14px',color:'#8c8c8c'}}>Unmanaged Apps</div>
222+
<divstyle={{fontSize:'24px',fontWeight:600}}>{stats.unmanaged}</div>
223+
</div>
224+
</div>
225+
226+
<Dividerstyle={{margin:"16px 0"}}/>
227+
228+
{/* Error display */}
229+
{error&&(
230+
<Alert
231+
message="Error loading apps"
232+
description={error}
233+
type="error"
234+
showIcon
235+
style={{marginBottom:"16px"}}
236+
/>
237+
)}
238+
239+
{/* Configuration warnings */}
240+
{(!environment.environmentApikey||!environment.environmentApiServiceUrl)&&!error&&(
241+
<Alert
242+
message="Configuration Issue"
243+
description="Missing required configuration: API key or API service URL"
244+
type="warning"
245+
showIcon
246+
style={{marginBottom:"16px"}}
247+
/>
248+
)}
249+
250+
{/* Content */}
251+
{loading ?(
252+
<divstyle={{display:'flex',justifyContent:'center',padding:'20px'}}>
253+
<Spintip="Loading apps..."/>
254+
</div>
255+
) :apps.length===0 ?(
256+
<Empty
257+
description={error||"No apps found in this workspace"}
258+
image={Empty.PRESENTED_IMAGE_SIMPLE}
259+
/>
260+
) :(
261+
<>
262+
{/* Search Bar */}
263+
<divstyle={{marginBottom:16}}>
264+
<Search
265+
placeholder="Search apps by name or ID"
266+
allowClear
267+
onSearch={value=>setSearchText(value)}
268+
onChange={e=>setSearchText(e.target.value)}
269+
style={{width:300}}
270+
/>
271+
{searchText&&filteredApps.length!==apps.length&&(
272+
<divstyle={{marginTop:8}}>
273+
Showing{filteredApps.length} of{apps.length} apps
274+
</div>
275+
)}
276+
</div>
277+
278+
<Table
279+
columns={columns}
280+
dataSource={filteredApps}
281+
rowKey="applicationId"
282+
pagination={{pageSize:10}}
283+
size="middle"
284+
scroll={{x:'max-content'}}
285+
/>
286+
</>
287+
)}
288+
</Card>
289+
);
290+
};
291+
292+
exportdefaultAppsTab;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp