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