프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py    
2 demo_board > demo_board.py    
3 demo_board > demo_state.py    
4 demo_board > demo_auth.py    
5 demo_board > demo_servers.py 조회조건 선택후 "검색" 시 Progress 보여주기 수정
6 demo_board > demo_helpers.py    

 

기존 검색 버튼의 on_click 이벤트에 다중 이벤트로 Progress 를 붙여서 데이터 조회중이라는 상태를 표시하도록 한다.

 

필요한 컴포넌트 

Modal (https://pynecone.io/docs/library/overlay/modal)

 

1. Modal 컴포넌트를 생성

여기서는 Circlar Progress를 사용했습니다. 새로 함수 추가

def progress_modal(ServerState):
    """Display for an progress circle."""
    return pc.modal(
        pc.modal_overlay(
            pc.modal_content(
                pc.modal_header("Your service request is being processed."),
                pc.modal_body(
                    pc.center(
                        pc.circular_progress(is_indeterminate=True),
                    ),
                ),
                align="center",
            ),
        ),
        is_open=ServerState.prog_show,
    )

 

2. 생성한 Modal을 메인 UI 함수에 추가

progress_modal(ServerState)를 추가합니다.

def servers():
    """The Servers page."""
    return pc.center(
        pc.vstack(
            navbar(State, ServerState.app_name),
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        searchbar(ServerState),
                        progress_modal(ServerState),
                        render_table(ServerState),
                        #render_datatable(ServerState),
                        footer_bottom(),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="10em",
            width="100%",
        ),
    )

 

 

3. Modal 컴포넌트 이벤트 함수 등록

1번, 2번 코드를 기존 코드에 추가합니다.

class ServerState(State):
    """The state for the Server page."""
    print("──────────────────── [ ServerState(State) ] ─────────────────────")
    app_name: str = "Server & Apps"
    
    
    생략 ...
    
    # 1. Progress 를 보여줄지 말지 결정할 Toggle 이벤트
    prog_show: bool = False
    def prog_show_change(self):
        """Toggle the Update Progress modal."""
        self.prog_show = not (self.prog_show)
        
    생략 ...
        
    def get_data_list(self):
        #print("\n[ServerState] get_data_list")
        pd_result_list: List[Dict] = get_server_data(self.select1_id, self.select2_id)
        self.table_columns = list(pd_result_list.columns)
        self.table_data = pd_result_list.values.tolist()
        #print("[ServerState] get_data_list pd_result_list len : {}\n".format(len(pd_result_list)))

        # 2. 기존 "검색" 버튼 클릭시 데이터 조회하는 Class 함수에 
        # Progress 컨포넌트 상태 변경 이벤트를 호출
        return self.prog_show_change()

 

4. "검색" 버튼의 on_cllick 이벤트에 Progress를 띄우도록 다중 이벤트 등록

on_click=[ServerState.prog_show_change,ServerState.get_data_list]

먼저 prog_show_change 이벤트로 Progress Modal을 띄우고 get_data_list로 데이터 조회를 합니다.

def searchbar(ServerState):
    """The searchbar."""
    return pc.box(
    
    생략 ...
    
                pc.button(
                    "검색", #Search
                    bg="navy", #gray
                    color="white", 
                    size="md",
                    width="150px",
                    #on_click=ServerState.get_data_list,
                    on_click=[ServerState.prog_show_change,ServerState.get_data_list],
                ),

 

ServerState Class 함수의 get_data_list 에 리턴으로 상태변경 Toggle 함수를 호출 하도록 했기때문에 데이터 조회가 끝나면 함수가 호출되어 Progress Modal이 닫기게 됩니다.

프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py    
2 demo_board > demo_board.py    
3 demo_board > demo_state.py    
4 demo_board > demo_auth.py    
5 demo_board > demo_servers.py 조회조건 선택후 "검색" 시 Progress 보여주기 수정
6 demo_board > demo_helpers.py    

 

데이터 조회시 시간이 오래 걸리게 되면 화면에서 사용자가 진행되고있는지 상태를 알수있도록, 기존 검색 버튼의 on_click 이벤트에 다중 이벤트로 Progress 를 붙여서 데이터 조회중이라는 상태를 표시하도록 한다.

 

 

필요한 컴포넌트 

Modal (https://pynecone.io/docs/library/overlay/modal)

 

1. Modal 컴포넌트를 생성

여기서는 Circlar Progress를 사용했습니다. 새로 함수 추가

def progress_modal(ServerState):
    """Display for an progress circle."""
    return pc.modal(
        pc.modal_overlay(
            pc.modal_content(
                pc.modal_header("Your service request is being processed."),
                pc.modal_body(
                    pc.center(
                        pc.circular_progress(is_indeterminate=True),
                    ),
                ),
                align="center",
            ),
        ),
        is_open=ServerState.prog_show,
    )

 

2. 생성한 Modal을 메인 UI 함수에 추가

progress_modal(ServerState)를 추가합니다.

def servers():
    """The Servers page."""
    return pc.center(
        pc.vstack(
            navbar(State, ServerState.app_name),
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        searchbar(ServerState),
                        progress_modal(ServerState),
                        render_table(ServerState),
                        #render_datatable(ServerState),
                        footer_bottom(),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="10em",
            width="100%",
        ),
    )

 

 

3. Modal 컴포넌트 이벤트 함수 등록

1번, 2번 코드를 기존 코드에 추가합니다.

class ServerState(State):
    """The state for the Server page."""
    print("──────────────────── [ ServerState(State) ] ─────────────────────")
    app_name: str = "Server & Apps"
    
    
    생략 ...
    
    # 1. Progress 를 보여줄지 말지 결정할 Toggle 이벤트
    prog_show: bool = False
    def prog_show_change(self):
        """Toggle the Update Progress modal."""
        self.prog_show = not (self.prog_show)
        
    생략 ...
        
    def get_data_list(self):
        #print("\n[ServerState] get_data_list")
        pd_result_list: List[Dict] = get_server_data(self.select1_id, self.select2_id)
        self.table_columns = list(pd_result_list.columns)
        self.table_data = pd_result_list.values.tolist()
        #print("[ServerState] get_data_list pd_result_list len : {}\n".format(len(pd_result_list)))

        # 2. 기존 "검색" 버튼 클릭시 데이터 조회하는 Class 함수에 
        # Progress 컨포넌트 상태 변경 이벤트를 호출
        return self.prog_show_change()

 

4. "검색" 버튼의 on_cllick 이벤트에 Progress를 띄우도록 다중 이벤트 등록

on_click=[ServerState.prog_show_change,ServerState.get_data_list]

먼저 prog_show_change 이벤트로 Progress Modal을 띄우고 get_data_list로 데이터 조회를 합니다.

def searchbar(ServerState):
    """The searchbar."""
    return pc.box(
    
    생략 ...
    
                pc.button(
                    "검색", #Search
                    bg="navy", #gray
                    color="white", 
                    size="md",
                    width="150px",
                    #on_click=ServerState.get_data_list,
                    on_click=[ServerState.prog_show_change,ServerState.get_data_list],
                ),

 

ServerState Class 함수의 get_data_list 에 리턴으로 상태변경 Toggle 함수를 호출 하도록 했기때문에 데이터 조회가 끝나면 함수가 호출되어 Progress Modal이 닫기게 됩니다.

프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py    
2 demo_board > demo_board.py    
3 demo_board > demo_state.py    
4 demo_board > demo_auth.py    
5 demo_board > demo_servers.py 다중 Select 컴포넌트 추가 및 Dummy 데이터 수정
6 demo_board > demo_helpers.py    

 

서버 모니터링 demo_servers.py 에서 선택되는 Select 컴포넌트의 값에 따라서 여러 종류의 서버 유형의 결과를 조회하기위해서

Table 상단에 다중 Select 를 추가합니다.

 

수정전

 

수정후

 

Select 항목 선택후 "검색" 결과

수정된 내용

 

SeverState에서 사용할 함수들..

# Select 컴포넌트 2개에 표시될 데이터 List[Dict] Dummy 를 Dataframe으로 리턴
def get_select_data():
    result_list =  [
        {'service_id': 'app_mng', 'parent_id': 'servers', 'service_nm': '인프라 서버 관리'}, 
        {'service_id': 'app_web', 'parent_id': 'app_mng', 'service_nm': 'WEB 서버 관리'},
        {'service_id': 'app_was', 'parent_id': 'app_mng', 'service_nm': 'WAS 서버 관리'},
        {'service_id': 'app_rds', 'parent_id': 'app_mng', 'service_nm': 'DB 서버 관리'}
    ]
    result_list = pd.DataFrame(result_list)
    return result_list

# Dummy 를 Dataframe에서 상위 즉 parent_id가 동일한 항목들을 List로 리턴
def get_select_options(select_list, parent_id):
    return select_list[select_list['parent_id']==parent_id]['service_nm'].values.tolist()

# Dummy 를 Dataframe에서 선택된 service_name 으로 해당 Dict의 service_id를 찾아서 리턴
def get_select_id(select_list, select1_id, option):
    selected_parent = select_list[select_list['parent_id']==select1_id]
    select_selected=selected_parent[selected_parent['service_nm']==option]

    select_id=""
    if len(select_selected) > 0:
        select_id=select_selected['service_id'].values[0] 

    return select_id

 

class ServerState(State):

class ServerState(State):
    """The Substate of base state for the server page."""
    print("──────────────────── [ ServerState(State) ] ────────────────────")
    app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명

    # (추가됨)
    # select 컴포넌트에서 사용할 Dummy 데이타
    select_list = get_select_data() 

    # 화면 로드시에 첫번째 Select 컴포넌트에 값을 세팅
    select1_options: list[str] = get_select_options(select_list, "servers")
    # 두번째 Select는 빈값으로 두고 첫번째 Select가 선택될때 이벤트를 받아서 항목을 추가
    select2_options: list[str] = []

    # 현재 선택된 Select 항목의 값들을 저장하기위한 vars
    select1_id: str = "" 
    select2_id: str = "" 
    
    select1_value: str = "" 
    select2_value: str = "" 
    
    ...(생략)

    # (추가됨) Select 컴포넌트에서 on_change 이벤트가 발생할때 호출
    def select_change(self, level, value):
    
    	# 선택항목이 select1 이면, 
        if(level == "select1"):
            self.select1_id="" 
            self.select1_value=""
            self.select2_id=""
            self.select2_value="" 
            self.select2_options=[]
            
            # 선택된 Option service_nm 값으로 해당 select_id를 찾아온다.
            select_id = get_select_id(self.select_list, "servers", value)
            
            self.select1_id=select_id
            self.select1_value=value
            # 앞서 찾은 select1의 id 값으로 select2 컴포넌트를위한 옵션 List를 조회/등록
            self.select2_options=get_select_options(self.select_list, select_id)

        elif(level == "select2"):
        	# Select2 가 선택됐을때는 선택된 service_nm에 대한 service_id 를 찾아 var에 저장
            select_id = get_select_id(self.select_list, self.select1_id, value)
            self.select2_id=select_id
            self.select2_value=value
        else:
            return

    # (추가됨) select_change에서 선택된 값들을 해서 "검색" 버튼 클릭시 데이터를 조회
    def get_data_list(self):
        pd_result_list: List[Dict] = get_data(self.select2_id)
        self.table_columns = list(pd_result_list.columns)
        self.table_data = pd_result_list.values.tolist()

 

다중 Select 컴포넌트 그리기

def search_select(ServerState):
    """The Multi Select combo."""
    return pc.box(
        pc.hstack(
            pc.hstack(
                pc.select(
                    ServerState.select1_options,
                    placeholder="Select an Option.",
                    on_change=lambda value: ServerState.select_change('select1', value),
                    #is_disabled=True,
                    border_style="solid",
                    border_width="1px",
                    border_color="#43464B",
                    value=ServerState.select1_value,
                ),
                pc.select(
                    ServerState.select2_options,
                    placeholder="Select an Option.",
                    on_change=lambda value: ServerState.select_change('select2', value),
                    #is_disabled=True,
                    border_style="solid",
                    border_width="1px",
                    border_color="#43464B",
                    value=ServerState.select2_value,
                ),
                pc.button(
                    "검색", #Search
                    bg="navy", #gray
                    color="white", 
                    size="md",
                    width="150px",
                    on_click=ServerState.get_data_list,
                ),
                border_width="0px",
            ),
            border_width="0px",
        ),
        #position="fixed",
        #width="40%",
        #top="105px",
        z_index="500",
    )

 

마지막으로 "/server" 함수에 search_select 추가

def servers():
    return pc.box(
        pc.vstack(
            navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가 
            pc.cond(
                State.logged_in,
                pc.box(
                        #pc.heading("서버 모니터링", font_size="2em"),
                        #pc.heading("서버 모니터링"),
                        search_select(ServerState), # search_select 추가
                        render_table(ServerState),
                        #pc.divider(),
                        #render_datatable(ServerState),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/login",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="5.5em",
            width="100%",
        ),
    )

 

전체소스

import pynecone as pc

import pandas as pd
from typing import List, Dict

from .demo_state import State # Substate of Base State
from .demo_helpers import navbar

def get_select_data():
    result_list =  [
        {'service_id': 'app_mng', 'parent_id': 'servers', 'service_nm': '인프라 서버 관리'}, 
        {'service_id': 'app_web', 'parent_id': 'app_mng', 'service_nm': 'WEB 서버 관리'},
        {'service_id': 'app_was', 'parent_id': 'app_mng', 'service_nm': 'WAS 서버 관리'},
        {'service_id': 'app_rds', 'parent_id': 'app_mng', 'service_nm': 'DB 서버 관리'}
    ]
    result_list = pd.DataFrame(result_list)
    return result_list

def get_select_options(select_list, parent_id):
    return select_list[select_list['parent_id']==parent_id]['service_nm'].values.tolist()

def get_select_id(select_list, select1_id, option):
    selected_parent = select_list[select_list['parent_id']==select1_id]
    select_selected=selected_parent[selected_parent['service_nm']==option]

    select_id=""
    if len(select_selected) > 0:
        select_id=select_selected['service_id'].values[0] 

    return select_id

def get_data(server_type):
    # 샘플 데이터 List[Dict]
    if server_type == "" or server_type == None:
        result_list = [{'Result': 'No data found (검색 조건을 선택한 후 조회 버튼을 클릭하세요)'}]
    else:
        result_list = [
            {"Server Type": server_type, "Server Name": "alpha", "Total Disk": "10G", "Used Disk": "5G", "Available Disk": "5G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
            {"Server Type": server_type, "Server Name": "brovo", "Total Disk": "10G", "Used Disk": "5G", "Available Disk": "5G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
            {"Server Type": server_type, "Server Name": "charlie", "Total Disk": "10G", "Used Disk": "5G", "Available Disk": "5G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
            {"Server Type": server_type, "Server Name": "delta", "Total Disk": "10G", "Used Disk": "5G", "Available Disk": "5G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
            {"Server Type": server_type, "Server Name": "echo", "Total Disk": "10G", "Used Disk": "5G", "Available Disk": "5G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        ]

    pd_result_list = pd.DataFrame(result_list)
    pd_result_list=pd_result_list.fillna('')   # NaN값을 0 or ''로 치환한다.
    pd_result_list=pd_result_list.astype(str)  # 전체 Field를 str로 변환한다.

    return pd_result_list


class ServerState(State):
    """The Substate of base state for the server page."""
    print("──────────────────── [ ServerState(State) ] ────────────────────")
    app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명

    select_list = get_select_data()

    select1_options: list[str] = get_select_options(select_list, "servers")
    select2_options: list[str] = []

    select1_id: str = "" 
    select2_id: str = "" 
    
    select1_value: str = "" 
    select2_value: str = "" 


    pd_result_list: List[Dict] = get_data("")
    #print(pd_result_list)

    # table_columns : Dict에서 Columns 정보를 가져와서 Table의 thead를 구성
    # table_data : Dataframe에서 value만 List로 만들어서 Table의 tbody를 구성
    # table_name : Table의 table_caption 

    table_columns = list(pd_result_list.columns)
    table_data: List[List[str]] = pd_result_list.values.tolist()
    table_name: str = "WEB 서버 상태 확인"

    box_align: str = ""

    def select_change(self, level, value):

        if(level == "select1"):
            self.select1_id="" 
            self.select1_value=""
            self.select2_id=""
            self.select2_value="" 
            self.select2_options=[]
            
            select_id = get_select_id(self.select_list, "servers", value)
            
            self.select1_id=select_id
            self.select1_value=value
            self.select2_options=get_select_options(self.select_list, select_id)

        elif(level == "select2"):
            select_id = get_select_id(self.select_list, self.select1_id, value)
            self.select2_id=select_id
            self.select2_value=value
        else:
            return

    def get_data_list(self):
        pd_result_list: List[Dict] = get_data(self.select2_id)
        self.table_columns = list(pd_result_list.columns)
        self.table_data = pd_result_list.values.tolist()


def servers():
    return pc.box(
        pc.vstack(
            navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가 
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        search_select(ServerState),
                        render_table(ServerState),
                        #pc.divider(),
                        #render_datatable(ServerState),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/login",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="5.5em",
            width="100%",
        ),
    )

def search_select(ServerState):
    """The Multi Select combo."""
    return pc.box(
        pc.hstack(
            pc.hstack(
                pc.select(
                    ServerState.select1_options,
                    placeholder="Select an Option.",
                    on_change=lambda value: ServerState.select_change('select1', value),
                    #is_disabled=True,
                    border_style="solid",
                    border_width="1px",
                    border_color="#43464B",
                    value=ServerState.select1_value,
                ),
                pc.select(
                    ServerState.select2_options,
                    placeholder="Select an Option.",
                    on_change=lambda value: ServerState.select_change('select2', value),
                    #is_disabled=True,
                    border_style="solid",
                    border_width="1px",
                    border_color="#43464B",
                    value=ServerState.select2_value,
                ),
                pc.button(
                    "검색", #Search
                    bg="navy", #gray
                    color="white", 
                    size="md",
                    width="150px",
                    on_click=ServerState.get_data_list,
                ),
                border_width="0px",
            ),
            border_width="0px",
        ),
        #position="fixed",
        #width="40%",
        #top="105px",
        z_index="500",
    )

def render_tbody_tr(tr_data, index):
    return pc.tr(
        pc.td(index + 1),
        pc.foreach(tr_data, lambda data: pc.td(data)),
    )

def render_table(ServerState):
    return pc.box(
        pc.vstack(
            pc.table(
                pc.table_caption(ServerState.table_name),
                pc.thead(
                    pc.tr(
                        pc.td("#"),
                        pc.foreach(ServerState.table_columns, lambda data: pc.td(data)),
                    ),
                    bg="navy",
                    color="white",
                ),
                pc.tbody(
                    pc.foreach(ServerState.table_data, lambda data, i: render_tbody_tr(data, i)),
                ),
            ),
            border_width="1px",
            overflow="auto",
        ),
        padding_top="1em",
        width="98%",
    )

def render_datatable(ServerState):
    return pc.box(
        pc.vstack(
            pc.data_table(
                data=ServerState.pd_result_list,
                pagination=True,
                search=True,
                sort=False, #True, False
                resizable=True,
                border_color="#43464B",
            ),
            width="100%",
            #align_items="start",
            #align_items="left",
            align_items=ServerState.box_align,
            #padding_x="15%",
            overflow="auto",
            #overflow_x="scroll",
            #overflow_y="scroll",
        ),
        border_width="0px",
        border_color="#43464B",
        width="98%",
    )

프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py    
2 demo_board > demo_board.py    
3 demo_board > demo_state.py    
4 demo_board > demo_auth.py    
5 demo_board > demo_servers.py List[Dict] 데이터로 Table, DataTable 생성 수정
6 demo_board > demo_helpers.py    

"/" route하는 Page를 등록하지 않고 구동 후 url 요청하면 에러 발생

 

결과 Table 화면 (http://localhost:3000/servers)

결과 테이블

biz_servers.py (기존)

import pynecone as pc

from .demo_state import State # Substate of Base State
from .demo_helpers import navbar

class ServerState(State):
    """The Substate of base state for the server page."""
    print("──────────────────── [ ServerState(State) ] ────────────────────")
    app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명

def servers():
    return pc.box(
        pc.vstack(
            navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가 
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        pc.heading("서버 모니터링 페이지", font_size="2em"),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/login",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="5em",
            width="100%",
        ),
    )

demo_server.py 화면을 구성하기위한 데이터는 따로 python 서비스 를 구현해서 데이터를 List[Dict] Type으로 리턴 받는다는 가정하에 샘플데이터를 만들어서 구현하겠습니다.

 

추가되는 함수 설명

def get_data()                                         # Table, DataTable을 위한 Sample List[Dict] 데이터 리턴

def render_tbody_tr(tr_data, index)     # Table thead 

def render_table(ServerState)              # Table 생성

def render_datatable(ServerState).     # DataTable 생성

 

샘플 데이터 

def get_data():
    # 샘플 데이터 List[Dict]
    result_list: List[Dict] = [
        {"Server Type": "WEB", "Server Name": "alpha", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "brovo", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "charlie", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "delta", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "echo", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
    ]
    pd_result_list = pd.DataFrame(result_list)
    pd_result_list=pd_result_list.fillna('')   # NaN값을 0 or ''로 치환한다.
    pd_result_list=pd_result_list.astype(str)  # 전체 Field를 str로 변환한다.

    return pd_result_list

Table을 추가

def render_tbody_tr(tr_data, index):
    return pc.tr(
        pc.td(index + 1),
        pc.foreach(tr_data, lambda data: pc.td(data)),
    )

def render_table(ServerState):
    return pc.box(
        pc.vstack(
            pc.table(
                pc.table_caption(ServerState.table_name),
                pc.thead(
                    pc.tr(
                        pc.td("#"),
                        pc.foreach(ServerState.table_columns, lambda data: pc.td(data)),
                    ),
                    bg="navy",
                    color="white",
                ),
                pc.tbody(
                    pc.foreach(ServerState.table_data, lambda data, i: render_tbody_tr(data, i)),
                ),
            ),
            border_width="1px",
            overflow="auto",
        ),
        padding_top="1em",
        width="98%",
    )

 

DataTable을 추가해보겠습니다.

DataTable은 자동으로 Table Header를 생성하기 위해서 앞서 생성한 List[Dict]를 Dataframe으로 변환한 데이터를 사용합니다.

def render_datatable(ServerState):
    return pc.box(
        pc.vstack(
            pc.data_table(
                data=ServerState.pd_result_list,
                pagination=True,
                search=True,
                sort=False, #True, False
                resizable=True,
                border_color="#43464B",
            ),
            width="100%",
            #align_items="start",
            #align_items="left",
            align_items=ServerState.box_align,
            #padding_x="15%",
            overflow="auto",
            #overflow_x="scroll",
            #overflow_y="scroll",
        ),
        border_width="0px",
        border_color="#43464B",
        width="98%",
    )

 

biz_servers.py (수정)

import pynecone as pc

import pandas as pd
from typing import List, Dict

from .demo_state import State # Substate of Base State
from .demo_helpers import navbar

def get_data():
    # 샘플 데이터 List[Dict]
    result_list: List[Dict] = [
        {"Server Type": "WEB", "Server Name": "alpha", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "brovo", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "charlie", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "delta", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
        {"Server Type": "WEB", "Server Name": "echo", "Total Disk": "100G", "Used Disk": "50G", "Available Disk": "50G", "HTTPS Status": "running", "Last Access": "17/Feb/2023:17:13:51"},
    ]
    pd_result_list = pd.DataFrame(result_list)
    pd_result_list=pd_result_list.fillna('')   # NaN값을 0 or ''로 치환한다.
    pd_result_list=pd_result_list.astype(str)  # 전체 Field를 str로 변환한다.

    return pd_result_list


class ServerState(State):
    """The Substate of base state for the server page."""
    print("──────────────────── [ ServerState(State) ] ────────────────────")
    app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명

    pd_result_list: List[Dict] = get_data()
    #print(pd_result_list)

    # table_columns : Dict에서 Columns 정보를 가져와서 Table의 thead를 구성
    # table_data : Dataframe에서 value만 List로 만들어서 Table의 tbody를 구성
    # table_name : Table의 table_caption 

    table_columns = list(pd_result_list.columns)
    table_data: List[List[str]] = pd_result_list.values.tolist()
    table_name: str = "WEB 서버 상태 확인"

    box_align: str = ""

def servers():
    return pc.box(
        pc.vstack(
            navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가 
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        pc.heading("서버 모니터링", font_size="2em"),
                        #pc.heading("서버 모니터링"),
                        render_table(ServerState),
                        pc.divider(),
                        render_datatable(ServerState),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/login",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="5.5em",
            width="100%",
        ),
    )

def render_tbody_tr(tr_data, index):
    return pc.tr(
        pc.td(index + 1),
        pc.foreach(tr_data, lambda data: pc.td(data)),
    )

def render_table(ServerState):
    return pc.box(
        pc.vstack(
            pc.table(
                pc.table_caption(ServerState.table_name),
                pc.thead(
                    pc.tr(
                        pc.td("#"),
                        pc.foreach(ServerState.table_columns, lambda data: pc.td(data)),
                    ),
                    bg="navy",
                    color="white",
                ),
                pc.tbody(
                    pc.foreach(ServerState.table_data, lambda data, i: render_tbody_tr(data, i)),
                ),
            ),
            border_width="1px",
            overflow="auto",
        ),
        padding_top="1em",
        width="98%",
    )

def render_datatable(ServerState):
    return pc.box(
        pc.vstack(
            pc.data_table(
                data=ServerState.pd_result_list,
                pagination=True,
                search=True,
                sort=False, #True, False
                resizable=True,
                border_color="#43464B",
            ),
            width="100%",
            #align_items="start",
            #align_items="left",
            align_items=ServerState.box_align,
            #padding_x="15%",
            overflow="auto",
            #overflow_x="scroll",
            #overflow_y="scroll",
        ),
        border_width="0px",
        border_color="#43464B",
        width="98%",
    )

 

결과 화면

Table과 DataTable이 아래위로 표시됩니다.

 

 

사용자 등록 후 로그인이 정상적으로 되면, Navigation Bar 오른쪽 상단에(붉은 사각형 영역) 로그인 사용자 정보, 다른 페이지 이동 및 로그아웃을 포함 하는 메뉴를 추가해보겠습니다.

 

https://pynecone.io/docs/library/media/avatar 참고

 

프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py    
2 demo_board > demo_board.py    
3 demo_board > demo_state.py /logout 추가 수정
4 demo_board > demo_auth.py    
5 demo_board > demo_servers.py    
6 demo_board > demo_helpers.py navbar 오른쪽 상단에 Menu를 추가 수정

 

 

demo_state.py

logout 이 호출되면 State에 저장된값을 모두 reset 시키고 "/login" 페이지로 redirect.

import pynecone as pc

class User(pc.Model, table=True): 
    """A table of Users."""

    username: str 
    password: str
    userrole: str

class State(pc.State):
    """The base state for the app."""
    print("──────────────────── [ State(pc.State) ] ────────────────────")

    username: str
    userrole: str
    logged_in: bool = False

    def logout(self):
        """Log out a user."""
        self.reset()
        return pc.redirect("/login")

 

demo_helpers.py (기존)

import pynecone as pc
from .demo_state import State

def navbar(State, app_name):
    """The navbar."""
    return pc.box(
        pc.hstack(
            pc.hstack(
                pc.image(src="bada.png", width="48px"),
                pc.heading("Demo Board"),
                pc.heading(" - "+app_name, size="lg", color="navy"),
            ),
            pc.spacer(),
            justify="space-between",
            border_bottom="0.15em solid #d3d3d3",
            padding_x="2em",
            padding_y="1em",
            bg="rgba(255,255,255, 1)",
        ),
        position="fixed",
        width="100%",
        top="0px",
        z_index="500",
    )

 

demo_helpers.py (변경후)

매뉴에는 사용자 이름, 사용자 권한, 등록되어있는 페이지, 로그아웃을 포함.

import pynecone as pc
from .demo_state import State

def navbar(State, app_name):
    """The navbar."""
    return pc.box(
        pc.hstack(
            pc.hstack(
                pc.image(src="bada.png", width="48px"),
                pc.heading("Demo Board"),
                pc.heading(" - "+app_name, size="lg", color="navy"),
            ),
            pc.spacer(),
            pc.menu(
                pc.menu_button(
                    pc.cond(
                        State.logged_in,
                        pc.avatar(name=State.username, size="md"),
                        pc.box(),
                    )
                ),
                pc.menu_list(
                    pc.center(
                        pc.vstack(
                            pc.avatar(name=State.username, size="md"),
                            pc.text(State.username+"("+State.userrole+")"),
                        )
                    ),
                    pc.menu_divider(),
                    pc.link(pc.menu_item("Servers"),href="/servers"),
                    pc.menu_divider(),
                    pc.link(pc.menu_item("Sign Out"), on_click=State.logout),
                ),
            ),
            justify="space-between",
            border_bottom="0.15em solid #d3d3d3",
            padding_x="2em",
            padding_y="1em",
            bg="rgba(255,255,255, 1)",
        ),
        position="fixed",
        width="100%",
        top="0px",
        z_index="500",
    )

 

기본적인 화면 및 State 구성을 했으니 실제 로그인/로그아웃을 구현해보겠습니다.

 

로그인에 사용할 사용자 정보를 관리하기위해서 pynecone이 기본으로 제공하는 SQLite 파일 DB를 사용합니다.

pynecone docs에 보면 pc.session 을 통해서 SQLAlchemy syntax의 query로 Database에 데이터를 등록, 변경, 삭제, 조회 할수 있습니다. (https://pynecone.io/docs/database/queries)

"with pc.session" 으로 데이터처리를 하고 해당 block이 끝나면 자동으로 session도 닫게 됩니다.

 

이제 구현방법입니다.

- 먼저, State(pc.State) 에서 전 App에서 사용가능한 사용자 정보(이름, 패스워드, 권한)를 포함하는 User Class 생성하고 Base State에 로그인 한 사용자, 권한, 상태를 저장하는 vars를 생성

- demo_auth.py의 login에 사용자이름, 패스워드를 입력받는 로그인 Page 생성

- 사용자 등록하는 signup Page 생성 및 사용자 등록

- DB Browser for SQLite를 이용해서 직접 pynecondb에 붙어서 기본 사용자를확인 

 

프로젝트 명(폴더) : demo_board

# 파일 경로 설명 구분
1 pcconfig.py pc 구동 설정 파일  
2 demo_board > demo_board.py "/signup" 사용자 등록 Page 추가
"/servers" Page 추가
수정
3 demo_board > demo_state.py 나중 추가 확장성(페이지추가)를 위해 Base State(pc.State)  수정
4 demo_board > demo_auth.py "/login" 페이지 변경 및 "/signup" 페이지 추가 수정
5 demo_board > demo_servers.py 로그인후 오픈할 페이지 추가
6 demo_board > demo_helpers.py    

 

https://pynecone.io/docs/database/tables

 

demo_state.py 수정

import pynecone as pc

#table=True 인수는 Pynecone에게 이 클래스에 대해 데이터베이스에 테이블을 생성하도록 지시
class User(pc.Model, table=True): 
    """A table of Users."""

    username: str                 # 사용자명
    password: str                 # 패스워드
    userrole: str                 # 사용자 권한

class State(pc.State):
    """The base state for the app."""
    print("──────────────────── [ State(pc.State) ] ────────────────────")

    username: str
    userrole: str
    logged_in: bool = False       # 로그인/로그아웃시 상태를 저장

 

demo_auth.py 수정

Log in / Sign up 을위해서 추가 변경되는 부분이 많습니다.

import pynecone as pc

from .demo_state import State , User # Base State, User Class import
from .demo_helpers import navbar

# 로그인 호면에 대한 스타일 시트 적용
styles = {
    "login_page": {
        "padding_top": "10em",
        "text_align": "top",
        "position": "relative",
        "background_image": "bg.svg",
        "background_size": "100% auto",
        "width": "100%",
        "height": "100vh",
    },
    "login_input": {
        "shadow": "lg",
        "padding": "1em",
        "border_radius": "lg",
        "background": "white",
    },
}

class AuthState(State):
    """The Substate of base state for the login page."""
    print("──────────────────── [ AuthState(State) ] ────────────────────")
    app_name: str = "Log In"

    password: str           # 로그인에서 입력받은 Password

    new_username: str       # 신규 사용자 Name
    new_userrole: str       # 신규 사용자 Role
    new_password: str       # 신규 사용자 Password
    confirm_password: str   # 신규 사용자 Confirm Password

    message: str            # 사용자 등록시 발생하는 Error 등 메시지 표시
    message_color: str = "red" # Message의 기본색은 red, Sign up 성공인경우 navy

    def signup(self):
        """Sign up a user."""
        print("signup values : [{}], [{}], [{}]".format(self.new_username, self.new_password, self.new_userrole))
        with pc.session() as session:
            #self.message_color = "red"
            # 입력 값 Validation 체크
            if self.new_userrole == "" or self.new_userrole == None:
                self.message = "Select user role."
                return
            if self.new_username == "" or self.new_username == None:
                self.message = "Input user name."
                return
            if self.new_password == "" or self.new_password == None:
                self.message = "Input passwd"
                return
            if self.new_password != self.confirm_password:
                self.message = "Passwords do not match."
                return
            if session.exec(User.select.where(User.username == self.new_username)).first():
                self.message = "Username already exists."
                return

            user = User(username=self.new_username, password=self.new_password, userrole=self.new_userrole)
            session.add(user)
            session.commit()
            self.logged_in = False
            self.message_color = "navy"
            self.message = "User siginup completed, please log in"
            return pc.redirect("/login")

    def login(self):
        """Log in a user."""
        if self.username == "" or self.password=="":
            self.message = "Input username and password."
            return

        with pc.session() as session:
            user = session.exec(
                User.select.where(User.username == self.username)
            ).first()
            if user and user.password == self.password:
                self.logged_in = True
                self.userrole = user.userrole
                return pc.redirect("/servers")
            else:
                self.message = "Invalid username or password."
                return

    # Signup 화면에서 선택한 Role 정보를 AuthState에 저장
    def role_change(self,value):
        print("role_change value : {}".format(value))
        self.new_userrole = value

    def clear_message(self):
        self.message = ""
        self.message_color = "red"


def signup():
    return pc.box(
        pc.vstack(
            navbar(State, AuthState.app_name),
            pc.center(
                pc.vstack(
                    pc.heading("Sign Up", font_size="1.5em"),
                    pc.select(
                        ["User", "Admin"],
                        placeholder="Select a Role.",
                        on_change=lambda value: AuthState.role_change(value),
                        border_style="solid",
                        border_width="1px",
                        border_color="#43464B",
                    ),
                    pc.input(
                        on_blur=AuthState.set_new_username, placeholder="Username", width="100%", 
                        on_click=AuthState.clear_message,
                    ),
                    pc.input(
                        on_blur=AuthState.set_new_password,
                        placeholder="Password",
                        type_="password",
                        width="100%",
                        on_click=AuthState.clear_message,
                    ),
                    pc.input(
                        on_blur=AuthState.set_confirm_password,
                        placeholder="Confirm Password",
                        type_="password",
                        width="100%",
                        on_click=AuthState.clear_message,
                    ),
                    pc.button("Sign Up", on_click=AuthState.signup, width="100%"),
                    pc.text(AuthState.message, color=AuthState.message_color),
                ),
                style=styles["login_input"],
            ),
        ),
        style=styles["login_page"],
    )

def login():
    return pc.box(
        pc.vstack(
            navbar(State, AuthState.app_name),
            pc.center(
                pc.vstack(
                    pc.heading("Log In", font_size="1.5em"),
                    pc.input(
                        on_blur=State.set_username, placeholder="Username", width="100%", 
                        on_click=AuthState.clear_message,
                    ),
                    pc.input(
                        on_change=AuthState.set_password,
                        placeholder="Password",
                        type_="password",
                        width="100%",
                        on_click=AuthState.clear_message,
                    ),
                    # Username, Password를 입력받으면 AuthState의 login 함수 호출하는 event 작동
                    pc.button("Login", on_click=AuthState.login, width="100%"),  
                    # 사용자 등록하기위한 Signup
                    pc.link(
                        pc.button("Sign Up", width="100%"), href="/signup", width="100%"
                    ),
                    pc.text(AuthState.message, color=AuthState.message_color),
                ),
                style=styles["login_input"], # 로그인 Page에 맞는 추가 스타일 
            ),
        ),
        style=styles["login_page"],
    )

 

demo_servers.py

"/servers" 페이지는 pc.cond 컴포넌트로 Base State의 logged_in var가 True 일때만 열리게 구성합니다.

import pynecone as pc

from .demo_state import State # Substate of Base State
from .demo_helpers import navbar

class ServerState(State):
    """The Substate of base state for the server page."""
    print("──────────────────── [ ServerState(State) ] ────────────────────")
    app_name: str = "Servers" #1 Navigation Bar에 표시될 Page 명

def servers():
    return pc.box(
        pc.vstack(
            navbar(State, ServerState.app_name), #2 맨위 상단에 navbar를 추가 
            pc.cond(
                State.logged_in,
                pc.box(
                    pc.vstack(
                        pc.heading("서버 모니터링 페이지", font_size="2em"),
                    ),
                    width="100%",
                    border_width="0px",
                ),
                pc.link(
                    pc.button("먼저 로그인 하세요"),
                    href="/login",
                    color="rgb(107,99,246)",
                    button=True,
                )
            ),
            padding_top="5em",
            width="100%",
        ),
    )

로그인 없이 접근할경우

login 없이 접근할때

 

demo_board.py

"""Welcome to Pynecone! This file outlines the steps to create a basic app."""
from pcconfig import config

import pynecone as pc
from .demo_state import State
from .demo_auth import login, signup # Page 추가
from .demo_servers import servers

def index():
    return pc.center(
        pc.vstack(
            pc.heading("Pynecone Demo Board!", font_size="2em"),
            pc.link(
                "로그인",
                href="/login",
                border="0.1em solid",
                padding="0.5em",
                border_radius="0.5em",
                _hover={
                    "color": "rgb(107,99,246)",
                },
            ),
            spacing="1.5em",
            font_size="2em",
        ),
        padding_top="10%",
    )


# Add state and page to the app.
app = pc.App(state=State)
app.add_page(index)
app.add_page(login)
app.add_page(signup) # Page 추가
app.add_page(servers) # Page 추가
app.compile()

 

서버 재시작 (ctrl + c & pc run)

Log in page
Sign up page

 

서버 재구동후 DB Browser for SQLite 로 pynecone.db 파일을 열어보면 user 테이블이 생성된것을 확인

 

DB Browser for SQLite 를 닫고 사용자를 추가 (Sign up)

 

사용자 정보 입력 후 Sign Up
사용자 등록 완료후 로그인 페이지

pynecone.db에 저장된 사용자 확인 

(문서 만들기 시작할때는 낮이었는데 이제 밤이라 야간모드로 전환되었네요)

 

로그인

등록된 ID/PW 입력

로그인 성공 후 /servers Page로 이동

pynecone 사이트

https://pynecone.io/docs/getting-started/project-structure

 

프로젝트 디렉토리 생성 

% mkdir demo_board
% cd demo_board
% ll
total 0
drwxr-xr-x   2 dongsik  staff   64  2 18 11:02 .
drwxr-xr-x  11 dongsik  staff  352  2 18 11:02 ..

프로젝트 초기화

% pc init
[11:05:35] Initializing the web directory.                           utils.py:410
           Initializing the app directory.                           utils.py:399
           Finished Initializing: demo_board                         pc.py:49

초기화된 프로젝트 디렉토리 구조

% ll
total 16
drwxr-xr-x   7 dongsik  staff  224  2 18 11:05 .
drwxr-xr-x  11 dongsik  staff  352  2 18 11:02 ..
drwxr-xr-x  10 dongsik  staff  320  2 11 00:49 .web
drwxr-xr-x   3 dongsik  staff   96  2 11 00:49 assets
drwxr-xr-x   5 dongsik  staff  160  2 18 11:05 demo_board
-rw-r--r--   1 dongsik  staff  128  2 18 11:05 pcconfig.py

# 디렉토리 구조 상세보기
% tree .
.
├── .web               # 컴파일된 NextJS 
├── assets             # images, fonts등 정적 파일 
│   └── favicon.ico    # pynecon favicon file
├── demo_board         # 메인 프로젝트 디렉토리
│   ├── __init__.py
│   └── demo_board.py  # Default App 파일 (생성한 프로젝트 이름과 동일하게 생성됨)
└── pcconfig.py        # App에 대한 설정파일

첫번째 구동

% pc run
───────────────────── Starting Pynecone App ──────────────────────
────────────────── Installing frontend packages ──────────────────
bun install v0.5.0 (2db04ef9)
────────────────────────── App Running ───────────────────────────
$ next dev
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
event - compiled client and server successfully in 2.4s (757 modules)
wait  - compiling /404 (client and server)...
event - compiled client and server successfully in 191 ms (761 modules)
warn  - Fast Refresh had to perform a full reload. 
        Read more: https://nextjs.org/docs/basic-features/fast-refresh#how-it-works
wait  - compiling / (client and server)...
event - compiled client and server successfully in 375 ms (831 modules)

실행화면

http://localhost:3000/

 

다음 페이지 : https://amnesia.tistory.com/9

pynecone demo dashboard를 업무 모니터링하는 용도로 사용하기 위해서 사용중인 클라우드의 CentOS서버에 Docker Container로 pynecone 서버를 구동했습니다.

 

서버 (클라우드) : CentOS 7.9 

cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)

Mac에서 docker image 받아서 사용해도 괜찮습니다.

 

 

Python 최신 & Node js Docker image

https://hub.docker.com/r/nikolaik/python-nodejs

 

Docker

 

hub.docker.com

https://github.com/nikolaik/docker-python-nodejs

 

GitHub - nikolaik/docker-python-nodejs: 🐳 Python with Node.js docker image

🐳 Python with Node.js docker image. Contribute to nikolaik/docker-python-nodejs development by creating an account on GitHub.

github.com

해당 컨테이너는 수시로 업데이트 됩니다. 

Docker Pull Command

docker pull nikolaik/python-nodejs

All images have a default user pn with uid 1000 and gid 1000.

 

다운받은 Docker Image 확인

$ docker images
REPOSITORY               TAG       IMAGE ID       CREATED         SIZE
nikolaik/python-nodejs   latest    eaeeaf150538   6 weeks ago     1.29GB

(문서만드는 시점에 Docker image의 python, node js 버젼이 올라갔네요 하지만 최신으로 받아도 설치에는 문제 없을겁니다.)

 

Linux 서버의 사용자 디렉토리에 pynecone 폴더를 만들고 해당 폴더를 volume으로 지정해서 컨테이너를 구동합니다.

이때 포튼 기본포트(3000), API 포트 (8000)을 열어야 합니다.

 

물론 3000, 8000 번포트는 클라우드 서버의 방화벽에도 포트를 오픈해야 외부에서 접속 가능합니다. 

 

docker run -it -d --name=demo_board -p 3000:3000 -p 8000:8000 -v /home/<사용자>/pynecone:/pynecone --user 1000 nikolaik/python-nodejs:1.0 bash

docker run -it -d --name=demo_board -p 3000:3000 -p 8000:8000 -v /home/<사용자>/pynecone:/pynecone --user 1000 nikolaik/python-nodejs:1.0 bash

 

root 로 컨테이너 들어가기

docker exec -it --user root demo_board bash

 

$ docker ps
CONTAINER ID   IMAGE                       COMMAND  CREATED       STATUS       PORTS                                                      NAMES
9e1be00f07e1   nikolaik/python-nodejs:1.0   "bash"  5 weeks ago   Up 5 weeks   0.0.0.0:3000->3000/tcp, 0.0.0.0:8000->8000/tcp, 5006/tcp   demo_board
$

 

vim 설치

apt-get update
apt-get install vim

리눅스 버전확인

root@9e1be00f07e1:/pynecone# cd /pynecone
root@9e1be00f07e1:/pynecone# uname -a
Linux 11d0133a3eb6 3.10.0-1160.42.2.el7.x86_64 #1 SMP Tue Sep 7 14:49:57 UTC 2021 x86_64 GNU/Linux
root@9e1be00f07e1:/pynecone# cat /etc/issue
Debian GNU/Linux 11 \n \l
root@9e1be00f07e1:/pynecone#

Python, Node 버전 확인

root@9e1be00f07e1:/pynecone# python -V
Python 3.11.1
root@9e1be00f07e1:/pynecone# node -v
v18.13.0
root@9e1be00f07e1:/pynecone#

pynecone 설치전 pip list 확인

root@9e1be00f07e1:/pynecone# pip list
Package          Version
---------------- ----------
certifi          2022.12.7
distlib          0.3.6
filelock         3.9.0
pip              23.0
pipenv           2022.12.19
platformdirs     2.6.2
setuptools       65.5.1
virtualenv       20.17.1
virtualenv-clone 0.5.7
wheel            0.38.4
root@9e1be00f07e1:/pynecone#

pynecone 설치

root@9e1be00f07e1:/pynecone# pip install pynecone

설치후 pip list 확인

root@9e1be00f07e1:/pynecone# pip list
Package            Version
------------------ ----------
anyio              3.6.2
async-timeout      4.0.2
certifi            2022.12.7
charset-normalizer 3.0.1
click              8.1.3
commonmark         0.9.1
distlib            0.3.6
fastapi            0.88.0
filelock           3.9.0
greenlet           2.0.2
gunicorn           20.1.0
h11                0.14.0
httpcore           0.16.3
httpx              0.23.3
idna               3.4
numpy              1.24.1
pandas             1.5.3
pip                23.0.1
pipenv             2022.12.19
platformdirs       2.6.2
plotly             5.13.0
psutil             5.9.4
pydantic           1.10.2
Pygments           2.14.0
PyMySQL            1.0.2
pynecone           0.1.15
python-dateutil    2.8.2
pytz               2022.7.1
redis              4.4.2
requests           2.28.2
rfc3986            1.5.0
rich               12.6.0
setuptools         65.5.1
six                1.16.0
sniffio            1.3.0
SQLAlchemy         1.4.41
sqlalchemy2-stubs  0.0.2a32
sqlmodel           0.0.8
starlette          0.22.0
tenacity           8.1.0
typer              0.4.2
typing_extensions  4.4.0
urllib3            1.26.14
uvicorn            0.20.0
virtualenv         20.17.1
virtualenv-clone   0.5.7
websockets         10.4
wheel              0.38.4
root@9e1be00f07e1:/pynecone#

 

설치가 완료된 후에는 일반 사용자로 컨테이너로 들어가서 작업하면됩니다.

docker exec -it demo_board bash

 

 

필요한 Python 라이브러리가 설치완료되면 본격적으로 demo 프로젝트를 생성합니다.

 

1.pynecone Demo 프로젝트 생성 참조해서 첫번째 프로젝트를 생성합니다.

https://amnesia.tistory.com/6

 

1.pynecone Demo 프로젝트 생성

pynecone 사이트 https://pynecone.io/docs/getting-started/project-structure 프로젝트 디렉토리 생성 % mkdir demo_board % cd demo_board % ll total 0 drwxr-xr-x 2 dongsik staff 64 2 18 11:02 . drwxr-xr-x 11 dongsik staff 352 2 18 11:02 .. 프로

amnesia.tistory.com

 

운영업무에서 사용할 대시보드를 pynecone으로 구현해볼려고 합니다.

기본적인 골격을 만들기위해서 아래 데모로 Navigation bar, 로그인, 매뉴, 다중 콤보, Table, DataTable 등 필요한 기능들을 하나씩 기능 테스트 및 구현해보고 있습니다.

 

개별 기능은 아래 '카테고리'에서 확인가능합니다. 

 

데모 구성은 https://amnesia.tistory.com/category/pynecone/demo%20board

 

'pynecone/demo board' 카테고리의 글 목록

 

amnesia.tistory.com

 

Demo

 

 

 

pynecone 버전 0.1.15
테스트 웹 브라우져 네이버 웨일, 구글 크롬(Chrome)

목표 : pynecone을 이용해서 업무에서 사용할 Business Dash Board를 구현

  • Reference Gallery Twitter Clone 샘플 참고
  • Common한 로그인 화면 구현
  • SQLite db(pynecone.db) 를 이용해서 사용자 관리하는 샘플을 구현

 

첫페이지

 

아이디 패스워드 입력
로그인 성공후 첫베이지
오른쪽 상단 매뉴 및 로그인 정보
SQLLite pynecone.db 에 등록된 사용자정보
사용자 추가 팝업
사용자 추가
#2 사용자가 추가됨

 

삭제할 사용자 번호(#) 입력후 "Delete"

 

 

#2 가 삭제됨

 

 

+ Recent posts