Merge branch 'frontend' into 'master'
[New] Docker Image 관련 화면 추가, Navigation 추가 등 See merge request !10
Showing
6 changed files
with
327 additions
and
67 deletions
1 | -import React, { useEffect } from 'react' | 1 | +import React, { useEffect, useState } from 'react' |
2 | import { BrowserRouter, Route, Redirect, Switch, Link } from 'react-router-dom' | 2 | import { BrowserRouter, Route, Redirect, Switch, Link } from 'react-router-dom' |
3 | import { ChakraProvider } from '@chakra-ui/react' | 3 | import { ChakraProvider } from '@chakra-ui/react' |
4 | +import Navigation from './components/Navigation' | ||
4 | import Index from './components/Index' | 5 | import Index from './components/Index' |
5 | import SignIn from './components/SignIn' | 6 | import SignIn from './components/SignIn' |
6 | import SignUp from './components/SignUp' | 7 | import SignUp from './components/SignUp' |
8 | +import DockerImageCreate from './components/DockerImageCreate' | ||
9 | +import DockerImageList from './components/DockerImageList' | ||
7 | import DockerContainerCreate from './components/DockerContainerCreate' | 10 | import DockerContainerCreate from './components/DockerContainerCreate' |
8 | import DockerContainerList from './components/DockerContainerList' | 11 | import DockerContainerList from './components/DockerContainerList' |
9 | import 'semantic-ui-css/semantic.min.css' | 12 | import 'semantic-ui-css/semantic.min.css' |
... | @@ -12,12 +15,17 @@ const App = () => { | ... | @@ -12,12 +15,17 @@ const App = () => { |
12 | return ( | 15 | return ( |
13 | <ChakraProvider> | 16 | <ChakraProvider> |
14 | <BrowserRouter> | 17 | <BrowserRouter> |
18 | + <Navigation/> | ||
15 | <Switch> | 19 | <Switch> |
16 | <Route exact path="/" component={Index}/> | 20 | <Route exact path="/" component={Index}/> |
17 | <Route exact path="/sign-in" component={SignIn}/> | 21 | <Route exact path="/sign-in" component={SignIn}/> |
18 | <Route exact path="/sign-up" component={SignUp}/> | 22 | <Route exact path="/sign-up" component={SignUp}/> |
19 | - <Route exact path="/docker/container/create" component={DockerContainerCreate}/> | 23 | + <Route exact path="/docker/image/create" component={DockerImageCreate}/> |
20 | - <Route exact path="/docker/container/list" component={DockerContainerList}/> | 24 | + <Route exact path="/docker/image/list" component={DockerImageList}/> |
25 | + <Route exact path="/docker/container/create" | ||
26 | + component={DockerContainerCreate}/> | ||
27 | + <Route exact path="/docker/container/list" | ||
28 | + component={DockerContainerList}/> | ||
21 | <Redirect path="*" to="/"/> | 29 | <Redirect path="*" to="/"/> |
22 | </Switch> | 30 | </Switch> |
23 | </BrowserRouter> | 31 | </BrowserRouter> | ... | ... |
... | @@ -63,37 +63,6 @@ const DockerContainerList = () => { | ... | @@ -63,37 +63,6 @@ const DockerContainerList = () => { |
63 | </ListItem> | 63 | </ListItem> |
64 | ))} | 64 | ))} |
65 | </UnorderedList> | 65 | </UnorderedList> |
66 | - | ||
67 | - <Stack | ||
68 | - divider={<StackDivider borderColor="gray.200"/>} | ||
69 | - spacing={4} | ||
70 | - align="stretch" | ||
71 | - > | ||
72 | - | ||
73 | - | ||
74 | - <FormControl id="container" isRequired> | ||
75 | - <FormLabel>Container Name</FormLabel> | ||
76 | - <Input | ||
77 | - type="email" | ||
78 | - placeholder="Container Name" | ||
79 | - /> | ||
80 | - </FormControl> | ||
81 | - | ||
82 | - <FormControl id="image"> | ||
83 | - <FormLabel>Image</FormLabel> | ||
84 | - <Select placeholder="생성할 컨테이너의 OS를 설정해주세요"> | ||
85 | - <option value="ubuntu 18.04">Ubuntu 18.04.5 LTS</option> | ||
86 | - <option value="ubuntu 20.04">Ubuntu 20.04.2 LTS</option> | ||
87 | - </Select> | ||
88 | - </FormControl> | ||
89 | - | ||
90 | - </Stack> | ||
91 | - | ||
92 | - <Stack> | ||
93 | - | ||
94 | - </Stack> | ||
95 | - | ||
96 | - | ||
97 | </Container> | 66 | </Container> |
98 | ) | 67 | ) |
99 | } | 68 | } | ... | ... |
frontend/src/components/DockerImageCreate.js
0 → 100644
1 | +import React, { useEffect, useState } from 'react' | ||
2 | +import { | ||
3 | + Container, | ||
4 | + Heading, | ||
5 | + Button, | ||
6 | + Text, | ||
7 | + Box, Alert, AlertIcon, | ||
8 | + AlertDialog, | ||
9 | + AlertDialogContent, | ||
10 | + AlertDialogOverlay, | ||
11 | +} from '@chakra-ui/react' | ||
12 | +import { | ||
13 | + Divider, | ||
14 | + TextArea, | ||
15 | + Header, | ||
16 | + Grid, | ||
17 | + Form, | ||
18 | +} from 'semantic-ui-react' | ||
19 | +import http from '../utils/http' | ||
20 | +import { useHistory } from 'react-router-dom' | ||
21 | + | ||
22 | + | ||
23 | +const DockerImageCreate = () => { | ||
24 | + const [token, setToken] = useState('') | ||
25 | + const [dockerfile, setDockerfile] = useState('') | ||
26 | + const [isSuccess, setSuccess] = useState(false) | ||
27 | + | ||
28 | + const history = useHistory() | ||
29 | + | ||
30 | + const uploadDockerfile = () => { | ||
31 | + http.post('/dockerfile/add', { | ||
32 | + content: dockerfile, | ||
33 | + }, { | ||
34 | + headers: { | ||
35 | + Authorization: `token ${token}`, | ||
36 | + } | ||
37 | + }).then(response => { | ||
38 | + const {success, data, message} = response.data | ||
39 | + if (success) { | ||
40 | + setSuccess(success) | ||
41 | + setTimeout(2000, () => { | ||
42 | + history.push('/docker/image/list') | ||
43 | + }) | ||
44 | + } | ||
45 | + }) | ||
46 | + } | ||
47 | + | ||
48 | + useEffect(() => { | ||
49 | + const token = localStorage.getItem('token') | ||
50 | + setToken(token) | ||
51 | + }, []) | ||
52 | + | ||
53 | + return ( | ||
54 | + <Container> | ||
55 | + <Divider hidden/> | ||
56 | + <Heading>Upload Your Dockerfile</Heading> | ||
57 | + | ||
58 | + <Divider hidden /> | ||
59 | + | ||
60 | + <Form> | ||
61 | + <span>dockerfile : </span> | ||
62 | + <TextArea | ||
63 | + value={dockerfile} | ||
64 | + rows={dockerfile.split('\n').length + 2} | ||
65 | + onChange={(e, {value}) => setDockerfile(value)} | ||
66 | + /> | ||
67 | + </Form> | ||
68 | + <Divider hidden/> | ||
69 | + <Grid> | ||
70 | + <Grid.Column | ||
71 | + width={3} | ||
72 | + floated='right' | ||
73 | + > | ||
74 | + <Button | ||
75 | + size='sm' | ||
76 | + colorScheme='teal' | ||
77 | + onClick={() => uploadDockerfile()} | ||
78 | + > | ||
79 | + Upload | ||
80 | + </Button> | ||
81 | + </Grid.Column> | ||
82 | + </Grid> | ||
83 | + | ||
84 | + | ||
85 | + <AlertDialog | ||
86 | + isOpen={isSuccess} | ||
87 | + onClose={() => setSuccess(false)} | ||
88 | + > | ||
89 | + <AlertDialogOverlay> | ||
90 | + <AlertDialogContent> | ||
91 | + <Alert status="success"> | ||
92 | + <AlertIcon /> | ||
93 | + Successfully uploaded ! | ||
94 | + </Alert> | ||
95 | + </AlertDialogContent> | ||
96 | + </AlertDialogOverlay> | ||
97 | + </AlertDialog> | ||
98 | + | ||
99 | + </Container> | ||
100 | + ) | ||
101 | +} | ||
102 | + | ||
103 | +export default DockerImageCreate |
frontend/src/components/DockerImageList.js
0 → 100644
1 | +import React, { useEffect, useState } from 'react' | ||
2 | +import { | ||
3 | + Container, | ||
4 | + Heading, | ||
5 | + Button, | ||
6 | + Text, | ||
7 | + Box, | ||
8 | +} from '@chakra-ui/react' | ||
9 | +import { | ||
10 | + Divider, Grid, | ||
11 | + Header, | ||
12 | +} from 'semantic-ui-react' | ||
13 | +import http from '../utils/http' | ||
14 | +import { useHistory } from 'react-router-dom' | ||
15 | + | ||
16 | +const ImageBlock = ({ title, desc, ...rest }) => { | ||
17 | + return ( | ||
18 | + <Box p={5} shadow="md" borderWidth="1px" {...rest}> | ||
19 | + <Heading fontSize="xl">{title}</Heading> | ||
20 | + <Text mt={4}>{desc}</Text> | ||
21 | + </Box> | ||
22 | + ) | ||
23 | +} | ||
24 | + | ||
25 | +const DockerImageList = () => { | ||
26 | + const [token, setToken] = useState('') | ||
27 | + const [images, setImages] = useState([]) | ||
28 | + | ||
29 | + const history = useHistory() | ||
30 | + | ||
31 | + const setTokenFromLocalStorage = () => { | ||
32 | + const token = localStorage.getItem('token') | ||
33 | + setToken(token) | ||
34 | + } | ||
35 | + | ||
36 | + const getImages = () => { | ||
37 | + http.get('/image/list', { | ||
38 | + headers: { | ||
39 | + Authorization: `token ${token}`, | ||
40 | + }, | ||
41 | + }).then(response => { | ||
42 | + const { success, code, data, message } = response.data | ||
43 | + if (success) { | ||
44 | + const { count, data } = data | ||
45 | + setImages(data) | ||
46 | + } | ||
47 | + }) | ||
48 | + } | ||
49 | + | ||
50 | + useEffect(() => { | ||
51 | + setTokenFromLocalStorage() | ||
52 | + getImages() | ||
53 | + }, []) | ||
54 | + | ||
55 | + return ( | ||
56 | + <Container> | ||
57 | + <Divider hidden /> | ||
58 | + <Heading>Image List</Heading> | ||
59 | + <Divider hidden /> | ||
60 | + <Grid> | ||
61 | + <Grid.Column | ||
62 | + floated='right' width={3} | ||
63 | + > | ||
64 | + <Button | ||
65 | + size='xs' | ||
66 | + colorScheme='blue' | ||
67 | + onClick={() => history.push('/docker/image/create')} | ||
68 | + > | ||
69 | + New Image | ||
70 | + </Button> | ||
71 | + </Grid.Column> | ||
72 | + </Grid> | ||
73 | + {images.map(image => ( | ||
74 | + <ImageBlock | ||
75 | + title={image.name} | ||
76 | + desc={image.name} | ||
77 | + /> | ||
78 | + ))} | ||
79 | + </Container> | ||
80 | + ) | ||
81 | +} | ||
82 | + | ||
83 | +export default DockerImageList | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
1 | -import React, { useEffect } from 'react' | 1 | +import React, { useEffect, useState } from 'react' |
2 | -import { BrowserRouter, Route, Redirect, Switch, Link, useHistory } from 'react-router-dom' | 2 | +import { |
3 | + BrowserRouter, | ||
4 | + Route, | ||
5 | + Redirect, | ||
6 | + Switch, | ||
7 | + Link, | ||
8 | + useHistory, | ||
9 | +} from 'react-router-dom' | ||
3 | import { | 10 | import { |
4 | Container, | 11 | Container, |
5 | Heading, | 12 | Heading, |
... | @@ -34,9 +41,10 @@ import { | ... | @@ -34,9 +41,10 @@ import { |
34 | Wrap, | 41 | Wrap, |
35 | } from '@chakra-ui/react' | 42 | } from '@chakra-ui/react' |
36 | import { | 43 | import { |
44 | + Tab, | ||
37 | Divider, | 45 | Divider, |
38 | } from 'semantic-ui-react' | 46 | } from 'semantic-ui-react' |
39 | - | 47 | +import http from '../utils/http' |
40 | 48 | ||
41 | const Feature = ({ title, desc, ...rest }) => { | 49 | const Feature = ({ title, desc, ...rest }) => { |
42 | return ( | 50 | return ( |
... | @@ -47,59 +55,107 @@ const Feature = ({ title, desc, ...rest }) => { | ... | @@ -47,59 +55,107 @@ const Feature = ({ title, desc, ...rest }) => { |
47 | ) | 55 | ) |
48 | } | 56 | } |
49 | 57 | ||
50 | - | ||
51 | const Index = () => { | 58 | const Index = () => { |
59 | + const [token, setToken] = useState('') | ||
60 | + const [currentTab, setCurrentTab] = useState('') | ||
61 | + const [me, setMe] = useState({}) | ||
62 | + | ||
52 | const history = useHistory() | 63 | const history = useHistory() |
53 | 64 | ||
65 | + const panes = [ | ||
66 | + { | ||
67 | + menuItem: 'Tab 1', | ||
68 | + render: () => <Tab.Pane attached={false}>Tab 1 Content</Tab.Pane>, | ||
69 | + }, | ||
70 | + { | ||
71 | + menuItem: 'Tab 2', | ||
72 | + render: () => <Tab.Pane attached={false}>Tab 2 Content</Tab.Pane>, | ||
73 | + }, | ||
74 | + { | ||
75 | + menuItem: 'Tab 3', | ||
76 | + render: () => <Tab.Pane attached={false}>Tab 3 Content</Tab.Pane>, | ||
77 | + }, | ||
78 | + ] | ||
79 | + | ||
80 | + const whoAmI = () => { | ||
81 | + http.get('/info/user', { | ||
82 | + headers: { | ||
83 | + Authorization: `token ${token}`, | ||
84 | + }, | ||
85 | + }).then(response => { | ||
86 | + const { success, code, data, message } = response.data | ||
87 | + if (success) { | ||
88 | + setMe(data) | ||
89 | + } | ||
90 | + }) | ||
91 | + } | ||
92 | + | ||
93 | + useEffect(() => { | ||
94 | + const token = localStorage.getItem('token') | ||
95 | + setToken(token) | ||
96 | + | ||
97 | + if (token) { | ||
98 | + whoAmI() | ||
99 | + } | ||
100 | + }) | ||
101 | + | ||
54 | return ( | 102 | return ( |
55 | <Container> | 103 | <Container> |
56 | - <Divider hidden /> | 104 | + <Divider hidden/> |
57 | - <Wrap justify="right"> | 105 | + |
58 | - <Button size="xs" colorScheme="blue" variant="outline" | 106 | + {me.name ? ( |
59 | - onClick={() => history.push('/sign-in') } | 107 | + <Heading size="md"> |
60 | - > | 108 | + Hi {me.name} |
61 | - SIGN IN | 109 | + </Heading> |
62 | - </Button> | 110 | + ) : ( |
63 | - <Button size="xs" colorScheme="blue" | 111 | + <Wrap justify="right"> |
64 | - onClick={() => history.push('/sign-up') } | 112 | + <Button size="xs" colorScheme="blue" variant="outline" |
65 | - > | 113 | + onClick={() => history.push('/sign-in')} |
66 | - SIGN UP | 114 | + > |
67 | - </Button> | 115 | + SIGN IN |
68 | - </Wrap> | 116 | + </Button> |
117 | + <Button size="xs" colorScheme="blue" | ||
118 | + onClick={() => history.push('/sign-up')} | ||
119 | + > | ||
120 | + SIGN UP | ||
121 | + </Button> | ||
122 | + </Wrap> | ||
123 | + )} | ||
69 | 124 | ||
70 | <Heading>The Infrastructure Cloud</Heading> | 125 | <Heading>The Infrastructure Cloud</Heading> |
71 | - <Text>Easily deploy cloud servers, bare metal, and storage worldwide!</Text> | 126 | + <Text>Easily deploy cloud servers, bare metal, and storage |
127 | + worldwide!</Text> | ||
72 | 128 | ||
73 | - <Divider hidden /> | 129 | + <Divider hidden/> |
74 | 130 | ||
75 | 131 | ||
76 | - <Stack spacing={8}> | 132 | + <Stack spacing={8}> |
77 | <Feature | 133 | <Feature |
78 | - title='Cloud Compute' | 134 | + title="Cloud Compute" |
79 | - desc='Powerful compute instances with Intel CPUs and 100% SSD storage.' | 135 | + desc="Powerful compute instances with Intel CPUs and 100% SSD storage." |
80 | /> | 136 | /> |
81 | 137 | ||
82 | <Feature | 138 | <Feature |
83 | - title='Bare Metal' | 139 | + title="Bare Metal" |
84 | - desc='Fully automated dedicated servers with zero virtualization layer.' | 140 | + desc="Fully automated dedicated servers with zero virtualization layer." |
85 | /> | 141 | /> |
86 | - </Stack > | 142 | + </Stack> |
87 | 143 | ||
88 | - <Divider hidden /> | 144 | + <Divider hidden/> |
89 | 145 | ||
90 | - <Stack spacing={8}> | 146 | + <Stack spacing={8}> |
91 | <Feature | 147 | <Feature |
92 | - title='Block Storage' | 148 | + title="Block Storage" |
93 | - desc='Fast SSD-backed scalable and redundant storage with up to 10TB volumes.' | 149 | + desc="Fast SSD-backed scalable and redundant storage with up to 10TB volumes." |
94 | /> | 150 | /> |
95 | 151 | ||
96 | <Feature | 152 | <Feature |
97 | - title='Dedicated Cloud' | 153 | + title="Dedicated Cloud" |
98 | - desc='Dedicated cloud compute instances without the noisy neighbors.' | 154 | + desc="Dedicated cloud compute instances without the noisy neighbors." |
99 | /> | 155 | /> |
100 | - </Stack > | 156 | + </Stack> |
101 | 157 | ||
102 | - <Divider hidden /> | 158 | + <Divider hidden/> |
103 | </Container> | 159 | </Container> |
104 | ) | 160 | ) |
105 | } | 161 | } | ... | ... |
frontend/src/components/Navigation.js
0 → 100644
1 | +import { | ||
2 | + Container, | ||
3 | +} from '@chakra-ui/react' | ||
4 | +import { | ||
5 | + Tab, | ||
6 | + Divider, | ||
7 | +} from 'semantic-ui-react' | ||
8 | +import React, { useState } from 'react' | ||
9 | +import { Redirect } from 'react-router-dom' | ||
10 | + | ||
11 | +const Navigation = () => { | ||
12 | + const [activeIndex, setActiveIndex] = useState(0) | ||
13 | + | ||
14 | + const panes = [ | ||
15 | + { | ||
16 | + menuItem: 'Home', | ||
17 | + render: () => <Redirect to="/"/>, | ||
18 | + }, | ||
19 | + { | ||
20 | + menuItem: 'Image', | ||
21 | + render: () => <Redirect to="/docker/image/list"/>, | ||
22 | + }, | ||
23 | + { | ||
24 | + menuItem: 'Container', | ||
25 | + render: () => <Redirect to="/docker/container/list"/>, | ||
26 | + }, | ||
27 | + ] | ||
28 | + | ||
29 | + return ( | ||
30 | + <Container> | ||
31 | + <Divider hidden/> | ||
32 | + <Tab | ||
33 | + panes={panes} | ||
34 | + menu={{ secondary: true, pointing: true }} | ||
35 | + onTabChange={(e, { activeIndex }) => setActiveIndex(activeIndex)} | ||
36 | + /> | ||
37 | + </Container> | ||
38 | + ) | ||
39 | +} | ||
40 | + | ||
41 | +export default Navigation |
-
Please register or login to post a comment