Compare commits

..

2 Commits

Author SHA1 Message Date
xue jiahao
fd9d590be2 [UI] version page 2024-12-06 23:29:16 +08:00
xue jiahao
eaedab505c Add save version dialog
# Conflicts:
#	frontend/src/types/AppTypes.ts
2024-12-06 13:04:42 +08:00
101 changed files with 1124 additions and 5737 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1,36 +1,26 @@
from fastapi import Request
from fastapi.responses import JSONResponse
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import APIRouter, Depends, HTTPException, status
from datetime import timedelta
from app.db.session import get_db
from app.core import security
from app.core.auth import authenticate_user, sign_up_new_user
from app.core import security,tenantCacheService
from app.core.dbmanager import get_db
from sqlalchemy.orm import Session
auth_router = r = APIRouter()
@r.post("/token")
async def login(request: Request,db:Session= Depends(get_db) ,form_data: OAuth2PasswordRequestForm = Depends()):
if not db :
async def login(
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
):
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="abcIncorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
)
@@ -43,16 +33,12 @@ async def login(request: Request,db:Session= Depends(get_db) ,form_data: OAuth2P
permissions =";".join(list(set(perlst)))
access_token = security.create_access_token(
data={"sub": user.id,"roles":roles,"permissions": permissions,"tenant":user.tenantid,},
data={"sub": user.id, "roles":roles,"permissions": permissions ,},
expires_delta=access_token_expires,
)
request.state.user = user.id
return JSONResponse(
status_code=200,
content={"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
)
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
@r.post("/signup")
async def signup(

View File

@@ -8,26 +8,26 @@ import deepdiff
import app.core.config as config
import os
from pathlib import Path
from app.core.dbmanager import get_db
from app.db.session import SessionLocal
from app.db.crud import get_flows_by_app,get_kintoneformat
from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
from app.db.cruddb import domainService,appService
from app.db.cruddb.dbdomain import dbdomain
kinton_router = r = APIRouter()
def getkintoneenv(user = Depends(get_current_user),db = Depends(get_db)):
#db = SessionLocal()
domain = domainService.get_default_domain(db,user.id) #get_activedomain(db, user.id)
#db.close()
def getkintoneenv(user = Depends(get_current_user)):
db = SessionLocal()
domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
db.close()
kintoneevn = config.KINTONE_ENV(domain)
return kintoneevn
def getkintoneformat(db,user = Depends(get_current_user)):
#db = SessionLocal()
def getkintoneformat():
db = SessionLocal()
formats = get_kintoneformat(db)
#db.close()
db.close()
return formats
@@ -452,10 +452,10 @@ def getTempPath(filename):
fpath = os.path.join(rootdir,"Temp",filename)
return fpath
def createappjs(domain_url,app,db):
#db = SessionLocal()
flows = appService.get_flow(db,domain_url,app) #get_flows_by_app(db,domain_url,app)
#db.close()
def createappjs(domain_url,app):
db = SessionLocal()
flows = get_flows_by_app(db,domain_url,app)
db.close()
content={}
for flow in flows:
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
@@ -627,9 +627,9 @@ async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getk
@r.post("/createappfromexcel",)
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
try:
mapping = getkintoneformat(db)[format]
mapping = getkintoneformat()[format]
except Exception as e:
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
@@ -666,9 +666,9 @@ async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...
@r.post("/updateappfromexcel")
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
try:
mapping = getkintoneformat(db)[format]
mapping = getkintoneformat()[format]
except Exception as e:
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
@@ -758,11 +758,11 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
@r.post("/createjstokintone",)
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv),db = Depends(get_db)):
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
try:
jscs=[]
files=[]
files.append(createappjs(env.BASE_URL, app, db))
files.append(createappjs(env.BASE_URL, app))
files.append(getTempPath('alc_runtime.js'))
files.append(getTempPath('alc_runtime.css'))
for file in files:

View File

@@ -2,8 +2,8 @@ from http import HTTPStatus
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
from fastapi.responses import JSONResponse
# from app.core.operation import log_operation
# from app.db import Base,engine
from app.core.dbmanager import get_db
from app.db import Base,engine
from app.db.session import get_db
from app.db.crud import *
from app.db.schemas import *
from typing import List, Optional
@@ -11,11 +11,10 @@ from app.core.auth import get_current_active_user,get_current_user
from app.core.apiexception import APIException
from app.core.common import ApiReturnModel,ApiReturnPage
#from fastapi_pagination import Page
from app.db.cruddb import domainService,appService
from app.db.cruddb import dbdomain
import httpx
import app.core.config as config
from app.core import domainCacheService,tenantCacheService
platform_router = r = APIRouter()
@@ -28,7 +27,7 @@ async def test(
user = Depends(get_current_active_user),
db=Depends(get_db),
):
domainService.select(db,{"tenantid":1,"name":["b","c"]})
dbdomain.select(db,{"tenantid":1,"name":["b","c"]})
@r.get(
@@ -43,11 +42,11 @@ async def apps_list(
):
try:
domain = domainService.get_default_domain(db,user.id) #get_activedomain(db, user.id)
domain = dbdomain.get_default_domain(db,user.id) #get_activedomain(db, user.id)
if not domain:
return ApiReturnModel(data = None)
filtered_apps = []
platformapps = appService.get_apps(db,domain.url)
platformapps = get_apps(db,domain.url)
kintoneevn = config.KINTONE_ENV(domain)
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
@@ -73,80 +72,34 @@ async def apps_list(
except Exception as e:
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
@r.post("/apps", tags=["App"],
response_model=ApiReturnModel[AppList|None],
response_model_exclude_none=True)
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
async def apps_update(
request: Request,
app: VersionUpdate,
app: AppVersion,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data =appService.update_appversion(db, domainurl,app,user.id))
return update_appversion(db, app,user.id)
except Exception as e:
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
@r.delete("/apps/{appid}",tags=["App"],
response_model=ApiReturnModel[AppList|None],
response_model_exclude_none=True
@r.delete(
"/apps/{domainurl}/{appid}", response_model_exclude_none=True
)
async def apps_delete(
request: Request,
domainurl:str,
appid: str,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data =appService.delete_app(db, domainurl,appid))
return delete_apps(db, domainurl,appid)
except Exception as e:
raise APIException('platform:apps',request.url._url,f"Error occurred while delete app({appid}):",e)
@r.get(
"/appversions/{appid}",tags=["App"],
response_model=ApiReturnPage[AppVersion|None],
response_model_exclude_none=True,
)
async def appversions_list(
request: Request,
appid: str,
user = Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
if not domainurl:
return ApiReturnPage(data = None)
return appService.get_appversions(db,domainurl,appid)
except Exception as e:
raise APIException('platform:appversions',request.url._url,f"Error occurred while get app({appid}) version :",e)
@r.put(
"/appversions/{appid}/{version}",tags=["App"],
response_model=ApiReturnModel[AppList|None],
response_model_exclude_none=True
)
async def appversions_change(
request: Request,
appid: str,
version: int,
user = Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
if not domainurl:
ApiReturnModel(data = None)
return ApiReturnModel(data = appService.change_appversion(db, domainurl,appid,version,user.id))
except Exception as e:
raise APIException('platform:appversions',request.url._url,f"Error occurred while change app version:",e)
raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e)
@r.get(
"/appsettings/{id}",
response_model=App,
@@ -237,26 +190,24 @@ async def action_data(
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
@r.get(
"/flow/{appid}",tags=["App"],
response_model=ApiReturnModel[List[Flow]|None],
"/flow/{flowid}",
response_model=Flow,
response_model_exclude_none=True,
)
async def flow_details(
request: Request,
appid: str,
user=Depends(get_current_active_user),
flowid: str,
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data = appService.get_flow(db, domainurl, appid))
app = get_flow(db, flowid)
return app
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
@r.get(
"/flows/{appid}", tags=["App"],
"/flows/{appid}",
response_model=List[Flow|None],
response_model_exclude_none=True,
)
@@ -267,19 +218,17 @@ async def flow_list(
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
if not domainurl:
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
if not domain:
return []
#flows = get_flows_by_app(db, domainurl, appid)
flows = appService.get_flow(db,domainurl,appid)
print("domain=>",domain)
flows = get_flows_by_app(db, domain.url, appid)
return flows
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
@r.post("/flow", tags=["App"],
response_model=ApiReturnModel[Flow|None],
response_model_exclude_none=True)
@r.post("/flow", response_model=Flow|None, response_model_exclude_none=True)
async def flow_create(
request: Request,
flow: FlowIn,
@@ -287,50 +236,43 @@ async def flow_create(
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data = appService.create_flow(db, domainurl, flow,user.id))
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
if not domain:
return None
return create_flow(db, domain.url, flow)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
@r.put(
"/flow", tags=["App"],
response_model=ApiReturnModel[Flow|None],
response_model_exclude_none=True
"/flow/{flowid}", response_model=Flow|None, response_model_exclude_none=True
)
async def flow_edit(
request: Request,
flowid: str,
flow: FlowIn,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data = appService.edit_flow(db,domainurl, flow,user.id))
domain = dbdomain.get_default_domain(db, user.id) #get_activedomain(db, user.id)
if not domain:
return None
return edit_flow(db,domain.url, flow,user.id)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
@r.delete(
"/flow/{flowid}", tags=["App"],
response_model=ApiReturnModel[Flow|None],
response_model_exclude_none=True
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
)
async def flow_delete(
request: Request,
flowid: str,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domainurl = domainCacheService.get_default_domainurl(db,user.id)
if not domainurl:
return ApiReturnModel(data = None)
return ApiReturnModel(data = appService.delete_flow(db, flowid))
return delete_flow(db, flowid)
except Exception as e:
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
@@ -339,36 +281,20 @@ async def flow_delete(
response_model=ApiReturnPage[Domain],
response_model_exclude_none=True,
)
async def domain_list(
async def domain_details(
request: Request,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
if user.is_superuser:
domains = domainService.get_domains(db)
domains = dbdomain.get_domains(db)
else:
domains = domainService.get_domains_by_manage(db,user.id)
domains = dbdomain.get_domains_by_owner(db,user.id)
return domains
except Exception as e:
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
@r.get(
"/domain/{domain_id}",tags=["Domain"],
response_model=ApiReturnModel[Domain|None],
response_model_exclude_none=True,
)
async def domain_detail(
request: Request,
domain_id:int,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
return ApiReturnModel(data = domainService.get(db,domain_id))
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while get domain detail:",e)
@r.post("/domain", tags=["Domain"],
response_model=ApiReturnModel[Domain],
response_model_exclude_none=True)
@@ -379,7 +305,7 @@ async def domain_create(
db=Depends(get_db),
):
try:
return ApiReturnModel(data = domainService.create_domain(db, domain,user.id))
return ApiReturnModel(data = dbdomain.create_domain(db, domain,user.id))
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
@@ -396,10 +322,7 @@ async def domain_edit(
db=Depends(get_db),
):
try:
domain = domainService.edit_domain(db, domain,user.id)
if domain :
domainCacheService.clear_default_domainurl()
return ApiReturnModel(data = domain)
return ApiReturnModel(data = dbdomain.edit_domain(db, domain,user.id))
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
@@ -415,9 +338,9 @@ async def domain_delete(
db=Depends(get_db),
):
try:
return ApiReturnModel(data = domainService.delete_domain(db,id))
return ApiReturnModel(data = dbdomain.delete_domain(db,id))
except Exception as e:
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain({id}):",e)
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
@r.get(
"/domain",
@@ -434,29 +357,28 @@ async def userdomain_details(
domains = get_domain(db, userId if userId is not None else user.id)
return domains
except Exception as e:
raise APIException('platform:userdomain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
@r.post(
"/userdomain",tags=["Domain"],
"/domain/{userid}",tags=["Domain"],
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
)
async def create_userdomain(
request: Request,
userdomain:UserDomainParam,
userid: int,
domainid:int ,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
userid = userdomain.userid
domainid = userdomain.domainid
if user.is_superuser:
domain = domainService.add_userdomain(db,user.id,userid,domainid)
domain = dbdomain.add_userdomain(db,user.id,userid,domainid)
else:
domain = domainService.add_userdomain_by_owner(db,user.id,userid,domainid)
domain = dbdomain.add_userdomain_by_owner(db,user.id,userid,domainid)
return ApiReturnModel(data = domain)
except Exception as e:
raise APIException('platform:userdomain',request.url._url,f"Error occurred while add user({userid}) domain({domainid}):",e)
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
@r.delete(
"/domain/{domainid}/{userid}",tags=["Domain"],
@@ -471,70 +393,11 @@ async def delete_userdomain(
db=Depends(get_db),
):
try:
return ApiReturnModel(data = domainService.delete_userdomain(db,userid,domainid))
return ApiReturnModel(data = dbdomain.delete_userdomain(db,userid,domainid))
except Exception as e:
raise APIException('platform:userdomain',request.url._url,f"Error occurred while delete user({userid}) domain({domainid}):",e)
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
@r.get(
"/managedomainuser/{domainid}",tags=["Domain"],
response_model=ApiReturnPage[UserOut|None],
response_model_exclude_none=True,
)
async def get_managedomainuser(
request: Request,
domainid:int,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
return domainService.get_managedomain_users(db,domainid)
except Exception as e:
raise APIException('platform:managedomain',request.url._url,f"Error occurred while get managedomain({user.id}) user:",e)
@r.post(
"/managedomain",tags=["Domain"],
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
)
async def create_managedomain(
request: Request,
userdomain:UserDomainParam,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
userid = userdomain.userid
domainid = userdomain.domainid
if user.is_superuser:
domain = domainService.add_managedomain(db,user.id,userid,domainid)
else:
domain = domainService.add_managedomain_by_owner(db,user.id,userid,domainid)
return ApiReturnModel(data = domain)
except Exception as e:
raise APIException('platform:managedomain',request.url._url,f"Error occurred while add manage({userid}) domain({domainid}):",e)
@r.delete(
"/managedomain/{domainid}/{userid}",tags=["Domain"],
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
)
async def delete_managedomain(
request: Request,
domainid:int,
userid: int,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
return ApiReturnModel(data = domainService.delete_managedomain(db,userid,domainid))
except Exception as e:
raise APIException('platform:managedomain',request.url._url,f"Error occurred while delete managedomain({userid}) domain({domainid}):",e)
@r.get(
"/defaultdomain",tags=["Domain"],
response_model=ApiReturnModel[DomainOut|None],
@@ -546,7 +409,7 @@ async def get_defaultuserdomain(
db=Depends(get_db),
):
try:
return ApiReturnModel(data =domainService.get_default_domain(db,user.id))
return ApiReturnModel(data =dbdomain.get_default_domain(db, user.id))
except Exception as e:
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while get user({user.id}) defaultdomain:",e)
@@ -555,14 +418,14 @@ async def get_defaultuserdomain(
response_model=ApiReturnModel[DomainOut|None],
response_model_exclude_none=True,
)
async def set_defualtuserdomain(
async def update_activeuserdomain(
request: Request,
domainid:int,
user=Depends(get_current_active_user),
db=Depends(get_db),
):
try:
domain = domainCacheService.set_default_domain(db,user.id,domainid)
domain = dbdomain.set_default_domain(db,user.id,domainid)
return ApiReturnModel(data= domain)
except Exception as e:
raise APIException('platform:defaultdomain',request.url._url,f"Error occurred while update user({user.id}) defaultdomain:",e)
@@ -580,7 +443,7 @@ async def get_domainshareduser(
db=Depends(get_db),
):
try:
return domainService.get_shareddomain_users(db,domainid)
return dbdomain.get_shareddomain_users(db,user.id,domainid)
except Exception as e:
raise APIException('platform:sharedomain',request.url._url,f"Error occurred while get user({user.id}) sharedomain:",e)

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter, Request, Depends, Response, Security, encoders
import typing as t
from app.core.common import ApiReturnModel,ApiReturnPage
from app.core.apiexception import APIException
from app.core.dbmanager import get_db
from app.db.session import get_db
from app.db.crud import (
get_allusers,
get_users,
@@ -13,10 +13,9 @@ from app.db.crud import (
assign_userrole,
get_roles,
)
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,AssignUserRoles,Permission
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,Permission
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
from app.db.cruddb import userService
from app.core import tenantCacheService
from app.db.cruddb import dbuser
users_router = r = APIRouter()
@@ -33,9 +32,9 @@ async def users_list(
):
try:
if current_user.is_superuser:
users = userService.get_users(db)
users = dbuser.get_users(db)
else:
users = userService.get_users_not_admin(db)
users = dbuser.get_users_not_admin(db)
return users
except Exception as e:
raise APIException('user:users',request.url._url,f"Error occurred while get user list",e)
@@ -60,7 +59,7 @@ async def user_details(
current_user=Depends(get_current_active_user),
):
try:
user = userService.get(db, user_id)
user = dbuser.get(db, user_id)
if user:
if user.is_superuser and not current_user.is_superuser:
user = None
@@ -82,7 +81,7 @@ async def user_create(
try:
if user.is_superuser and not current_user.is_superuser:
return ApiReturnModel(data = None)
return ApiReturnModel(data =userService.create_user(db, user,current_user.id))
return ApiReturnModel(data =dbuser.create_user(db, user,current_user.id))
except Exception as e:
raise APIException('user:users',request.url._url,f"Error occurred while create user({user.email}):",e)
@@ -102,7 +101,7 @@ async def user_edit(
try:
if user.is_superuser and not current_user.is_superuser:
return ApiReturnModel(data = None)
return ApiReturnModel(data = userService.edit_user(db,user_id,user,current_user.id))
return ApiReturnModel(data = dbuser.edit_user(db,user_id,user,current_user.id))
except Exception as e:
raise APIException('user:users',request.url._url,f"Error occurred while edit user({user_id}):",e)
@@ -118,10 +117,10 @@ async def user_delete(
current_user=Depends(get_current_active_user),
):
try:
user = userService.get(db,user_id)
user = dbuser.get(db,user_id)
if user.is_superuser and not current_user.is_superuser:
return ApiReturnModel(data = None)
return ApiReturnModel(data = userService.delete_user(db, user_id))
return ApiReturnModel(data = dbuser.delete_user(db, user_id))
except Exception as e:
raise APIException('user:users',request.url._url,f"Error occurred while delete user({user_id}):",e)
@@ -131,13 +130,14 @@ async def user_delete(
response_model_exclude_none=True,)
async def assign_role(
request: Request,
userroles:AssignUserRoles,
user_id:int,
roles:t.List[int],
db=Depends(get_db)
):
try:
return ApiReturnModel(data = userService.assign_userrole(db,userroles.userid,userroles.roleids))
return ApiReturnModel(data = dbuser.assign_userrole(db,user_id,roles))
except Exception as e:
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({userroles.userid}) roles({userroles.roleids}):",e)
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({user_id}) roles({roles}):",e)
@r.get(
"/roles",tags=["User"],
@@ -151,12 +151,11 @@ async def roles_list(
#current_user=Security(get_current_active_user, scopes=["role_list"]),
):
try:
if current_user.is_superuser:
roles = userService.get_roles(db)
roles = dbuser.get_roles(db)
else:
if len(current_user.roles)>0:
roles = userService.get_roles_by_level(db,current_user.roles)
roles = dbuser.get_roles_by_level(db,current_user.roles[0].level)
else:
roles = []
return ApiReturnModel(data = roles)
@@ -176,10 +175,10 @@ async def permssions_list(
):
try:
if current_user.is_superuser:
permissions = userService.get_permissions(db)
permissions = dbuser.get_permissions(db)
else:
if len(current_user.roles)>0:
permissions = userService.get_user_permissions(db,current_user.id)
permissions = dbuser.get_user_permissions(db,current_user.id)
else:
permissions = []
return ApiReturnModel(data = permissions)

View File

@@ -1,2 +0,0 @@
from app.core.cache import domainCacheService
from app.core.cache import tenantCacheService

View File

@@ -1,7 +1,7 @@
from fastapi import HTTPException, status,Depends
from fastapi import HTTPException, status
import httpx
from app.db.schemas import ErrorCreate
from app.core.dbmanager import get_log_db
from app.db.session import SessionLocal
from app.db.crud import create_log
class APIException(Exception):
@@ -31,10 +31,9 @@ class APIException(Exception):
self.error = ErrorCreate(location=location, title=title, content=content)
super().__init__(self.error)
def writedblog(exc: APIException,):
#db = SessionLocal()
db = get_log_db()
def writedblog(exc: APIException):
db = SessionLocal()
try:
create_log(db,exc.error)
finally:
db.close()
db.close()

View File

@@ -3,14 +3,13 @@ import jwt
from fastapi import Depends, HTTPException, Request, Security, status
from jwt import PyJWTError
from app.db import models, schemas
from app.db import models, schemas, session
from app.db.crud import get_user_by_email, create_user,get_user
from app.core import security
from app.db.cruddb import userService
from app.core.dbmanager import get_db
from app.db.cruddb import dbuser
async def get_current_user(request: Request,security_scopes: SecurityScopes,
db=Depends(get_db), token: str = Depends(security.oauth2_scheme)
async def get_current_user(security_scopes: SecurityScopes,
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -18,6 +17,7 @@ async def get_current_user(request: Request,security_scopes: SecurityScopes,
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
)
@@ -25,10 +25,6 @@ async def get_current_user(request: Request,security_scopes: SecurityScopes,
if id is None:
raise credentials_exception
tenant:str = payload.get("tenant")
if tenant is None:
raise credentials_exception
permissions: str = payload.get("permissions")
if not permissions =="ALL":
for scope in security_scopes.scopes:
@@ -39,10 +35,9 @@ async def get_current_user(request: Request,security_scopes: SecurityScopes,
token_data = schemas.TokenData(id = id, permissions=permissions)
except PyJWTError:
raise credentials_exception
user = userService.get_user(db, token_data.id)
user = dbuser.get_user(db, token_data.id)
if user is None:
raise credentials_exception
request.state.user = user.id
return user
async def get_current_active_user(
@@ -64,11 +59,11 @@ async def get_current_active_superuser(
def authenticate_user(db, email: str, password: str):
user = userService.get_user_by_email(db,email) #get_user_by_email(db, email)
user = get_user_by_email(db, email)
if not user:
return None
return False
if not security.verify_password(password, user.hashed_password):
return None
return False
return user

View File

@@ -1,72 +0,0 @@
import time
from typing import Any
from sqlalchemy.orm import Session
from app.db.cruddb import domainService,tenantService
from app.db.session import Database
class MemoryCache:
def __init__(self, max_cache_size: int = 100, ttl: int = 60):
self.cache = {}
self.max_cache_size = max_cache_size
self.ttl = ttl
def get(self, key: str) -> Any:
item = self.cache.get(key)
if item:
if time.time() - item['timestamp'] > self.ttl:
self.cache.pop(key)
return None
return item['value']
return None
def set(self, key: str, value: Any) -> None:
if len(self.cache) >= self.max_cache_size:
self.cache.pop(next(iter(self.cache)))
self.cache[key] = {'value': value, 'timestamp': time.time()}
# def clear(self,key) -> None:
# self.cache.pop(key,None)
def clear(self) -> None:
self.cache.clear()
class domainCache:
def __init__(self):
self.memoryCache = MemoryCache(max_cache_size=50, ttl=120)
def set_default_domain(self, db: Session,userid: int,domainid:str):
domain = domainService.set_default_domain(db,userid,domainid)
if domain:
self.memoryCache.set(f"DOMAIN_{userid}",domain.url)
return domain
def get_default_domainurl(self,db: Session, userid: int):
if not self.memoryCache.get(f"DOMAIN_{userid}"):
domain = domainService.get_default_domain(db,userid)
if domain:
self.memoryCache.set(f"DOMAIN_{userid}",domain.url)
return self.memoryCache.get(f"DOMAIN_{userid}")
def clear_default_domainurl(self):
self.memoryCache.clear()
domainCacheService =domainCache()
class tenantCache:
def __init__(self):
self.memoryCache = MemoryCache(max_cache_size=50, ttl=120)
def get_tenant_db(self,db: Session, tenantid: str):
if not self.memoryCache.get(f"TENANT_{tenantid}"):
tenant = tenantService.get_tenant(db,tenantid)
if tenant:
database = Database(tenant.db)
self.memoryCache.set(f"TENANT_{tenantid}",database)
return self.memoryCache.get(f"TENANT_{tenantid}")
tenantCacheService =tenantCache()

View File

@@ -1,20 +0,0 @@
from fastapi import Depends,Request
from app.db.session import get_tenant_db
from app.core import tenantCacheService
from app.db.session import tenantdb
def get_db(request: Request,tenant:str = "1",tenantdb = Depends(get_tenant_db)):
database = tenantCacheService.get_tenant_db(tenantdb,tenant)
try:
db = database.get_db()
request.state.tenant = tenant
request.state.db = db
yield db
finally:
db.close()
def get_log_db():
db = tenantdb.get_db()
return db

View File

@@ -1,75 +0,0 @@
from fastapi import Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from sqlalchemy.orm import Session
from app.db.models import OperationLog,User
from app.core.apiexception import APIException
from app.core.dbmanager import get_log_db
from app.db.crud import create_log
import json
class LoggingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.method in ("POST", "PUT", "PATCH","DELETE"):
try:
request.state.body = await request.json()
except json.JSONDecodeError:
request.state.body = await request.body()
else:
request.state.body = None
try:
response = await call_next(request)
state = request.state
except Exception as e:
await self.log_error(request, e)
response = JSONResponse(
content={"detail": "Internal Server Error"},
status_code=500
)
if hasattr(request.state, "user") and hasattr(request.state, "tenant"):
await self.log_request(request, response,state)
return response
async def log_request(self, request: Request, response,state):
try:
headers = dict(request.headers)
route = request.scope.get("route")
if route:
path_template = route.path
else:
path_template = request.url.path
db_operation = OperationLog(tenantid =request.state.tenant,
clientip = request.client.host if request.client else None,
useragent =headers.get("user-agent", ""),
userid = request.state.user,
operation = request.method,
function = path_template,
parameters = str({"path": request.path_params,"query": dict(request.query_params),"body": request.state.body}),
response = f"status_code:{response.status_code }" )
db = request.state.db
if db:
await self.write_log_to_db(db_operation,db)
except Exception as e:
print(f"Logging failed: {str(e)}")
async def log_error(self, request: Request, e: Exception):
exc = APIException('operation:dispatch',request.url._url,f"Error occurred while writting operation log:",e)
db = get_log_db()
try:
create_log(db,exc.error)
finally:
db.close()
async def write_log_to_db(self, db_operation,db):
db.add(db_operation)
db.commit()

View File

@@ -1,7 +1,7 @@
import jwt
from fastapi.security import OAuth2PasswordBearer
from passlib.context import CryptContext
from datetime import datetime, timedelta,timezone
from datetime import datetime, timedelta
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
import os
import base64
@@ -27,9 +27,9 @@ def verify_password(plain_password: str, hashed_password: str) -> bool:
def create_access_token(*, data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.now(timezone.utc) + expires_delta
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt

View File

@@ -1,4 +1,2 @@
from app.db.cruddb.dbuser import userService
from app.db.cruddb.dbdomain import domainService
from app.db.cruddb.dbapp import appService
from app.db.cruddb.dbtenant import tenantService
from app.db.cruddb.dbuser import dbuser
from app.db.cruddb.dbdomain import dbdomain

View File

@@ -1,4 +1,4 @@
from sqlalchemy import asc, desc, select
from sqlalchemy import asc, desc
from sqlalchemy.orm import Session
from sqlalchemy.orm.query import Query
from typing import Type, List, Optional
@@ -46,12 +46,11 @@ class crudbase:
and_conditions.append(column == value)
if and_conditions:
query = query.where(and_(*and_conditions))
query = query.filter(*and_conditions)
if or_conditions:
query = query.where(or_(*or_conditions))
query = query.filter(or_(*or_conditions))
return query
def _apply_sorting(self, query: Query, sort_by: Optional[str], sort_order: Optional[str]) -> Query:
if sort_by:
column = getattr(self.model, sort_by, None)
@@ -62,11 +61,12 @@ class crudbase:
query = query.order_by(asc(column))
return query
def get_all(self) -> Query:
return select(self.model)
def get_all(self, db: Session) -> Query:
return db.query(self.model)
def get(self, db: Session, item_id: int) -> Optional[models.Base]:
return db.execute(select(self.model).filter(self.model.id == item_id)).scalar_one_or_none()
return db.query(self.model).get(item_id)
def create(self, db: Session, obj_in: BaseModel) -> models.Base:
db_obj = self.model(**obj_in.model_dump())
@@ -76,7 +76,7 @@ class crudbase:
return db_obj
def update(self, db: Session, item_id: int, obj_in: BaseModel) -> Optional[models.Base]:
db_obj = self.get(db,item_id)
db_obj = db.query(self.model).filter(self.model.id == item_id).first()
if db_obj:
for key, value in obj_in.model_dump(exclude_unset=True).items():
setattr(db_obj, key, value)
@@ -86,16 +86,16 @@ class crudbase:
return None
def delete(self, db: Session, item_id: int) -> Optional[models.Base]:
db_obj = self.get(db,item_id)
db_obj = db.query(self.model).get(item_id)
if db_obj:
db.delete(db_obj)
db.commit()
return db_obj
return None
def get_by_conditions(self, filters: Optional[dict] = None, sort_by: Optional[str] = None,
def get_by_conditions(self, db: Session, filters: Optional[dict] = None, sort_by: Optional[str] = None,
sort_order: Optional[str] = "asc") -> Query:
query = select(self.model)
query = db.query(self.model)
if filters:
query = self._apply_filters(query, filters)
if sort_by:

View File

@@ -1,216 +0,0 @@
from datetime import datetime
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import select,and_
import typing as t
from app.db.cruddb.crudbase import crudbase
from fastapi_pagination.ext.sqlalchemy import paginate
from app.core.common import ApiReturnPage
from app.db import models, schemas
from app.core.security import chacha20Decrypt, get_password_hash
class dbflowhistory(crudbase):
def __init__(self):
super().__init__(model=models.FlowHistory)
def get_flows_by_appid_version(self,db: Session,domainurl:str,appid:str,version:int):
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid, "version":version})).scalars().all()
dbflowhistory = dbflowhistory()
class dbflow(crudbase):
def __init__(self):
super().__init__(model=models.Flow)
def get_domain_apps(self):
return None
def get_flow_by_flowid(self,db: Session,flowid:str):
return db.execute(super().get_by_conditions({"flowid":flowid})).scalars().first()
def get_flows_by_appid(self,db: Session,domainurl:str,appid:str):
return db.execute(select(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid))).scalars().all()
def create_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
db_flow = models.Flow(
flowid=flow.flowid,
appid=flow.appid,
eventid=flow.eventid,
domainurl=domainurl,
name=flow.name,
content=flow.content,
createuserid = userid,
updateuserid = userid
)
db.add(db_flow)
db_app = db.execute(select(models.App).where(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid))).scalars().first()
if not db_app:
db_app = models.App(
domainurl = domainurl,
appid=flow.appid,
appname=flow.appname,
version = 0,
createuserid= userid,
updateuserid = userid
)
db.add(db_app)
db.commit()
db.refresh(db_flow)
return db_flow
dbflow = dbflow()
class dbappversion(crudbase):
def __init__(self):
super().__init__(model=models.AppVersion)
def get_appversions(self,domainurl:str,appid:str):
return super().get_by_conditions({"domainurl":domainurl,"appid":appid})
def get_app_by_version(self,db: Session,domainurl:str,appid:str,version:int):
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid,"version":version})).scalars().first()
def get_app_latestversion(self,db: Session,domainurl:str,appid:str):
appversion = db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid},"version","desc")).scalars().first()
if appversion:
return appversion.version
else:
return 0
dbappversion = dbappversion()
class dbapp(crudbase):
def __init__(self):
super().__init__(model=models.App)
def get_app(self,db: Session,domainurl:str,appid:str):
return db.execute(super().get_by_conditions({"domainurl":domainurl,"appid":appid})).scalars().first()
def get_apps(self,db: Session,domainurl:str):
return db.execute(super().get_by_conditions({"domainurl":domainurl})).scalars().all()
def update_appversion(self,db: Session,domainurl, newversion: schemas.VersionUpdate,userid:int):
db_app = self.get_app(db,domainurl,newversion.appid)
if db_app:
db_app.version = dbappversion.get_app_latestversion(db,domainurl,newversion.appid)+1
db_app.updateuserid = userid,
db_app.versionname = newversion.versionname
db_app.is_saved = False
appversion = models.AppVersion(
domainurl = db_app.domainurl,
appid=db_app.appid,
appname=db_app.appname,
version = db_app.version,
versionname = newversion.versionname,
comment = newversion.comment,
updateuserid = userid,
createuserid = userid
)
db.add(appversion)
db.add(db_app)
flows = dbflow.get_flows_by_appid(db,domainurl,newversion.appid)
for flow in flows:
db_flowhistory = models.FlowHistory(
flowid = flow.flowid,
appid = flow.appid,
eventid = flow.eventid,
domainurl = flow.domainurl,
name = flow.name,
content = flow.content,
version = db_app.version,
updateuserid = userid,
createuserid = userid
)
db.add(db_flowhistory)
db.add(db_flowhistory)
db.commit()
db.refresh(db_app)
return db_app
return None
def change_appversion(self,db: Session, domainurl:str,appid:str,version:int,userid:int):
db_app = self.get_app(db, domainurl, appid)
if not db_app:
return None
db_appversion = dbappversion.get_app_by_version(db, domainurl, appid, version)
if not db_appversion:
return None
db_app.version = version
db_app.versionname = db_appversion.versionname
db_app.updateuserid = userid
db_app.is_saved = False
db.add(db_app)
flows = dbflow.get_flows_by_appid(db, domainurl, appid)
for flow in flows:
db.delete(flow)
db.flush()
flowhistorys = dbflowhistory.get_flows_by_appid_version(db, domainurl, appid, version)
for flow in flowhistorys:
db_flow = models.Flow(
flowid = flow.flowid,
appid = flow.appid,
eventid = flow.eventid,
domainurl = flow.domainurl,
name = flow.name,
content = flow.content,
updateuserid = userid,
createuserid = userid
)
db.add(db_flow)
db.commit()
db.refresh(db_app)
return db_app
def delete_app(self,db: Session, domainurl: str,appid: str ):
db_app =self.get_app(db,domainurl,appid)
if db_app:
flows = dbflow.get_flows_by_appid(db,domainurl,appid)
for flow in flows:
db.delete(flow)
db.delete(db_app)
db.commit()
return db_app
return None
def get_appversions(self,db: Session, domainurl:str,appid:str):
return paginate(db,dbappversion.get_appversions(domainurl,appid))
def get_flow(self,db: Session, domainurl: str, appid:str):
return dbflow.get_flows_by_appid(db,domainurl,appid)
def create_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
return dbflow.create_flow(db,domainurl,flow,userid)
def edit_flow(self,db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
db_flow = dbflow.get_flow_by_flowid(db, flow.flowid)
if not db_flow:
return dbflow.create_flow(db,domainurl,flow,userid)
db_flow.appid =flow.appid
db_flow.eventid=flow.eventid
db_flow.domainurl=domainurl
db_flow.name=flow.name
db_flow.content=flow.content
db_flow.updateuserid = userid
db.add(db_flow)
db_app = self.get_app(db, domainurl, flow.appid)
if db_app and db_app.version > 0:
db_app.is_saved = True
db.add(db_app)
db.commit()
db.refresh(db_flow)
return db_flow
def delete_flow(self,db: Session, flowid: str):
db_flow = dbflow.get_flow_by_flowid(db,flowid)
if db_flow:
return dbflow.delete(db,db_flow.id)
return None
appService = dbapp()

View File

@@ -1,7 +1,7 @@
from datetime import datetime
from fastapi import HTTPException, status
from sqlalchemy.orm import Session
from sqlalchemy import select,and_
from sqlalchemy import and_
import typing as t
from app.db.cruddb.crudbase import crudbase
@@ -16,76 +16,43 @@ class dbuserdomain(crudbase):
super().__init__(model=models.UserDomain)
def get_userdomain(self,db: Session,userid:int,domainid:int):
return db.execute(super().get_by_conditions({"userid":userid,"domainid":domainid})).scalars().first()
return super().get_by_conditions(db,{"userid":userid,"domainid":domainid}).first()
def get_userdomain_by_domainid(self,db: Session,ownerid:int,domainid:int):
return super().get_by_conditions({"domainid":domainid})
return super().get_by_conditions(db,{"domainid":domainid})
def get_default_domains(self,db: Session,domainid:int):
return db.execute(super().get_by_conditions({"domainid":domainid,"is_default":True})).scalars().all()
return super().get_by_conditions(db,{"domainid":domainid,"is_default":True}).all()
def get_user_default_domain(self,db: Session,userid:int):
return db.execute(super().get_by_conditions({"userid":userid,"is_default":True})).scalars().first()
return super().get_by_conditions(db,{"userid":userid,"is_default":True}).first()
dbuserdomain = dbuserdomain()
class dbmanagedomain(crudbase):
def __init__(self):
super().__init__(model=models.ManageDomain)
def get_managedomain(self,db: Session,userid:int,domainid:int):
return db.execute(super().get_by_conditions({"userid":userid,"domainid":domainid})).scalars().first()
def get_managedomain_by_domain(self,db: Session,domainid:int):
return db.execute(super().get_by_conditions({"domainid":domainid})).scalars().all()
dbmanagedomain = dbmanagedomain()
class dbdomain(crudbase):
def __init__(self):
super().__init__(model=models.Domain)
def get_domains(self,db: Session)-> ApiReturnPage[models.Base]:
return paginate(db,super().get_all())
def get_domains_by_manage(self,db: Session,userid:int)-> ApiReturnPage[models.Base]:
query = select(models.Domain).join(models.ManageDomain,models.ManageDomain.domainid == models.Domain.id).where(models.ManageDomain.userid == userid)
return paginate(db,query)
return paginate(super().get_all(db))
def get_domains_by_owner(self,db: Session,ownerid:int)-> ApiReturnPage[models.Base]:
return paginate(db,super().get_by_conditions({"ownerid":ownerid}))
return paginate( super().get_by_conditions(db,{"ownerid":ownerid}))
def create_domain(self,db: Session, domain: schemas.DomainIn,userid:int):
#db_domain = super().get_by_conditions(db,{"url":domain.url,"kintoneuser":domain.kintoneuser,"onwerid":userid}).first()
#if not db_domain:
domain.encrypt_kintonepwd()
db_domain = models.Domain(
tenantid = domain.tenantid,
name = domain.name,
url = domain.url,
kintoneuser = domain.kintoneuser,
kintonepwd = domain.kintonepwd,
is_active = domain.is_active,
createuserid = userid,
updateuserid = userid,
ownerid = userid
)
db.add(db_domain)
db.flush()
user_domain = models.UserDomain(userid = userid, domainid = db_domain.id ,createuserid = userid,updateuserid = userid)
db.add(user_domain)
manage_domain = models.ManageDomain(userid = userid, domainid = db_domain.id ,createuserid = userid,updateuserid = userid)
db.add(manage_domain)
db.commit()
db.refresh(db_domain)
return db_domain
domain.id = None
domain.createuserid = userid
domain.updateuserid = userid
domain.ownerid = userid
return super().create(db,domain)
#return db_domain
def delete_domain(self,db: Session,id: int):
db_managedomains = dbmanagedomain.get_managedomain_by_domain(db,id)
for manage in db_managedomains:
db.delete(manage)
return super().delete(db,id)
def edit_domain(self,db: Session, domain: schemas.DomainIn,userid:int) -> schemas.DomainOut:
@@ -112,8 +79,8 @@ class dbdomain(crudbase):
return None
def add_userdomain(self,db: Session,ownerid:int,userid:int,domainid:int) -> schemas.DomainOut:
db_domain = super().get(db,domainid)
if db_domain and db_domain.is_active:
db_domain = super().get_by_conditions(db,{"id":domainid,"is_active":True}).first()
if db_domain:
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
if not db_userdomain:
user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid)
@@ -123,7 +90,7 @@ class dbdomain(crudbase):
return None
def add_userdomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut:
db_domain = db.execute(super().get_by_conditions({"id":domainid,"is_active":True})).scalars().first()
db_domain = super().get_by_conditions(db,{"id":domainid,"ownerid":ownerid,"is_active":True}).first()
if db_domain:
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
if not db_userdomain:
@@ -150,69 +117,23 @@ class dbdomain(crudbase):
return None
def set_default_domain(self,db: Session, userid: int,domainid: int):
db_domain =db.execute(super().get_by_conditions({"id":domainid,"is_active":True})).scalars().first()
db_domain =super().get_by_conditions(db,{"id":domainid,"is_active":True}).first()
if db_domain:
db_default_domain = dbuserdomain.get_user_default_domain(db,userid)
db_userdomain =dbuserdomain.get_userdomain(db,userid,domainid)
if db_default_domain:
if db_default_domain.domainid != domainid:
db_default_domain.is_default = False
db_default_domain.updateuserid = userid
db.add(db_default_domain)
else:
return db_domain
db_default_domain.is_default = False
db_default_domain.updateuserid = userid
db.add(db_default_domain)
if db_userdomain:
db_userdomain.is_default = True
db_userdomain.updateuserid = userid
db.add(db_userdomain)
else:
db_userdomain = dbuserdomain.create(db,schemas.UserDomainIn(domainid=domainid,userid=userid,is_default = True))
db.add(db_userdomain)
db.commit()
return db_domain
else:
return None
return db_domain
def get_shareddomain_users(self,db: Session,domainid: int) -> ApiReturnPage[models.Base]:
users = select(models.User).join(models.UserDomain,models.UserDomain.userid == models.User.id).filter(models.UserDomain.domainid ==domainid)
return paginate(db,users)
def get_shareddomain_users(self,db: Session,ownerid:int,domainid: int) -> ApiReturnPage[models.Base]:
users = db.query(models.User).join(models.UserDomain,models.UserDomain.userid == models.User.id).filter(models.UserDomain.domainid ==domainid)
return paginate(users)
def add_managedomain(self,db: Session,ownerid:int,userid:int,domainid:int) -> schemas.DomainOut:
db_domain = self.get(db,domainid)
if db_domain:
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
if not db_managedomain:
manage_domain = models.ManageDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid)
db.add(manage_domain)
db.commit()
return db_domain
return None
def add_managedomain_by_owner(self,db: Session,ownerid:int, userid:int,domainid:int) -> schemas.DomainOut:
db_domain = db.execute(super().get_by_conditions({"id":domainid,"ownerid":ownerid,})).scalars().first()
if db_domain:
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
if not db_managedomain:
manage_domain = models.ManageDomain(userid = userid, domainid = domainid ,createuserid =ownerid,updateuserid = ownerid)
db.add(manage_domain)
db.commit()
return db_domain
return None
def delete_managedomain(self,db: Session, userid: int,domainid: int) -> schemas.DomainOut:
db_managedomain = dbmanagedomain.get_managedomain(db,userid,domainid)
if db_managedomain:
domain = db_managedomain.domain
if domain.ownerid != userid:
db.delete(db_managedomain)
db.commit()
return domain
return None
def get_managedomain_users(self,db: Session,domainid: int) -> ApiReturnPage[models.Base]:
users = select(models.User).join(models.ManageDomain,models.ManageDomain.userid == models.User.id).where(models.ManageDomain.domainid ==domainid)
return paginate(db,users)
domainService = dbdomain()
dbdomain = dbdomain()

View File

@@ -1,13 +0,0 @@
from app.db.cruddb.crudbase import crudbase
from app.db import models, schemas
from sqlalchemy.orm import Session
class dbtenant(crudbase):
def __init__(self):
super().__init__(model=models.Tenant)
def get_tenant(sefl,db:Session,tenantid: str):
tenant = db.execute(super().get_by_conditions({"tenantid":tenantid})).scalars().first()
return tenant
tenantService = dbtenant()

View File

@@ -32,13 +32,13 @@ class dbuser(crudbase):
return super().get(db,user_id)
def get_user_by_email(self,db: Session, email: str) -> schemas.User:
return db.execute(super().get_by_conditions({"email":email})).scalars().first()
return super().get_by_conditions(db,{"email":email}).first()
def get_users(self,db: Session) -> ApiReturnPage[models.Base]:
return paginate(db,super().get_all())
return paginate(super().get_all(db))
def get_users_not_admin(self,db: Session) -> ApiReturnPage[models.Base]:
return paginate(db,super().get_by_conditions({"is_superuser":False}))
return paginate(super().get_by_conditions(db,{"is_superuser":False}))
def create_user(self,db: Session, user: schemas.UserCreate,userid:int):
hashed_password = get_password_hash(user.password)
@@ -60,32 +60,26 @@ class dbuser(crudbase):
return super().update(db,user_id,user)
def get_roles(self,db: Session) -> t.List[schemas.RoleBase]:
return db.execute(dbrole.get_all()).scalars().all()
#return dbrole.get_all().all()
return dbrole.get_all(db).all()
def get_roles_by_level(self,db: Session,roles:t.List[models.Role]) -> t.List[schemas.RoleBase]:
level = 99999
for role in roles:
if role.level < level:
level = role.level
return db.execute(dbrole.get_by_conditions({"level":{"operator":">","value":level}})).scalars().all()
def get_roles_by_level(self,db: Session,level:int) -> t.List[schemas.RoleBase]:
return dbrole.get_by_conditions(db,{"level":{"operator":">=","value":level}}).all()
def assign_userrole(self,db: Session, user_id: int, roles: t.List[int]):
db_user = super().get(db,user_id)
if db_user:
for role in db_user.roles:
if role.id not in roles:
db_user.roles.remove(role)
db_user.roles.remove(role)
for roleid in roles:
role = dbrole.get(db,roleid)
if role not in db_user.roles:
if role:
db_user.roles.append(role)
db.commit()
db.refresh(db_user)
return db_user
def get_permissions(self,db: Session) -> t.List[schemas.Permission]:
return db.execute(dbpermission.get_all()).scalars().all()
def get_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]:
return dbpermission.get_all(db).all()
def get_user_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]:
permissions =[]
@@ -95,4 +89,4 @@ class dbuser(crudbase):
permissions += role.permissions
return list(set(permissions))
userService = dbuser()
dbuser = dbuser()

View File

@@ -1,14 +1,14 @@
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
from sqlalchemy.orm import Mapped,relationship,as_declarative,mapped_column
from datetime import datetime,timezone
from app.db import Base
from sqlalchemy.orm import relationship,as_declarative
from datetime import datetime
from app.db.session import Base
from app.core.security import chacha20Decrypt
@as_declarative()
class Base:
id = Column(Integer, primary_key=True, index=True)
create_time = Column(DateTime, default=datetime.now(timezone.utc))
update_time = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
create_time = Column(DateTime, default=datetime.now)
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
userrole = Table(
@@ -28,15 +28,14 @@ rolepermission = Table(
class User(Base):
__tablename__ = "user"
email = mapped_column(String(50), unique=True, index=True, nullable=False)
first_name = mapped_column(String(100))
last_name = mapped_column(String(100))
hashed_password = mapped_column(String(200), nullable=False)
is_active = mapped_column(Boolean, default=True)
is_superuser = mapped_column(Boolean, default=False)
tenantid = mapped_column(String(100))
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
email = Column(String(50), unique=True, index=True, nullable=False)
first_name = Column(String(100))
last_name = Column(String(100))
hashed_password = Column(String(200), nullable=False)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
roles = relationship("Role",secondary=userrole,back_populates="users")
@@ -45,132 +44,127 @@ class User(Base):
class Role(Base):
__tablename__ = "role"
name = mapped_column(String(100))
description = mapped_column(String(255))
level = mapped_column(Integer)
name = Column(String(100))
description = Column(String(255))
level = Column(Integer)
users = relationship("User",secondary=userrole,back_populates="roles")
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
class Permission(Base):
__tablename__ = "permission"
menu = mapped_column(String(100))
function = mapped_column(String(255))
link = mapped_column(String(100))
privilege = mapped_column(String(100))
menu = Column(String(100))
function = Column(String(255))
privilege = Column(String(100))
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
class App(Base):
__tablename__ = "app"
domainurl = mapped_column(String(200), nullable=False)
appname = mapped_column(String(200), nullable=False)
appid = mapped_column(String(100), index=True, nullable=False)
version = mapped_column(Integer)
versionname = mapped_column(String(200), nullable=False)
is_saved = mapped_column(Boolean, default=False)
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
domainurl = Column(String(200), nullable=False)
appname = Column(String(200), nullable=False)
appid = Column(String(100), index=True, nullable=False)
version = Column(Integer)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class AppVersion(Base):
__tablename__ = "appversion"
domainurl = mapped_column(String(200), nullable=False)
appname = mapped_column(String(200), nullable=False)
appid = mapped_column(String(100), index=True, nullable=False)
version = mapped_column(Integer)
versionname = mapped_column(String(200), nullable=False)
comment = mapped_column(String(200), nullable=False)
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
domainurl = Column(String(200), nullable=False)
appname = Column(String(200), nullable=False)
appid = Column(String(100), index=True, nullable=False)
version = Column(Integer)
versionname = Column(String(200), nullable=False)
comment = Column(String(200), nullable=False)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class AppSetting(Base):
__tablename__ = "appsetting"
appid = mapped_column(String(100), index=True, nullable=False)
setting = mapped_column(String(1000))
appid = Column(String(100), index=True, nullable=False)
setting = Column(String(1000))
class Kintone(Base):
__tablename__ = "kintone"
type = mapped_column(Integer, index=True, nullable=False)
name = mapped_column(String(100), nullable=False)
desc = mapped_column(String)
content = mapped_column(String)
type = Column(Integer, index=True, nullable=False)
name = Column(String(100), nullable=False)
desc = Column(String)
content = Column(String)
class Action(Base):
__tablename__ = "action"
name = mapped_column(String(100), index=True, nullable=False)
title = mapped_column(String(200))
subtitle = mapped_column(String(500))
outputpoints = mapped_column(String)
property = mapped_column(String)
categoryid = mapped_column(Integer,ForeignKey("category.id"))
nosort = mapped_column(Integer)
name = Column(String(100), index=True, nullable=False)
title = Column(String(200))
subtitle = Column(String(500))
outputpoints = Column(String)
property = Column(String)
categoryid = Column(Integer,ForeignKey("category.id"))
nosort = Column(Integer)
class Flow(Base):
__tablename__ = "flow"
flowid = mapped_column(String(100), index=True, nullable=False)
appid = mapped_column(String(100), index=True, nullable=False)
eventid = mapped_column(String(100), index=True, nullable=False)
domainurl = mapped_column(String(200))
name = mapped_column(String(200))
content = mapped_column(String)
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
flowid = Column(String(100), index=True, nullable=False)
appid = Column(String(100), index=True, nullable=False)
eventid = Column(String(100), index=True, nullable=False)
domainurl = Column(String(200))
name = Column(String(200))
content = Column(String)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class FlowHistory(Base):
__tablename__ = "flowhistory"
flowid = mapped_column(String(100), index=True, nullable=False)
appid = mapped_column(String(100), index=True, nullable=False)
eventid = mapped_column(String(100), index=True, nullable=False)
domainurl = mapped_column(String(200))
name = mapped_column(String(200))
content = mapped_column(String)
version = mapped_column(Integer)
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
flowid = Column(String(100), index=True, nullable=False)
appid = Column(String(100), index=True, nullable=False)
eventid = Column(String(100), index=True, nullable=False)
domainurl = Column(String(200))
name = Column(String(200))
content = Column(String)
version = Column(Integer)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class Tenant(Base):
__tablename__ = "tenant"
tenantid = mapped_column(String(100), index=True, nullable=False)
name = mapped_column(String(200))
licence = mapped_column(String(200))
startdate = mapped_column(DateTime)
enddate = mapped_column(DateTime)
db = mapped_column(String(200))
tenantid = Column(String(100), index=True, nullable=False)
name = Column(String(200))
licence = Column(String(200))
startdate = Column(DateTime)
enddate = Column(DateTime)
class Domain(Base):
__tablename__ = "domain"
tenantid = mapped_column(String(100), index=True, nullable=False)
name = mapped_column(String(100), nullable=False)
url = mapped_column(String(200), nullable=False)
kintoneuser = mapped_column(String(100), nullable=False)
kintonepwd = mapped_column(String(100), nullable=False)
is_active = mapped_column(Boolean, default=True)
tenantid = Column(String(100), index=True, nullable=False)
name = Column(String(100), nullable=False)
url = Column(String(200), nullable=False)
kintoneuser = Column(String(100), nullable=False)
kintonepwd = Column(String(100), nullable=False)
is_active = Column(Boolean, default=True)
def decrypt_kintonepwd(self):
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
return decrypted_pwd
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
ownerid = mapped_column(Integer,ForeignKey("user.id"))
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
ownerid = Column(Integer,ForeignKey("user.id"))
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
owner = relationship('User',foreign_keys=[ownerid])
@@ -179,23 +173,11 @@ class Domain(Base):
class UserDomain(Base):
__tablename__ = "userdomain"
userid = mapped_column(Integer,ForeignKey("user.id"))
domainid = mapped_column(Integer,ForeignKey("domain.id"))
is_default = mapped_column(Boolean, default=False)
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
domain = relationship("Domain")
user = relationship("User",foreign_keys=[userid])
createuser = relationship('User',foreign_keys=[createuserid])
updateuser = relationship('User',foreign_keys=[updateuserid])
class ManageDomain(Base):
__tablename__ = "managedomain"
userid = mapped_column(Integer,ForeignKey("user.id"))
domainid = mapped_column(Integer,ForeignKey("domain.id"))
createuserid = mapped_column(Integer,ForeignKey("user.id"))
updateuserid = mapped_column(Integer,ForeignKey("user.id"))
userid = Column(Integer,ForeignKey("user.id"))
domainid = Column(Integer,ForeignKey("domain.id"))
is_default = Column(Boolean, default=False)
createuserid = Column(Integer,ForeignKey("user.id"))
updateuserid = Column(Integer,ForeignKey("user.id"))
domain = relationship("Domain")
user = relationship("User",foreign_keys=[userid])
createuser = relationship('User',foreign_keys=[createuserid])
@@ -204,53 +186,51 @@ class ManageDomain(Base):
class Event(Base):
__tablename__ = "event"
category = mapped_column(String(100), nullable=False)
type = mapped_column(String(100), nullable=False)
eventid= mapped_column(String(100), nullable=False)
function = mapped_column(String(500), nullable=False)
mobile = mapped_column(Boolean, default=False)
eventgroup = mapped_column(Boolean, default=False)
category = Column(String(100), nullable=False)
type = Column(String(100), nullable=False)
eventid= Column(String(100), nullable=False)
function = Column(String(500), nullable=False)
mobile = Column(Boolean, default=False)
eventgroup = Column(Boolean, default=False)
class EventAction(Base):
__tablename__ = "eventaction"
eventid = mapped_column(String(100),ForeignKey("event.eventid"))
actionid = mapped_column(Integer,ForeignKey("action.id"))
eventid = Column(String(100),ForeignKey("event.eventid"))
actionid = Column(Integer,ForeignKey("action.id"))
class ErrorLog(Base):
__tablename__ = "errorlog"
title = mapped_column(String(50))
location = mapped_column(String(500))
content = mapped_column(String(5000))
title = Column(String(50))
location = Column(String(500))
content = Column(String(5000))
class OperationLog(Base):
__tablename__ = "operationlog"
tenantid = mapped_column(String(100))
clientip = mapped_column(String(200))
useragent = mapped_column(String(200))
userid = mapped_column(Integer,ForeignKey("user.id"))
operation = mapped_column(String(200))
function = mapped_column(String(200))
parameters = mapped_column(String(200))
response = mapped_column(String(200))
tenantid = Column(String(100))
domainurl = Column(String(200))
userid = Column(Integer,ForeignKey("user.id"))
operation = Column(String(200))
function = Column(String(200))
detail = Column(String(200))
user = relationship('User')
class KintoneFormat(Base):
__tablename__ = "kintoneformat"
name = mapped_column(String(50))
startrow =mapped_column(Integer)
startcolumn =mapped_column(Integer)
typecolumn =mapped_column(Integer)
codecolumn =mapped_column(Integer)
field = mapped_column(String(5000))
trueformat = mapped_column(String(10))
name = Column(String(50))
startrow =Column(Integer)
startcolumn =Column(Integer)
typecolumn =Column(Integer)
codecolumn =Column(Integer)
field = Column(String(5000))
trueformat = Column(String(10))
class Category(Base):
__tablename__ = "category"
categoryname = mapped_column(String(20))
nosort = mapped_column(Integer)
categoryname = Column(String(20))
nosort = Column(Integer)

View File

@@ -13,7 +13,6 @@ class Permission(BaseModel):
id: int
menu:str
function:str
link:str
privilege:str
class RoleBase(BaseModel):
@@ -25,10 +24,6 @@ class RoleBase(BaseModel):
class RoleWithPermission(RoleBase):
permissions:t.List[Permission] = []
class AssignUserRoles(BaseModel):
userid:int
roleids:t.List[int]
class UserBase(BaseModel):
email: str
@@ -56,7 +51,6 @@ class UserCreate(UserBase):
last_name: str
is_active:bool
is_superuser:bool
tenantid:t.Optional[str] = "1"
createuserid:t.Optional[int] = None
updateuserid:t.Optional[int] = None
@@ -88,28 +82,16 @@ class AppList(Base):
domainurl: str
appname: str
appid:str
version:int
is_saved:bool
versionname: t.Optional[str] = None
updateuser: UserOut
createuser: UserOut
version:int
class AppVersion(Base):
class AppVersion(BaseModel):
domainurl: str
appname: str
versionname: str
comment:str
appid:str
version:t.Optional[int] = None
updateuser: UserOut
createuser: UserOut
class VersionUpdate(BaseModel):
appid:str
versionname: str
comment:str
class TokenData(BaseModel):
id:int = 0
@@ -204,21 +186,12 @@ class DomainOut(BaseModel):
class ConfigDict:
orm_mode = True
class UserDomainParam(BaseModel):
userid:int
domainid:int
class UserDomain(BaseModel):
id: int
is_default: bool
domain:DomainOut
user:UserOut
class UserDomainIn(BaseModel):
is_default: bool
domainid:int
userid:int
class Domain(Base):
id: int
tenantid: str

View File

@@ -1,37 +1,20 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base, Session
from sqlalchemy.orm import sessionmaker,declarative_base
from app.core import config
engine = create_engine(
config.SQLALCHEMY_DATABASE_URI,
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# engine = create_engine(
# config.SQLALCHEMY_DATABASE_URI,
# )
# SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
class Database:
def __init__(self, database_url: str):
self.database_url = database_url
self.engine = create_engine(self.database_url)
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
self.Base = declarative_base()
def get_db(self):
db =self.SessionLocal()
return db
tenantdb = Database(config.SQLALCHEMY_DATABASE_URI)
def get_tenant_db():
db = tenantdb.get_db()
# Dependency
def get_db():
db = SessionLocal()
try:
yield db
yield db
finally:
db.close()
def get_user_db(database_url: str):
database = Database(database_url)
db = database.get_db()
return db
db.close()

View File

@@ -8,7 +8,7 @@ from app.api.api_v1.routers.users import users_router
from app.api.api_v1.routers.auth import auth_router
from app.api.api_v1.routers.platform import platform_router
from app.core import config
#from app.db import Base,engine
from app.db import Base,engine
from app.core.auth import get_current_active_user
from app.core.celery_app import celery_app
from app import tasks
@@ -20,9 +20,9 @@ from app.db.crud import create_log
from fastapi.responses import JSONResponse
import asyncio
from contextlib import asynccontextmanager
from app.core.operation import LoggingMiddleware
#Base.metadata.create_all(bind=engine)
Base.metadata.create_all(bind=engine)
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -45,8 +45,6 @@ app.add_middleware(
allow_headers=["*"],
)
app.add_middleware(LoggingMiddleware)
add_pagination(app)
# @app.middleware("http")

View File

@@ -5,170 +5,63 @@ from fastapi.testclient import TestClient
import typing as t
from app.core import config, security
from app.core.dbmanager import get_db
from app.db import models,schemas
from app.db.session import Base, get_db
from app.db import models
from app.main import app
from app.core import security
import jwt
def get_test_db_url() -> str:
return f"{config.SQLALCHEMY_DATABASE_URI}"
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
engine = create_engine(SQLALCHEMY_DATABASE_URI,echo=True)
test_session_maker = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@pytest.fixture(scope="session")
@pytest.fixture
def test_db():
"""
Modify the db session to automatically roll back after each test.
This is to avoid tests affecting the database state of other tests.
"""
# Connect to the test database
engine = create_engine(
get_test_db_url(),
)
connection = engine.connect()
transaction = connection.begin()
test_session = test_session_maker(bind=connection)
trans = connection.begin()
# Run a parent transaction that can roll back all changes
test_session_maker = sessionmaker(
autocommit=False, autoflush=False, bind=engine
)
test_session = test_session_maker()
#test_session.begin_nested()
# @event.listens_for(test_session, "after_transaction_end")
# def restart_savepoint(s, transaction):
# if transaction.nested and not transaction._parent.nested:
# s.expire_all()
# s.begin_nested()
yield test_session
# Roll back the parent transaction after the test is complete
test_session.close()
transaction.rollback()
#transaction.commit()
trans.rollback()
connection.close()
@pytest.fixture(scope="session")
@pytest.fixture(scope="function")
def test_client(test_db):
"""
Get a TestClient instance that reads/write to the test database.
"""
def get_test_db():
try:
yield test_db
finally:
test_db.close()
yield test_db
app.dependency_overrides[get_db] = get_test_db
with TestClient(app) as test_client:
yield test_client
@pytest.fixture(scope="session")
def test_tenant_id():
return "1"
@pytest.fixture(scope="session")
def test_user(test_db,test_tenant_id):
password ="test"
user = models.User(
email = "test@test.com",
first_name = "test",
last_name = "abc",
hashed_password = security.get_password_hash(password),
is_active = True,
is_superuser = False,
tenantid = test_tenant_id
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
dicUser = user.__dict__
dicUser["password"] = password
return dicUser
@pytest.fixture(scope="session")
def password():
return "password"
@pytest.fixture(scope="session")
def user(test_db,password,test_tenant_id):
user = models.User(
email = "user@test.com",
first_name = "user",
last_name = "abc",
hashed_password = security.get_password_hash(password),
is_active = True,
is_superuser = False,
tenantid = test_tenant_id
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
return user.__dict__
@pytest.fixture(scope="session")
def admin(test_db,password,test_tenant_id):
user = models.User(
email = "admin@test.com",
first_name = "admin",
last_name = "abc",
hashed_password = security.get_password_hash(password),
is_active = True,
is_superuser = True,
tenantid =test_tenant_id
)
test_db.add(user)
test_db.commit()
test_db.refresh(user)
return user.__dict__
@pytest.fixture(scope="session")
def login_user(test_db,test_client,user,password):
# test_db.add(user)
# test_db.commit()
#test_db.refresh(user)
response = test_client.post("/api/token", data={"username": user["email"], "password":password })
return response.json()["access_token"]
@pytest.fixture(scope="session")
def login_admin(test_db,test_client,admin,password):
# test_db.add(admin)
# test_db.commit()
#test_db.refresh(admin)
response = test_client.post("/api/token", data={"username": admin["email"], "password":password })
return response.json()["access_token"]
@pytest.fixture(scope="session")
def login_user_id(login_user):
payload = jwt.decode(login_user, security.SECRET_KEY, algorithms=[security.ALGORITHM])
id = payload.get("sub")
return id
@pytest.fixture(scope="session")
def login_admin_id(login_admin):
payload = jwt.decode(login_admin, security.SECRET_KEY, algorithms=[security.ALGORITHM])
id = payload.get("sub")
return id
@pytest.fixture(scope="session")
def test_role(test_db):
role = models.Role(
name = "test",
description = "test",
level = 1
)
test_db.add(role)
test_db.commit()
test_db.refresh(role)
return role.__dict__
@pytest.fixture(scope="session")
def test_domain(test_db,login_user_id):
domain = models.Domain(
tenantid = "1",
name = "テスト環境",
url = "https://mfu07rkgnb7c.cybozu.com",
kintoneuser = "MXZ",
kintonepwd = security.chacha20Encrypt("maxz1205"),
is_active = True,
createuserid =login_user_id,
updateuserid =login_user_id,
ownerid = login_user_id
)
test_db.add(domain)
test_db.flush()
user_domain = models.UserDomain(userid = login_user_id, domainid = domain.id ,createuserid = login_user_id,updateuserid = login_user_id)
test_db.add(user_domain)
manage_domain = models.ManageDomain(userid = login_user_id, domainid = domain.id ,createuserid = login_user_id,updateuserid = login_user_id)
test_db.add(manage_domain)
test_db.commit()
test_db.refresh(domain)
return domain
@pytest.fixture(scope="session")
def test_app_id():
return "132"
# @pytest.fixture
# def test_password() -> str:

View File

@@ -1,5 +0,0 @@
[pytest]
log_cli = 1
log_cli_level = CRITICAL
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
log_cli_date_format=%Y-%m-%d %H:%M:%S

View File

@@ -1,11 +0,0 @@
import logging
def test_usr_login(test_client,test_user):
response = test_client.post("/api/token", data={"username": test_user["email"], "password": test_user["password"]})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "access_token" in response.json()
assert "token_type" in response.json()
assert response.json()["user_name"] == test_user["first_name"]+ " " + test_user["last_name"]

View File

@@ -1,175 +0,0 @@
import logging
def test_get_domains(test_client,test_domain,login_user):
response = test_client.get("/api/domains",headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert len(data["data"]) == 1
assert data["data"][0]["name"] == test_domain.name
def test_create_domain(test_client, login_user,login_user_id):
create_domain ={
"id": 0,
"tenantid": "1",
"name": "abc",
"url": "efg",
"kintoneuser": "eee",
"kintonepwd": "fff",
"is_active": True,
}
response = test_client.post("/api/domain", json=create_domain,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == create_domain["name"]
assert data["data"]["url"] == create_domain["url"]
assert data["data"]["kintoneuser"] == create_domain["kintoneuser"]
assert data["data"]["is_active"] == create_domain["is_active"]
assert data["data"]["owner"]["id"] == login_user_id
def test_get_managedomainuser(test_client,test_domain,login_user,login_user_id):
response = test_client.get("/api/managedomainuser/" + str(test_domain.id),headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert len(data["data"]) == 1
assert data["data"][0]["id"] == login_user_id
def test_add_delete_userdomain(test_client,test_domain,test_user,login_user,login_user_id):
userdomain ={
"userid":test_user["id"],
"domainid":test_domain.id
}
response = test_client.post("/api/userdomain" , json=userdomain,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
response = test_client.delete(f"/api/domain/{test_domain.id}/{test_user["id"]}" , headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
def test_add_delete_managedomain(test_client,test_domain,test_user,login_user,login_user_id):
userdomain ={
"userid":test_user["id"],
"domainid":test_domain.id
}
response = test_client.post("/api/managedomain" , json=userdomain,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
response = test_client.delete(f"/api/managedomain/{test_domain.id}/{test_user["id"]}" , headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
def test_delete_domain(test_client, login_user,login_user_id):
delete_domain ={
"id": 0,
"tenantid": "1",
"name": "delete",
"url": "delete",
"kintoneuser": "delete",
"kintonepwd": "delete",
"is_active": True,
}
response = test_client.post("/api/domain", json=delete_domain,headers={"Authorization": "Bearer " + login_user})
data = response.json()
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
id = data["data"]["id"]
response = test_client.delete(f"/api/domain/{id}/{login_user_id}",headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
response = test_client.delete(f"/api/domain/{id}",headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert response.json()["data"]["name"] == delete_domain["name"]
response = test_client.get(f"/api/domain/{id}", headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
assert "data" not in response.json()
def test_set_defaultuserdomain(test_client, test_domain,login_user):
response = test_client.put("/api/defaultdomain/"+str(test_domain.id), headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
assert data["data"]["kintoneuser"] == test_domain.kintoneuser
assert data["data"]["is_active"] == test_domain.is_active
def test_get_defaultuserdomain(test_client, test_domain,login_user):
response = test_client.get("/api/defaultdomain", headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == test_domain.name
assert data["data"]["url"] == test_domain.url
assert data["data"]["kintoneuser"] == test_domain.kintoneuser
assert data["data"]["is_active"] == test_domain.is_active
def test_get_domainshareduser(test_client, test_domain,login_user,login_user_id):
response = test_client.get("/api/domainshareduser/"+str(test_domain.id), headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert len(data["data"]) == 1
assert data["data"][0]["id"] == login_user_id
def test_edit_domain(test_client, test_domain, login_user):
update_domain ={
"id": test_domain.id,
"tenantid": "1",
"name": "テスト環境abc",
"url": test_domain.url,
"kintoneuser": test_domain.kintoneuser,
"is_active": True
}
response = test_client.put("/api/domain", json=update_domain,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["name"] == update_domain["name"]
assert data["data"]["url"] == update_domain["url"]
assert data["data"]["kintoneuser"] == update_domain["kintoneuser"]
assert data["data"]["is_active"] == update_domain["is_active"]

View File

@@ -1,156 +0,0 @@
import logging
def test_users_list(test_client,login_user):
response = test_client.get("/api/v1/users", headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert len(data["data"]) == 2
def test_users_list_for_admin(test_client,login_admin):
response = test_client.get("/api/v1/users", headers={"Authorization": "Bearer " + login_admin})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert len(data["data"]) == 3
def test_user_create(test_client,login_user):
user_data = {
"email": "newuser1@example.com",
"password": "password123",
"first_name": "New",
"last_name": "User",
"is_active": True,
"is_superuser": False
}
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["id"] > 0
assert data["data"]["email"] == user_data["email"]
assert data["data"]["first_name"] == user_data["first_name"]
assert data["data"]["last_name"] == user_data["last_name"]
assert data["data"]["is_active"] == user_data["is_active"]
assert data["data"]["is_superuser"] == user_data["is_superuser"]
def test_admin_create(test_client,login_user):
user_data = {
"email": "newuser2@example.com",
"password": "password123",
"first_name": "New",
"last_name": "User",
"is_active": True,
"is_superuser": True
}
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert "data" not in data
def test_admin_create_for_admin(test_client,login_admin):
user_data = {
"email": "admin@example.com",
"password": "password123",
"first_name": "New",
"last_name": "User",
"is_active": True,
"is_superuser": True
}
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_admin})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["id"] > 0
assert data["data"]["email"] == user_data["email"]
assert data["data"]["first_name"] == user_data["first_name"]
assert data["data"]["last_name"] == user_data["last_name"]
assert data["data"]["is_active"] == user_data["is_active"]
assert data["data"]["is_superuser"] == user_data["is_superuser"]
def test_user_details(test_client,login_user_id, login_user,user):
id = login_user_id
response = test_client.get("/api/v1/users/"+ str(id), headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert data["data"]["email"] == user["email"]
assert data["data"]["first_name"] == user["first_name"]
assert data["data"]["last_name"] == user["last_name"]
assert data["data"]["is_active"] == user["is_active"]
assert data["data"]["is_superuser"] == user["is_superuser"]
assert data["data"]["id"] == id
def test_user_edit(test_client, login_user_id,login_user,user):
id = login_user_id
user_data = {
"email": user["email"],
"first_name": "Updated",
"last_name": "test",
"is_active": True,
"is_superuser": False
}
response = test_client.put("/api/v1/users/" + str(id), json=user_data, headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert data["data"]["email"] == user["email"]
assert data["data"]["first_name"] == user_data["first_name"]
assert data["data"]["last_name"] == user_data["last_name"]
assert data["data"]["is_active"] == user["is_active"]
assert data["data"]["id"] == id
def test_user_delete(test_client, login_user):
user_data = {
"email": "delete@example.com",
"password": "password123",
"first_name": "delete",
"last_name": "User",
"is_active": True,
"is_superuser": False
}
response = test_client.post("/api/v1/users", json=user_data, headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
id = data["data"]["id"]
response = test_client.delete("/api/v1/users/"+ str(id),headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
assert response.json()["data"]["email"] == "delete@example.com"
response = test_client.get("/api/v1/users/"+ str(id), headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
assert "data" not in response.json()
def test_role_assign(test_client, login_user_id,login_user,test_role):
userroles ={
"userid":login_user_id,
"roleids":[test_role["id"]]
}
response = test_client.post("/api/v1/userrole", json=userroles, headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
response = test_client.get("/api/v1/users/"+ str(login_user_id), headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert len(data["data"]["roles"]) == 1
def test_roles_get(test_client,login_user):
response = test_client.get("/api/v1/roles", headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert len(data["data"]) == 0
def test_roles_admin_get(test_client,login_admin):
response = test_client.get("/api/v1/roles", headers={"Authorization": "Bearer " + login_admin})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert len(data["data"]) == 1

View File

@@ -1,121 +0,0 @@
import logging
def test_create_flow(test_client,test_domain,test_app_id,login_user):
test_flow={
"flowid": "73e82bee-76a2-4347-a069-e21bf5e21111",
"appid": test_app_id,
"appname": "test_app",
"eventid": "a",
"name": "保存をクリックしたとき",
"content": ""
}
response = test_client.post("/api/flow", json=test_flow,headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert data["data"] is not None
assert data["data"]["domainurl"] == test_domain.url
assert data["data"]["flowid"] == test_flow["flowid"]
assert data["data"]["appid"] == test_flow["appid"]
assert data["data"]["eventid"] == test_flow["eventid"]
assert data["data"]["content"] == test_flow["content"]
def test_delete_flow(test_client,test_domain,test_app_id,login_user):
response = test_client.delete("/api/flow/73e82bee-76a2-4347-a069-e21bf5e21111",headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
def test_edit_flow(test_client,test_domain,test_app_id,login_user):
test_flow={
"flowid": "73e82bee-76a2-4347-a069-e21bf5e21111",
"appid": test_app_id,
"appname": "test_app_new",
"eventid": "abc",
"name": "保存をクリックしたとき",
"content": ""
}
response = test_client.put("/api/flow", json=test_flow,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["domainurl"] == test_domain.url
assert data["data"]["flowid"] == test_flow["flowid"]
assert data["data"]["appid"] == test_flow["appid"]
assert data["data"]["eventid"] == test_flow["eventid"]
assert data["data"]["content"] == test_flow["content"]
def test_appversions_update(test_client,test_domain,test_app_id,login_user):
app_version ={
"versionname": "version1",
"comment": "save version1",
"appid": test_app_id
}
response = test_client.post("/api/apps", json=app_version,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["domainurl"] == test_domain.url
assert data["data"]["version"] == 1
assert data["data"]["appid"] == app_version["appid"]
assert data["data"]["versionname"] == app_version["versionname"]
assert data["data"]["is_saved"] == False
def test_apps_list(test_client,login_user):
response = test_client.get("/api/apps", headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert data["data"] is not None
assert len(data["data"]) == 1
def test_appversions_list(test_client,test_domain,test_app_id,login_user):
response = test_client.get("/api/appversions/" + test_app_id , headers={"Authorization": "Bearer " + login_user})
assert response.status_code == 200
data = response.json()
assert "data" in data
assert data["data"] is not None
assert len(data["data"]) == 1
assert "versionname" in data["data"][0]
def test_appversions_change(test_client,test_domain,test_app_id,login_user):
app_version ={
"versionname": "version2",
"comment": "test",
"appid": test_app_id
}
response = test_client.post("/api/apps", json=app_version,headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["version"] == 2
assert data["data"]["versionname"] == app_version["versionname"]
assert data["data"]["is_saved"] == False
response = test_client.put("/api/appversions/" + test_app_id +"/1", headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None
assert data["data"]["domainurl"] == test_domain.url
assert data["data"]["version"] == 1
assert data["data"]["appid"] == test_app_id
def test_delete_app(test_client,test_app_id,login_user):
response = test_client.delete("/api/apps/"+ test_app_id, headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "data" in data
assert data["data"] is not None

View File

@@ -1,9 +0,0 @@
import logging
def test_get_allapps(test_client,test_domain,login_user):
response = test_client.get("/api/v1/allapps", headers={"Authorization": "Bearer " + login_user})
data = response.json()
logging.error(data)
assert response.status_code == 200
assert "apps" in data
assert data["apps"] is not None
assert len(data["apps"]) > 0

View File

@@ -29,11 +29,3 @@ python -m venv env
```bash
uvicorn app.main:app --reload
```
# ZCC対応
1. ENV環境中Pythone現在使用している証明書(cacert.pem)のパス確認
```
python -m certifi
# C:\Projects\AI-IOT\AppBuilderforkintone\backend\env\Scripts\python.exe: No module named certifi
```
2. 上記のコマンドを実行すると、証明書までのパスが出てくるので、どこかにメモしてください。次のコマンドで使います。

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -37,8 +37,7 @@ module.exports = configure(function (/* ctx */) {
// https://v2.quasar.dev/quasar-cli-vite/boot-files
boot: [
'axios',
'error-handler',
'permissions'
'error-handler'
],
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
@@ -105,7 +104,7 @@ module.exports = configure(function (/* ctx */) {
config: {},
// iconSet: 'material-icons', // Quasar icon set
lang: 'ja', // Quasar language pack
// lang: 'en-US', // Quasar language pack
// For special cases outside of where the auto-import strategy can have an impact
// (like functional components as one of the examples),

View File

@@ -1,7 +1,6 @@
import { boot } from 'quasar/wrappers';
import axios, { AxiosInstance, AxiosResponse } from 'axios';
import axios, { AxiosInstance } from 'axios';
import {router} from 'src/router';
import { IResponse } from 'src/types/BaseTypes';
declare module '@vue/runtime-core' {

View File

@@ -4,14 +4,14 @@ import { Router } from 'vue-router';
import { App } from 'vue';
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
document.documentElement.lang='ja-JP';
document.documentElement.lang="ja-JP";
app.config.errorHandler = (err: any, instance: any, info: string) => {
if (err.response && err.response.status === 401) {
// 認証エラーの場合再ログインする
console.error('(; ゚Д゚)/認証エラー(401)', err, info);
localStorage.removeItem('token');
router.replace({
path:'/login',
path:"/login",
query:{redirect:router.currentRoute.value.fullPath}
});
} else {

View File

@@ -1,75 +0,0 @@
// src/boot/permissions.ts
import { boot } from 'quasar/wrappers';
import { useAuthStore } from 'src/stores/useAuthStore';
import { DirectiveBinding } from 'vue';
export const MenuMapping = {
home: null,
app: null,
version: null,
user: 'user',
role: 'role',
domain: null,
userDomain: null,
};
export const Actions = {
user: {
show: 'user_list',
add: 'user_add',
edit: 'user_edit',
delete: 'user_delete',
},
role: {
show: 'role_list',
},
domain: {
show: 'domain_list',
add: 'domain_add',
edit: 'domain_edit',
delete: 'domain_delete',
grantUse: {
list: 'domain_grant_use_list',
edit: 'domain_grant_use_edit',
},
grantManage: {
list: 'domain_grant_manage_list',
edit: 'domain_grant_manage_edit',
},
},
};
const store = useAuthStore();
export default boot(({ app }) => {
app.directive('permissions', {
mounted(el: HTMLElement, binding: DirectiveBinding) {
if (!hasPermission(binding.value)) {
hideElement(el);
}
},
});
app.config.globalProperties.$hasPermission = hasPermission;
});
function hasPermission(value: any) {
if (!value || store.isSuperAdmin) {
return true;
}
if (typeof value === 'string') {
return store.permissions[value];
} else if (typeof value === 'object') {
return Object.values(value).some((permission: any) => store.permissions[permission]);
} else if (Array.isArray(value)) {
return value.some((permission) => store.permissions[permission]);
}
}
function hideElement(el: HTMLElement) {
if (el.parentNode) {
el.parentNode.removeChild(el);
} else {
el.style.display = 'none';
}
}

View File

@@ -52,7 +52,7 @@ export default {
filter:String
},
emits:[
'clearFilter'
"clearFilter"
],
setup(props,{emit}) {
const isLoaded=ref(false);

View File

@@ -46,9 +46,9 @@ export default defineComponent({
const { app } = toRefs(props);
const authStore = useAuthStore();
const appinfo = ref<AppInfo>({
appId: '',
name: '',
description: ''
appId: "",
name: "",
description: ""
});
const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
const getAppInfo = async (appId:string|undefined) => {
@@ -56,7 +56,7 @@ export default defineComponent({
return;
}
let result : any ={appId:'',name:''};
let result : any ={appId:"",name:""};
let retry =0;
while(retry<=3 && result && result.appId!==appId){
await new Promise(resolve => setTimeout(resolve, 1000));

View File

@@ -175,7 +175,7 @@ export default defineComponent({
if (flowStore.appInfo?.appId === selected.id) {
$q.notify({
type: 'negative',
caption: 'エラー',
caption: "エラー",
message: 'データソースを現在のアプリにすることはできません。'
});
} else if (selected.id !== data.value.sourceApp.id) {
@@ -208,7 +208,7 @@ export default defineComponent({
if (isDuplicate) {
$q.notify({
type: 'negative',
caption: 'エラー',
caption: "エラー",
message: '重複したフィールドは選択できません'
});
} else {

View File

@@ -42,9 +42,9 @@ import { useQuasar } from 'quasar';
}
},
emits:[
'closed',
'update:conditionTree',
'update:show'
"closed",
"update:conditionTree",
"update:show"
],
setup(props,context) {
const appDg = ref();
@@ -58,11 +58,11 @@ import { useQuasar } from 'quasar';
// message: `条件式を設定してください。`
// });
// }
context.emit('update:conditionTree',tree.value);
context.emit("update:conditionTree",tree.value);
}
showflg.value=false;
context.emit('update:show',false);
context.emit('closed',val);
context.emit("update:show",false);
context.emit("closed",val);
};
const showflg =ref(props.show);
//条件式をコピーする

View File

@@ -217,7 +217,7 @@ export default defineComponent( {
const canMerge =(node:INode)=>{
const checkedIndexs:number[] = ticked.value;
const findNode = checkedIndexs.find(index=>node.index===index);
console.log('findNode=>',findNode!==undefined,findNode);
console.log("findNode=>",findNode!==undefined,findNode);
return findNode!==undefined;
}
//グループ化解散

View File

@@ -1,105 +1,98 @@
<template>
<div class="q-pa-md">
<q-uploader
style="max-width: 400px"
:url="uploadUrl"
:label="title"
:headers="headers"
accept=".xlsx"
v-on:rejected="onRejected"
v-on:uploaded="onUploadFinished"
v-on:failed="onFailed"
field-name="files"
style="max-width: 400px"
:url="uploadUrl"
:label="title"
:headers="headers"
accept=".xlsx"
v-on:rejected="onRejected"
v-on:uploaded="onUploadFinished"
v-on:failed="onFailed"
field-name="files"
></q-uploader>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { createUploaderComponent, useQuasar } from 'quasar';
import { useAuthStore } from 'src/stores/useAuthStore';
import { ref } from 'vue';
const $q=useQuasar();
const authStore = useAuthStore();
const emit =defineEmits(['uploaded']);
/**
* ファイルアップロードを拒否する時の処理
* @param rejectedEntries
*/
function onRejected (rejectedEntries:any) {
// Notify plugin needs to be installed
// https://quasar.dev/quasar-plugins/notify#Installation
$q.notify({
type: 'negative',
message: `Excelファイルを選択してください。`
})
}
const $q = useQuasar();
const authStore = useAuthStore();
const emit = defineEmits(['uploaded']);
/**
* ファイルアップロード成功時の処理
*/
function onUploadFinished({xhr}:{xhr:XMLHttpRequest}){
let msg="ファイルのアップロードが完了しました。";
if(xhr && xhr.response){
msg=`${msg} (${xhr.responseText})`;
}
$q.notify({
type: 'positive',
caption:"通知",
message: msg
});
setTimeout(() => {
emit('uploaded',xhr.responseText);
}, 2000);
}
interface Props {
title?: string;
uploadUrl?: string;
}
/**
* 例外発生時、responseからエラー情報を取得する
* @param xhr
*/
function getResponseError(xhr:XMLHttpRequest){
try{
const resp = JSON.parse(xhr.responseText);
return 'detail' in resp ? resp.detail:'';
}catch(err){
return xhr.responseText;
}
}
const headers = ref([
{ name: 'Authorization', value: 'Bearer ' + authStore.token },
]);
/**
*
* @param info ファイルアップロード失敗時の処理
*/
function onFailed({files,xhr}:{files: readonly any[],xhr:XMLHttpRequest}){
let msg ="ファイルアップロードが失敗しました。";
if(xhr && xhr.status){
const detail = getResponseError(xhr);
msg=`${msg} (${xhr.status }:${detail})`
}
$q.notify({
type:"negative",
message:msg
});
}
const props = withDefaults(defineProps<Props>(), {
title: '設計書から導入する(Excel)',
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`,
});
interface Props {
title: string;
uploadUrl:string;
}
/**
* ファイルアップロードを拒否する時の処理
* @param rejectedEntries
*/
function onRejected(rejectedEntries: any) {
// Notify plugin needs to be installed
// https://quasar.dev/quasar-plugins/notify#Installation
$q.notify({
type: 'negative',
message: 'Excelファイルを選択してください。',
});
}
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
const props = withDefaults(defineProps<Props>(), {
title:"設計書から導入する(Excel)",
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
/**
* ファイルアップロード成功時の処理
*/
function onUploadFinished({ xhr }: { xhr: XMLHttpRequest }) {
let msg = 'ファイルのアップロードが完了しました。';
if (xhr && xhr.response) {
msg = `${msg} (${xhr.responseText})`;
}
$q.notify({
type: 'positive',
caption: '通知',
message: msg,
});
setTimeout(() => {
emit('uploaded', xhr.responseText);
}, 2000);
}
/**
* 例外発生時、responseからエラー情報を取得する
* @param xhr
*/
function getResponseError(xhr: XMLHttpRequest) {
try {
const resp = JSON.parse(xhr.responseText);
return 'detail' in resp ? resp.detail : '';
} catch (err) {
return xhr.responseText;
}
}
/**
*
* @param info ファイルアップロード失敗時の処理
*/
function onFailed({
files,
xhr,
}: {
files: readonly any[];
xhr: XMLHttpRequest;
}) {
let msg = 'ファイルアップロードが失敗しました。';
if (xhr && xhr.status) {
const detail = getResponseError(xhr);
msg = `${msg} (${xhr.status}:${detail})`;
}
$q.notify({
type: 'negative',
message: msg,
});
}
});
</script>
<style lang="scss"></style>
<style lang="scss">
</style>

View File

@@ -42,7 +42,7 @@ export default {
onMounted(() => {
loading.value = true;
api.get('api/domains').then(res =>{
api.get(`api/domains`).then(res =>{
res.data.data.forEach((data) => {
const item = {
id: data.id,

View File

@@ -8,25 +8,65 @@
size="md"
:label="userStore.currentDomain.domainName"
:disable-dropdown="true"
dropdown-icon="none"
dropdown-icon='none'
:disable="true"
>
<q-list>
<q-item :active="isCurrentDomain(domain)" active-class="active-domain-item" v-for="domain in domains" :key="domain.domainName"
clickable v-close-popup @click="onItemClick(domain)">
<q-item-section side>
<q-icon name="share" size="sm" :color="isCurrentDomain(domain) ? 'orange': ''" text-color="white"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>{{domain.domainName}}</q-item-label>
<q-item-label caption>{{domain.kintoneUrl}}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-btn-dropdown>
</template>
<script setup lang="ts">
<script setup lang="ts" >
import { IDomainInfo } from 'src/types/DomainTypes';
import { useAuthStore } from 'stores/useAuthStore';
import { useDomainStore } from 'src/stores/useDomainStore';
import { computed } from 'vue';
import { useRoute } from 'vue-router';
const userStore = useAuthStore();
const domainStore = useDomainStore();
const route = useRoute()
const domains = computed(() => domainStore.userDomains);
(async ()=>{
await domainStore.loadUserDomains();
})();
const isUnclickable = computed(()=>{
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
});
const isCurrentDomain=(domain:IDomainInfo)=>{
return domain.id === userStore.currentDomain.id;
}
const onItemClick=(domain:IDomainInfo)=>{
console.log(domain);
userStore.setCurrentDomain(domain);
}
</script>
<style lang="scss">
.q-btn.disabled.customized-disabled-btn {
opacity: 1 !important;
cursor: default !important;
.q-icon.q-btn-dropdown__arrow {
display: none;
}
* {
cursor: default !important;
}
}
.q-item.active-domain-item {
color: inherit;
background: #eee;
}
.q-btn.disabled.customized-disabled-btn * {
cursor: default !important;
}
</style>

View File

@@ -1,6 +1,5 @@
<template>
<q-item
v-permissions="permission"
clickable
tag="a"
:target="target?target:'_blank'"
@@ -36,7 +35,6 @@ export interface EssentialLinkProps {
isSeparator?: boolean;
target?:string;
disable?:boolean;
permission?: string|null;
}
withDefaults(defineProps<EssentialLinkProps>(), {
caption: '',

View File

@@ -73,14 +73,14 @@ export default {
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
rows.push({id:index, name: fld.label || fld.code, ...fld });
}else if(props.fieldTypes.includes('lookup') && ('lookup' in fld)){
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
rows.push({id:index, name: fld.label || fld.code, ...fld });
}
}
} else {
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
rows.push({id:index, name: fld.label || fld.code, ...fld });
}else if(props.fieldTypes.includes('lookup') && ('lookup' in fld)){
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
rows.push({id:index, name: fld.label || fld.code, ...fld });
}
}

View File

@@ -1,24 +0,0 @@
<template>
<q-badge class="q-mr-xs" v-if="isOwner" color="secondary">所有者</q-badge>
<!-- <q-badge v-else-if="isManager" color="primary">管理者</q-badge> -->
<q-badge v-if="isSelf" color="purple">自分</q-badge>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
interface Props {
user: { id: number };
domain: IDomainOwnerDisplay;
}
const props = defineProps<Props>();
const isSelf = computed(() => props.user.id === (Number)(authStore.userId));
const isOwner = computed(() => props.user.id === props.domain.owner.id);
const isManager = computed(() => props.user.id === props.domain.owner.id); // TODO
</script>

View File

@@ -2,13 +2,12 @@
<q-dialog :auto-close="false" :model-value="visible" persistent bordered>
<q-card class="dialog-content" >
<q-toolbar class="bg-grey-4">
<q-toolbar-title>{{ dialogTitle }}</q-toolbar-title>
<q-toolbar-title>{{domain.name}}のドメイン利用権限設定</q-toolbar-title>
<q-btn flat round dense icon="close" @click="close" />
</q-toolbar>
<q-card-section class="q-mx-md " >
<q-select
v-permissions="props.actionPermissions.edit"
class="q-mt-md"
:disable="loading||!domain.domainActive"
filled
@@ -19,17 +18,11 @@
:options="canSharedUserFilteredOptions"
clearable
:placeholder="canSharedUserFilter ? '' : domain.domainActive ? '権限を付与するユーザーを選択' : 'ドメインが無効なため、権限を付与できません'"
@filter="filterFn">
<template v-slot:selected-item="scope">
<span v-if="canSharedUserFilter">
{{ canSharedUserFilter.fullName }} {{ canSharedUserFilter.email }}
<role-label :domain="domain" :user="scope.opt"></role-label>
</span>
</template>
@filter="filterFn"
:display-value="canSharedUserFilter?`${canSharedUserFilter.fullName} ${canSharedUserFilter.email}`:''">
<template v-slot:after>
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplayWithShareRole)" />
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplay)" />
</template>
<template v-slot:option="scope">
@@ -37,31 +30,18 @@
<q-item-section avatar>{{scope.opt.id}}</q-item-section>
<q-item-section>{{scope.opt.fullName}}</q-item-section>
<q-item-section>{{scope.opt.email}}</q-item-section>
<q-item-section side>
<div style="width: 6.5em;">
<role-label :domain="domain" :user="scope.opt"></role-label>
</div>
</q-item-section>
</q-item>
</template>
</q-select>
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" :title="userListTitle">
<template v-slot:body-cell-role="{ row }">
<q-td auto-width>
<role-label :domain="domain" :user="row"></role-label>
</q-td>
</template>
<sharing-user-list class="q-mt-md" style="height: 330px" :users="sharedUsers" :loading="loading" title="ドメイン利用権限を持つユーザー">
<template v-slot:actions="{ row }">
<q-btn v-permissions="props.actionPermissions.edit" round title="解除" flat color="primary" :disable="isActionDisable && isActionDisable(row)" padding="xs" size="1em" :loading="row.isRemoving" icon="person_off" @click="removeShareTo(row)" />
<q-btn title="解除" flat color="primary" padding="xs" size="1em" :loading="row.id == removingUser?.id" icon="person_off" @click="removeShareTo(row)" />
</template>
</sharing-user-list>
</q-card-section>
<q-card-actions align="right" class="text-primary">
<q-btn flat label="確定" @click="checkClose" />
<q-btn flat label="確定" @click="close" />
<q-btn flat label="キャンセル" @click="close" />
</q-card-actions>
</q-card>
@@ -69,32 +49,15 @@
</template>
<script setup lang="ts">
import { ref, watch } from 'vue';
import { ref, watch, onMounted } from 'vue';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { IUser, IUserDisplay } from '../../types/UserTypes';
import { api } from 'boot/axios';
import { useAuthStore } from 'stores/useAuthStore';
import SharingUserList from 'components/ShareDomain/SharingUserList.vue';
import RoleLabel from 'components/ShareDomain/RoleLabel.vue';
import { Dialog } from 'quasar'
const authStore = useAuthStore();
interface Props {
modelValue: boolean;
domain: IDomainOwnerDisplay;
dialogTitle: string;
userListTitle: string;
isActionDisable?: (user: IUserDisplay) => boolean;
shareApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
removeSharedApi: (user: IUserDisplay, domain: IDomainOwnerDisplay) => Promise<any>;
getSharedApi: (domain: IDomainOwnerDisplay) => Promise<any>;
actionPermissions: { 'list': string, 'edit': string };
}
interface IUserDisplayWithShareRole extends IUserDisplay {
isRemoving: boolean;
role: number;
}
const props = defineProps<Props>();
@@ -105,16 +68,17 @@ const emit = defineEmits<{
}>();
const addLoading = ref(false);
const removingUser = ref<IUserDisplay>();
const loading = ref(true);
const visible = ref(props.modelValue);
const allUsers = ref<IUserDisplayWithShareRole[]>([]);
const sharedUsers = ref<IUserDisplayWithShareRole[]>([]);
const allUsers = ref<IUserDisplay[]>([]);
const sharedUsers = ref<IUserDisplay[]>([]);
const sharedUsersIdSet = new Set<number>();
const canSharedUsers = ref<IUserDisplayWithShareRole[]>([]);
const canSharedUserFilter = ref<IUserDisplayWithShareRole>();
const canSharedUserFilteredOptions = ref<IUserDisplayWithShareRole[]>([]);
const canSharedUsers = ref<IUserDisplay[]>([]);
const canSharedUserFilter = ref<IUserDisplay>();
const canSharedUserFilteredOptions = ref<IUserDisplay[]>([]);
const filterFn = (val:string, update: (cb: () => void) => void) => {
update(() => {
@@ -134,12 +98,7 @@ watch(
visible.value = newValue;
sharedUsers.value = [];
canSharedUserFilter.value = undefined
loading.value = false;
addLoading.value = false;
if (newValue) {
if (Object.keys(allUsers.value).length == 0) {
await getUsers();
}
await loadShared();
}
}
@@ -152,79 +111,39 @@ watch(
}
);
const checkClose = () => {
if (!canSharedUserFilter.value) {
close();
return;
}
Dialog.create({
title: '注意',
message: '選択済だがまだ付与未完了のユーザーがあります。<br>必要な操作を選んでください。',
html: true,
persistent: true,
ok: {
color: 'primary',
label: '付与'
},
cancel: '直接閉じる',
}).onCancel(() => {
close();
}).onOk(() => {
shareTo(canSharedUserFilter.value as IUserDisplayWithShareRole);
});
};
const close = () => {
emit('close');
};
const shareTo = async (user: IUserDisplayWithShareRole) => {
const shareTo = async (user: IUserDisplay) => {
addLoading.value = true;
loading.value = true;
await props.shareApi(user, props.domain);
await api.post(`api/domain/${user.id}?domainid=${props.domain.id}`)
await loadShared();
canSharedUserFilter.value = undefined;
loading.value = false;
addLoading.value = false;
}
const removeShareTo = async (user: IUserDisplayWithShareRole) => {
const removeShareTo = async (user: IUserDisplay) => {
removingUser.value = user;
loading.value = true;
user.isRemoving = true;
await props.removeSharedApi(user, props.domain);
if (isCurrentDomain()) {
await authStore.loadCurrentDomain();
}
await api.delete(`api/domain/${props.domain.id}/${user.id}`)
await loadShared();
loading.value = false;
removingUser.value = undefined;
};
const isCurrentDomain = () => {
return props.domain.id === authStore.currentDomain.id;
}
const loadShared = async () => {
loading.value = true;
sharedUsersIdSet.clear();
const { data } = await props.getSharedApi(props.domain);
sharedUsers.value = data.data.reduce((arr: IUserDisplayWithShareRole[], item: IUser) => {
const { data } = await api.get(`/api/domainshareduser/${props.domain.id}`);
sharedUsers.value = data.data.map((item: IUser) => {
const val = itemToDisplay(item);
if(!sharedUsersIdSet.has(val.id)) {
sharedUsersIdSet.add(val.id);
// for sort
if (isOwner(val.id)) {
val.role = 2;
} else if (isManager(val.id)) {
val.role = 1;
} else {
val.role = 0;
}
arr.push(val);
}
return arr;
}, []).sort((a: IUserDisplayWithShareRole, b: IUserDisplayWithShareRole) => b.role - a.role);
sharedUsersIdSet.add(val.id);
return val;
});
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
canSharedUserFilteredOptions.value = canSharedUsers.value;
@@ -232,17 +151,16 @@ const loadShared = async () => {
loading.value = false;
}
function isOwner(userId: number) {
return userId === props.domain?.owner?.id
}
function isManager(userId: number) {
return false // TODO
}
onMounted(async () => {
await getUsers();
})
const getUsers = async () => {
if (Object.keys(allUsers.value).length > 0) {
return;
}
loading.value = true;
const result = await api.get('api/v1/users');
const result = await api.get(`api/v1/users`);
allUsers.value = result.data.data.map(itemToDisplay);
loading.value = false;
}
@@ -257,16 +175,13 @@ const itemToDisplay = (item: IUser) => {
email: item.email,
isSuperuser: item.is_superuser,
isActive: item.is_active,
role: 0,
} as IUserDisplayWithShareRole
} as IUserDisplay
}
</script>
<style lang="scss">
.dialog-content {
width: 700px !important;
max-width: 80vw !important;
width: 60vw;
max-height: 80vh;
.q-select {
min-width: 0 !important;

View File

@@ -1,63 +0,0 @@
<template>
<share-domain-dialog
:dialogTitle="`「${domain.name}」の接続先管理権限設定`"
userListTitle="接続先管理権限を持つユーザー"
:domain="domain"
:share-api="shareApi"
:remove-shared-api="removeSharedApi"
:get-shared-api="getSharedApi"
:is-action-disable="(row) => row.id === authStore.userId"
:model-value="modelValue"
:action-permissions="Actions.domain.grantManage"
@update:modelValue="updateModelValue"
@close="close"
/>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import { IUserDisplay } from '../../types/UserTypes';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
import { useAuthStore } from 'src/stores/useAuthStore';
import { IResponse } from 'src/types/BaseTypes';
const authStore = useAuthStore();
interface Props {
modelValue: boolean;
domain: IDomainOwnerDisplay;
}
const props = defineProps<Props>();
async function shareApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
return api.post('api/managedomain', {
userid: user.id,
domainid: domain.id,
});
}
async function removeSharedApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
return api.delete<IResponse>(`api/managedomain/${domain.id}/${user.id}`);
}
async function getSharedApi(domain: IDomainOwnerDisplay) {
return api.get<IResponse>(`/api/managedomainuser/${domain.id}`);
}
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'close'): void;
}>();
const updateModelValue = (value: boolean) => {
emit('update:modelValue', value);
};
const close = () => {
emit('close');
};
</script>

View File

@@ -1,58 +0,0 @@
<template>
<share-domain-dialog
:dialogTitle="`「${domain.name}」のドメイン利用権限設定`"
userListTitle="ドメイン利用権限を持つユーザー"
:domain="domain"
:share-api="shareApi"
:remove-shared-api="removeSharedApi"
:get-shared-api="getSharedApi"
:model-value="modelValue"
:action-permissions="Actions.domain.grantUse"
@update:modelValue="updateModelValue"
@close="close"
/>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import { IUserDisplay } from '../../types/UserTypes';
import { IDomainOwnerDisplay } from '../../types/DomainTypes';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
interface Props {
modelValue: boolean;
domain: IDomainOwnerDisplay;
}
const props = defineProps<Props>();
async function shareApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
return api.post('api/userdomain', {
userid: user.id,
domainid: domain.id,
});
}
async function removeSharedApi(user: IUserDisplay, domain: IDomainOwnerDisplay) {
return api.delete(`api/domain/${domain.id}/${user.id}`);
}
async function getSharedApi(domain: IDomainOwnerDisplay) {
return api.get(`/api/domainshareduser/${domain.id}`);
}
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'close'): void;
}>();
const updateModelValue = (value: boolean) => {
emit('update:modelValue', value);
};
const close = () => {
emit('close');
};
</script>

View File

@@ -10,17 +10,10 @@
</q-input>
</template>
<template v-for="col in columns" :key="col.name" v-slot:[`body-cell-${col.name}`]="props">
<slot :name="`body-cell-${col.name}`" :row="props.row" :column="props.col">
<!-- 默认内容 -->
<q-td v-if="col.name !== 'actions'" :props="props" >
<span>{{ props.row[col.name] }}</span>
</q-td>
<!-- actions -->
<q-td v-else auto-width :props="props">
<slot name="actions" :row="props.row"></slot>
</q-td>
</slot>
<template v-slot:body-cell-actions="props">
<q-td :props="props">
<slot name="actions" :row="props.row"></slot>
</q-td>
</template>
</q-table>
</template>
@@ -44,10 +37,9 @@ const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
{ name: 'fullName', label: '名前', field: 'fullName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'role', label: '', field: 'role', align: 'left', sortable: false },
{ name: 'actions', label: '', field: 'actions', sortable: false },
];
const filter = ref('');
const pagination = ref({ rowsPerPage: 10 });
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
</script>

View File

@@ -12,7 +12,7 @@
<slot></slot>
</q-card-section>
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
<q-btn flat :label="okBtnLabel || '確定'" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
<q-btn flat label="キャンセル" :disable="okBtnLoading" v-close-popup @click="CloseDialogue('Cancel')" />
</q-card-actions>
</q-card>
@@ -30,7 +30,6 @@ export default {
height:String,
minWidth:String,
minHeight:String,
okBtnLabel:String,
okBtnLoading:Boolean,
okBtnAutoClose:{
type: Boolean,

View File

@@ -1,17 +1,13 @@
<template>
<q-btn v-if="hasPermission()" flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
<q-menu :max-width="maxWidth">
<q-list dense :style="{ 'min-width': minWidth }">
<q-btn flat padding="xs" round size="1em" icon="more_vert" class="action-menu">
<q-menu>
<q-list dense :style="'min-width:' + minWidth ">
<template v-for="(item, index) in actions" :key="index" >
<q-item v-if="isAction(item)" v-permissions="item.permission" :disable="isFunction(item.disable) ? item.disable(row) : item.disable"
:class="item.class" clickable v-close-popup @click="item.action(row)">
<q-item v-if="isAction(item)" :class="item.class" clickable v-close-popup @click="item.action(row)">
<q-item-section side style="color: inherit;">
<q-icon size="1.2em" :name="item.icon" />
</q-item-section>
<q-item-section>{{ item.label }}</q-item-section>
<q-tooltip v-if="item.tooltip && !isFunction(item.tooltip) || (isFunction(item.tooltip) && item.tooltip(row))" :delay="500" self="center middle">
{{ isFunction(item.tooltip) ? item.tooltip(row) : item.tooltip }}
</q-tooltip>
</q-item>
<q-separator v-else />
</template>
@@ -21,15 +17,12 @@
</template>
<script lang="ts">
import { PropType, getCurrentInstance } from 'vue';
import { PropType } from 'vue';
import { IDomainOwnerDisplay } from '../types/DomainTypes';
interface Action {
label: string;
icon?: string;
tooltip?: string|((row: IDomainOwnerDisplay) => string);
disable?: boolean|((row: IDomainOwnerDisplay) => boolean);
permission?: string|object;
action: (row: any) => void|Promise<void>;
class?: string;
}
@@ -45,10 +38,6 @@ export default {
type: Object as PropType<IDomainOwnerDisplay>,
required: true
},
maxWidth: {
type: String,
default: '150px'
},
minWidth: {
type: String,
default: '100px'
@@ -61,32 +50,16 @@ export default {
methods: {
isAction(item: MenuItem): item is Action {
return !('separator' in item);
},
isFunction(item: any): item is ((row: IDomainOwnerDisplay) => boolean|string) {
return typeof item === 'function';
},
hasPermission() {
const proxy = getCurrentInstance()?.proxy;
if (!proxy) {
return false;
}
for (const item of this.actions) {
if (this.isAction(item) && proxy.$hasPermission(item.permission)) {
return true;
}
}
}
}
};
</script>
<style lang="scss" scoped>
.q-table tr > td:last-child .action-menu {
opacity: 0.25 !important;
opacity: 0.25;
}
.q-table tr:hover > td:last-child .action-menu:not([disabled]) {
opacity: 1 !important;
.q-table tr:hover > td:last-child .action-menu {
opacity: 1;
}
</style>

View File

@@ -6,14 +6,9 @@
<div class="text-h6 ellipsis">{{ item.name }}</div>
<div class="text-subtitle2">{{ item.url }}</div>
</div>
<div class="col-auto">
<div v-if="!isOwnerFunc(item.owner.id)" class="col-auto">
<!-- <q-badge color="secondary" text-color="white" align="middle" class="q-mb-xs" label="他人の所有" /> -->
<q-chip v-if="!isOwnerFunc(item.owner.id)" square color="secondary" text-color="white" icon="people" label="他人の所有" size="sm" />
<q-chip v-else square color="purple" text-color="white" icon="people" label="自分" size="sm" />
<div class="text-right">
<!-- icon="add_moderator" -->
<!-- <q-chip square color="primary" text-color="white" label="管理者" size="sm" /> -->
</div>
<q-chip square color="secondary" text-color="white" icon="people" label="他人の所有" size="sm" />
</div>
</div>
@@ -30,7 +25,7 @@
<div class="text-grey-7 text-caption text-weight-medium">
所有者
</div>
<div class="smaller-font-size">{{ !isOwnerFunc(item.owner.id) ? item.owner.fullName : '自分' }}</div>
<div class="smaller-font-size">{{ item.owner.fullName }}</div>
</div>
</div>
</q-card-section>

View File

@@ -1,40 +0,0 @@
<template>
<q-btn flat no-caps dense icon="account_circle" :label="userInfo.fullName">
<q-menu class="bar-user-menu">
<div class="row no-wrap q-px-md q-pt-sm ">
<div class="column items-center justify-center">
<q-icon name="account_circle" color="grey" size="3em" />
</div>
<div class="column q-ml-sm overflow-hidden">
<div class="text-subtitle1 ellipsis full-width">{{ userInfo.fullName }}</div>
<div class="text-grey-7 ellipsis text-caption q-mb-sm full-width">{{ userInfo.email }}</div>
</div>
</div>
<div class="row q-pb-sm q-px-md">
<q-chip v-if="authStore.isSuperAdmin" square color="accent" text-color="white" icon="admin_panel_settings"
label="システム管理者" size="sm" />
<q-chip v-else v-for="(item) in roles" square class="role-label" color="primary" text-color="white" :key="item.id" :label="item.name" size="sm" />
</div>
<div class="row q-pb-sm q-px-md">
<q-btn outline color="negative" icon="logout" label="Logout" @click="authStore.logout()" class="full-width" size="sm" v-close-popup />
</div>
</q-menu>
</q-btn>
</template>
<script setup lang="ts">
import { useAuthStore } from 'stores/useAuthStore';
import { computed } from 'vue';
const authStore = useAuthStore();
const userInfo = computed(() => authStore.userInfo);
const roles = computed(() => authStore.roles);
</script>
<style lang="scss" >
.bar-user-menu {
max-width: 230px !important;
}
.role-label {
margin: 2px;
}
</style>

View File

@@ -23,7 +23,7 @@ defineExpose({
})
const getUsers = async (filter = () => true) => {
loading.value = true;
const result = await api.get('api/v1/users');
const result = await api.get(`api/v1/users`);
rows.value = result.data.data.map((item) => {
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
}).filter(filter);

View File

@@ -6,20 +6,13 @@
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
:filter="filter" style="max-height: 65vh;" @update:selected="emitSelected">
<template v-for="col in columns" :key="col.name" v-slot:[`body-cell-${col.name}`]="props">
<template v-slot:[`body-cell-${detailField}`]="props">
<q-td :props="props">
<!-- 使用动态插槽名称 -->
<slot v-if="col.name !== detailField" :name="`body-cell-${col.name}`" :row="props.row" :column="props.col">
<!-- 默认内容 -->
<span>{{ props.row[col.name] }}</span>
</slot>
<q-scroll-area v-else class="description-cell">
<q-scroll-area class="description-cell">
<div v-html="props.row[detailField]"></div>
</q-scroll-area>
</q-td>
</template>
</q-table>
</div>
</template>
@@ -48,14 +41,6 @@ export default {
fetchData: {
type: Function as PropType<() => Promise<IRow[]>>,
required: true
},
sortBy: {
type: String,
required: false
},
sortDesc: {
type: Boolean,
required: false
}
},
emits: ['update:selected'],
@@ -79,8 +64,6 @@ export default {
selected,
isLoaded,
pagination: ref({
sortBy: props.sortBy || undefined,
descending: props.sortDesc || undefined,
rowsPerPage: 10
}),
emitSelected

View File

@@ -1,71 +0,0 @@
<template>
<detail-field-table
detailField="description"
:name="name"
:type="type"
:filter="filter"
:columns="columns"
:fetchData="fetchUsers"
@update:selected="(item) => { selected = item }">
<template v-slot:body-cell-status="props">
<div class="row">
<div v-if="props.row.isActive">
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
</div>
<div v-else>
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
</div>
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
label="システム管理者" size="sm" />
</div>
</template>
</detail-field-table>
</template>
<script lang="ts">
import { ref, PropType } from 'vue';
import { IUser } from 'src/types/UserTypes';
import { api } from 'boot/axios';
import DetailFieldTable from './DetailFieldTable.vue';
export default {
name: 'UserSelectBox',
components: {
DetailFieldTable
},
props: {
name: String,
type: String,
filter: String,
filterInitRowsFunc: {
type: Function as PropType<(user: IUser) => boolean>,
}
},
setup(props) {
const selected = ref<IUser[]>([]);
const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
{ name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true },
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' }
];
const fetchUsers = async () => {
const result = await api.get('api/v1/users');
return result.data.data.map((item: any) => {
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active, roles: item.roles.map(role => role.id) }
}).filter(user => !props.filterInitRowsFunc || props.filterInitRowsFunc(user));
};
return {
columns,
fetchUsers,
selected
};
}
};
</script>

View File

@@ -3,32 +3,19 @@
detailField="comment"
type="single"
:columns="columns"
sortBy="id"
:sortDesc="true"
:fetchData="fetchVersionHistory"
@update:selected="(item) => { selected = item }"
>
<template v-slot:body-cell-id="p">
<div class="flex justify-between">
<span>{{ p.row.id }}</span>
<q-badge v-if="p.row.isActive" color="primary">現在</q-badge>
</div>
</template>
</detail-field-table>
/>
</template>
<script lang="ts">
import { defineComponent, PropType, ref } from 'vue';
import { IAppDisplay, IAppVersion, IAppVersionDisplay } from 'src/types/AppTypes';
import { defineComponent, PropType, ref, watch } from 'vue';
import { IAppDisplay, IAppVersion } from 'src/types/AppTypes';
import { date } from 'quasar';
import { api } from 'boot/axios';
import DetailFieldTable from './DetailFieldTable.vue';
import { IUser, IUserDisplay } from 'src/types/UserTypes';
interface IVersionDisplay extends IAppVersionDisplay {
isActive : boolean
}
export default defineComponent({
name: 'VersionHistory',
components: {
@@ -41,23 +28,23 @@ export default defineComponent({
},
},
setup(props, { emit }) {
const selected = ref<IVersionDisplay[]>();
const selected = ref<IAppVersion[]>([]);
const columns = [
{ name: 'id', label: 'バージョン', field: 'version', align: 'left', sortable: true },
{ name: 'version', label: 'ID', field: 'version', align: 'left', sortable: true },
{ name: 'name', label: 'バージョン名', field: 'name', align: 'left', sortable: true },
{ name: 'comment', label: 'コメント', field: 'comment', align: 'left', sortable: true },
// { name: 'creator', label: '作成者', field: (row: IVersionDisplay) => row.creator.fullName, align: 'left', sortable: true },
// { name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
// { name: 'updater', label: '更新者', field: (row: IVersionDisplay) => row.updater.fullName, align: 'left', sortable: true },
// { name: 'updateTime', label: '更新日時', field: 'updateTime', align: 'left', sortable: true },
{ name: 'creator', label: '作成者', field: (row: IAppVersion) => row.creator.fullName, align: 'left', sortable: true },
{ name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
{ name: 'updater', label: '更新者', field: (row: IAppVersion) => row.updater.fullName, align: 'left', sortable: true },
{ name: 'updateTime', label: '更新日時', field: 'updateTime', align: 'left', sortable: true },
];
const formatDate = (dateStr: string) => {
return date.formatDate(dateStr, 'YYYY/MM/DD HH:mm:ss');
};
const toUserDisplay = (user: IUser) => {
const toUserDisaplay = (user: IUser) => {
return {
id: user.id,
firstName: user.first_name,
@@ -71,29 +58,24 @@ export default defineComponent({
}
const fetchVersionHistory = async () => {
const { data } = await api.get(`api/appversions/${props.app.id}`);
return data.data.reduce((arr: IVersionDisplay[], item: any) => {
const val = {
id: item.version,
isActive: item.version === props.app.version,
version: item.version,
appid: item.appid,
name: item.versionname,
comment: item.comment,
// updater: toUserDisplay(item.updateuser),
// updateTime: formatDate(item.updatetime),
// creator: toUserDisplay(item.createuser),
// createTime: formatDate(item.createtime),
} as IVersionDisplay;
arr.push(val);
return arr;
}, []);
}
const { data } = await api.get(`api/apps/${props.app.id}/versions`);
return data.data.map((item: any) => ({
id: item.id,
version: item.version,
appid: item.appid,
name: item.name,
comment: item.comment,
updater: toUserDisaplay(item.updateuser),
updateTime: formatDate(item.updatetime),
creator: toUserDisaplay(item.createuser),
createTime: formatDate(item.createtime),
} as IAppVersion));
};
return {
fetchVersionHistory,
columns,
selected,
selected
};
},
});

View File

@@ -1,40 +1,26 @@
<template>
<q-input
ref="nameRef"
v-model="versionInfo.name"
filled
autofocus
label="バージョン名"
:rules="[
val => !!val || 'バージョン名を入力してください。',
(val) => !val || val.length <= 80 || '80字以内で入力ください'
]"
:rules="[(val) => !val || val.length <= 30 || '30字以内で入力ください']"
/>
<q-input
ref="commentRef"
v-model="versionInfo.comment"
v-model="versionInfo.desc"
filled
type="textarea"
:rules="[(val) => !val || val.length <= 300 || '300字以内で入力ください']"
:rules="[(val) => !val || val.length <= 80 || '80字以内で入力ください']"
label="説明"
/>
</template>
<script setup lang="ts">
import { ref, watch, defineProps, defineEmits } from 'vue';
import { QInput } from 'quasar';
import { IVersionSubmit } from 'src/types/AppTypes';
const nameRef = ref();
const commentRef = ref();
const isValid = () => {
const nameHasError = nameRef.value?.hasError ?? false;
const commentHasError = commentRef.value?.hasError ?? false;
return !nameHasError && !commentHasError;
};
import { IVersionInfo } from 'src/types/AppTypes';
const props = defineProps<{
modelValue: IVersionSubmit;
modelValue: IVersionInfo;
}>();
const defaultTitle = `${new Date().toLocaleString()}`;
@@ -42,15 +28,10 @@ const defaultTitle = `${new Date().toLocaleString()}`;
const versionInfo = ref({
...props.modelValue,
name: props.modelValue.name || defaultTitle,
comment: props.modelValue.comment || '',
});
const emit = defineEmits(['update:modelValue']);
defineExpose({
isValid
})
watch(
versionInfo,
() => {

View File

@@ -44,7 +44,7 @@ import { useAuthStore } from 'src/stores/useAuthStore';
export default defineComponent({
name: 'AppSelector',
emits:[
'appSelected'
"appSelected"
],
components:{
AppSelectBox,
@@ -59,7 +59,7 @@ export default defineComponent({
const closeDg=(val :any)=>{
showSelectApp.value=false;
console.log('Dialog closed->',val);
console.log("Dialog closed->",val);
if (val == 'OK') {
const data = appDg.value.selected[0];
console.log(data);

View File

@@ -74,7 +74,7 @@ export default defineComponent({
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
const isFieldChange = (node: IKintoneEventNode) => {
return node.header == 'EVENT' && node.eventId.indexOf('.change.') > -1;
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
}
const getSelectedClass = (node: IKintoneEventNode) => {
@@ -117,7 +117,7 @@ export default defineComponent({
$q.notify({
type: 'positive',
caption: '通知',
caption: "通知",
message: `イベント ${node.label} 削除`
})
}

View File

@@ -92,11 +92,11 @@ export default defineComponent({
},
emits: [
'addNode',
'nodeSelected',
'nodeEdit',
'deleteNode',
'deleteAllNextNodes',
'copyFlow'
"nodeSelected",
"nodeEdit",
"deleteNode",
"deleteAllNextNodes",
"copyFlow"
],
setup(props, context) {
const store = useFlowEditorStore();
@@ -204,7 +204,7 @@ export default defineComponent({
* 変数名取得
*/
const varName =(node:IActionNode)=>{
const prop = node.actionProps.find((prop) => prop.props.name === 'verName');
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props.modelValue.name;
};
const copyFlow=()=>{

View File

@@ -31,11 +31,11 @@
import { ref, defineComponent, computed, PropType } from 'vue';
import { IActionNode, ActionNode, ActionFlow, RootAction } from '../../types/ActionTypes';
export enum Direction {
Default = 'None',
Left = 'LEFT',
Right = 'RIGHT',
LeftNotNext = 'LEFTNOTNEXT',
RightNotNext = 'RIGHTNOTNEXT',
Default = "None",
Left = "LEFT",
Right = "RIGHT",
LeftNotNext = "LEFTNOTNEXT",
RightNotNext = "RIGHTNOTNEXT",
}
export default defineComponent({
name: 'NodeLine',

View File

@@ -55,7 +55,7 @@ export default defineComponent({
});
const connectProps=(props:IProp)=>{
const connProps:any={};
if(props && 'connectProps' in props && props.connectProps!=undefined){
if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){
let targetProp = componentData.value.find((prop)=>prop.props.name===connProp.propName);
if(targetProp){

View File

@@ -72,11 +72,11 @@ export default defineComponent({
}
},
setup(props, { emit }) {
const color = ref(props.modelValue??'');
const isSelected = computed(()=>props.modelValue && props.modelValue!=='');
const color = ref(props.modelValue??"");
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
const customExp = props.rules === undefined ? [] : eval(props.rules);
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),'anyColor']:[];
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),"anyColor"]:[];
const rulesExp=[...requiredExp,...customExp];
watchEffect(()=>{
emit('update:modelValue', color.value);

View File

@@ -91,7 +91,7 @@ export default defineComponent({
},
setup(props, { emit }) {
let source = reactive(props.connectProps['source']);
let source = reactive(props.connectProps["source"]);
if(!source){
source = props.context.find(element => element.props.name === 'sources');
}

View File

@@ -206,32 +206,32 @@ export default defineComponent({
//集計処理方法
const logicalOperators = ref([
{
'operator': '',
'label': 'なし'
"operator": "",
"label": "なし"
},
{
'operator': 'SUM',
'label': '合計'
"operator": "SUM",
"label": "合計"
},
{
'operator': 'AVG',
'label': '平均'
"operator": "AVG",
"label": "平均"
},
{
'operator': 'MAX',
'label': '最大値'
"operator": "MAX",
"label": "最大値"
},
{
'operator': 'MIN',
'label': '最小値'
"operator": "MIN",
"label": "最小値"
},
{
'operator': 'COUNT',
'label': 'カウント'
"operator": "COUNT",
"label": "カウント"
},
{
'operator': 'FIRST',
'label': '最初の値'
"operator": "FIRST",
"label": "最初の値"
}
]);
const checkInput=(val:ValueType)=>{
@@ -239,13 +239,13 @@ export default defineComponent({
return false;
}
if(!val.name){
return '集計結果の変数名を入力してください';
return "集計結果の変数名を入力してください";
}
if(!val.vars || val.vars.length==0){
return '集計処理を設定してください';
return "集計処理を設定してください";
}
if(val.vars.some((x)=>!x.vName)){
return '集計結果変数名を入力してください';
return "集計結果変数名を入力してください";
}
return true;
}

View File

@@ -70,14 +70,14 @@ export default defineComponent({
const eventId =store.currentFlow?.getRoot()?.name;
if(eventId===undefined){return;}
let displayName = inputValue.value;
if(props.connectProps!==undefined && 'displayName' in props.connectProps){
displayName =props.connectProps['displayName'].props.modelValue;
if(props.connectProps!==undefined && "displayName" in props.connectProps){
displayName =props.connectProps["displayName"].props.modelValue;
}
const customButtonId=`${eventId}.customButtonClick`;
const findedEvent = store.eventTree.findEventById(customButtonId);
if(findedEvent && 'events' in findedEvent){
if(findedEvent && "events" in findedEvent){
const customEvents = findedEvent as IKintoneEventGroup;
const addEventId = customButtonId+'.' + inputValue.value;
const addEventId = customButtonId+"." + inputValue.value;
if(store.eventTree.findEventById(addEventId)){
return;
}

View File

@@ -71,7 +71,7 @@ export default defineComponent({
const rulesExp=[...requiredExp,...customExp];
watchEffect(()=>{
emit('update:modelValue',numValue.value);
emit("update:modelValue",numValue.value);
});
return {
numValue,

View File

@@ -59,7 +59,7 @@ export default defineComponent({
const properties=ref(props.nodeProps);
const connectProps=(props:IProp)=>{
const connProps:any={context:properties};
if(props && 'connectProps' in props && props.connectProps!=undefined){
if(props && "connectProps" in props && props.connectProps!=undefined){
for(let connProp of props.connectProps){
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
if(targetProp){

View File

@@ -40,7 +40,8 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
},
props: {
actionNode:{
type:Object as PropType<IActionNode>
type:Object as PropType<IActionNode>,
required:true
},
drawerRight:{
type:Boolean,
@@ -54,7 +55,7 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
setup(props,{emit}) {
const showPanel =ref(props.drawerRight);
const cloneProps = (actionProps:IActionProperty[]|undefined):IActionProperty[]|null=>{
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
if(!actionProps){
return null;
}

View File

@@ -9,7 +9,7 @@ export class Auth
params.append('username', user);
params.append('password', pwd);
try{
const result = await api.post('api/token',params);
const result = await api.post(`api/token`,params);
console.info(result);
localStorage.setItem('Token', result.data.access_token);
return true;

View File

@@ -32,7 +32,7 @@ export class FlowCtrl {
* @returns
*/
async UpdateFlow(jsonData: any): Promise<boolean> {
const result = await api.put('api/flow', jsonData);
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
console.info(result.data);
return true;
}

View File

@@ -8,7 +8,7 @@
<q-badge align="top" outline>V{{ version }}</q-badge>
</q-toolbar-title>
<domain-selector></domain-selector>
<user-info-button />
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
</q-toolbar>
</q-header>
@@ -19,6 +19,10 @@
</q-item-label>
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />
<div v-if="isAdmin()">
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
</div>
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
</q-list>
</q-drawer>
@@ -30,13 +34,11 @@
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, getCurrentInstance } from 'vue';
import { computed, onMounted, reactive } from 'vue';
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
import DomainSelector from 'components/DomainSelector.vue';
import UserInfoButton from 'components/UserInfoButton.vue';
import { useAuthStore } from 'stores/useAuthStore';
import { useRoute } from 'vue-router';
import { MenuMapping } from 'src/boot/permissions';
const authStore = useAuthStore();
const route = useRoute()
@@ -49,8 +51,7 @@ const essentialLinks: EssentialLinkProps[] = reactive([
icon: 'home',
link: '/',
target: '_self',
disable: noDomain,
permission: MenuMapping.home
disable: noDomain
},
// {
// title: 'フローエディター',
@@ -60,13 +61,12 @@ const essentialLinks: EssentialLinkProps[] = reactive([
// target: '_self'
// },
{
title: 'アプリ',
caption: 'アプリのカスタマイズ',
title: 'アプリ管理',
caption: 'アプリを管理する',
icon: 'widgets',
link: '/#/app',
target: '_self',
disable: noDomain,
permission: MenuMapping.app
disable: noDomain
},
// {
// title: '条件エディター',
@@ -79,40 +79,6 @@ const essentialLinks: EssentialLinkProps[] = reactive([
title: '',
isSeparator: true
},
// ------------ユーザー-------------
{
title: 'ユーザー管理',
caption: 'ユーザーを管理する',
icon: 'manage_accounts',
link: '/#/user',
target: '_self',
permission: MenuMapping.user
},
{
title: 'ロールの割り当て',
caption: 'ロールを管理する',
icon: 'work',
link: '/#/role',
target: '_self',
permission: MenuMapping.role
},
// ------------ドメイン-------------
{
title: '接続先管理',
caption: 'kintoneの接続先設定',
icon: 'domain',
link: '/#/domain',
target: '_self',
permission: MenuMapping.domain
},
{
title: '接続先の割り当て',
caption: '利用可能な接続先の設定',
icon: 'assignment_ind',
link: '/#/userDomain',
target: '_self',
permission: MenuMapping.userDomain
},
// {
// title:'Kintone ポータル',
// caption:'Kintone',
@@ -133,6 +99,33 @@ const essentialLinks: EssentialLinkProps[] = reactive([
// },
]);
const domainLinks: EssentialLinkProps[] = reactive([
{
title: 'ドメイン管理',
caption: 'kintoneのドメイン設定',
icon: 'domain',
link: '/#/domain',
target: '_self'
},
{
title: 'ドメイン適用',
caption: 'ユーザー使用可能なドメインの設定',
icon: 'assignment_ind',
link: '/#/userDomain',
target: '_self'
},
]);
const adminLinks: EssentialLinkProps[] = reactive([
{
title: 'ユーザー管理',
caption: 'ユーザーを管理する',
icon: 'manage_accounts',
link: '/#/user',
target: '_self'
},
])
const version = process.env.version;
const productName = process.env.productName;
@@ -141,7 +134,10 @@ onMounted(() => {
});
function toggleLeftDrawer() {
getCurrentInstance();
authStore.toggleLeftMenu();
}
function isAdmin(){
const permission = authStore.permissions;
return permission === 'admin'
}
</script>

View File

@@ -2,10 +2,10 @@
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="widgets" label="アプリ" />
<q-breadcrumbs-el icon="widgets" label="アプリ管理" />
</q-breadcrumbs>
</div>
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<template v-slot:top>
<q-btn color="primary" :disable="loading" label="新規" @click="showAddAppDialog" />
@@ -28,24 +28,9 @@
</a>
</q-td>
</template>
<template v-slot:body-cell-version="p">
<q-td :props="p">
<div class="flex justify-between full-width" >
<span v-if="p.row.version == 0"></span>
<span v-else class="ellipsis" :title="p.row.versionName">{{ p.row.versionName }}</span>
<q-badge v-if="isVersionEditing(p.row)" color="orange-7">変更あり</q-badge>
</div>
</q-td>
</template>
<template v-slot:body-cell-updateUser="p">
<q-td auto-width :props="p">
<q-badge v-if="p.row.updateUser.id == Number(authStore.userId)" color="purple">自分</q-badge>
<span v-else>{{ p.row.updateUser.fullName }}</span>
</q-td>
</template>
<template v-slot:body-cell-actions="p">
<q-td auto-width :props="p">
<table-action-menu :row="p.row" minWidth="180px" max-width="200px" :actions="actionList" />
<q-td :props="p">
<table-action-menu :row="p.row" :actions="actionList" />
</q-td>
</template>
</q-table>
@@ -61,34 +46,27 @@
<app-select-box ref="appDialog" name="アプリ" type="single" :filter="dgFilter" :filterInitRowsFunc="filterInitRows" />
</show-dialog>
<q-dialog v-model="deleteDialog" persistent>
<q-card>
<q-card-section class="row items-center">
<q-icon name="warning" color="warning" size="2em" />
<span class="q-ml-sm">削除してもよろしいですか</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" :loading="deleteUserLoading" @click="deleteApp" />
</q-card-actions>
</q-card>
</q-dialog>
<show-dialog v-model:visible="showVersionHistory" :name="targetRow?.name + 'のバージョン履歴'" @close="closeHistoryDg" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
<version-history ref="versionDialog" :app="targetRow as IAppDisplay" />
</show-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { useAppStore } from 'stores/useAppStore';
import { ref, onMounted, watch, reactive } from 'vue';
import { useQuasar } from 'quasar'
import { api } from 'boot/axios';
import { useAuthStore } from 'stores/useAuthStore';
import { useFlowEditorStore } from 'stores/flowEditor';
import { router } from 'src/router';
import { IAppDisplay } from 'src/types/AppTypes';
import { date } from 'quasar'
import { IManagedApp, IAppDisplay, IAppVersion } from 'src/types/AppTypes';
import ShowDialog from 'src/components/ShowDialog.vue';
import AppSelectBox from 'src/components/AppSelectBox.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import VersionHistory from 'components/dialog/VersionHistory.vue';
const appStore = useAppStore();
const authStore = useAuthStore();
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
@@ -96,9 +74,9 @@ const columns = [
{ name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting },
{ name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true },
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
{ name: 'updateUser', label: '最後更新者', field: '', align: 'left', sortable: true},
{ name: 'updateUser', label: '最後更新者', field: (row: IAppDisplay) => row.updateUser.fullName, align: 'left', sortable: true},
{ name: 'updateTime', label: '最後更新日', field: 'updateTime', align: 'left', sortable: true},
{ name: 'version', label: 'バージョン', field: '', align: 'left', sortable: true, style: 'max-width: 200px;',sort: numberStringSorting },
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting },
{ name: 'actions', label: '', field: 'actions' }
];
@@ -106,39 +84,54 @@ const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const loading = ref(false);
const filter = ref('');
const dgFilter = ref('');
const rows = computed(() => appStore.apps);
const rows = ref<IAppDisplay[]>([]);
const targetRow = ref<IAppDisplay>();
const rowIds = new Set<string>();
const $q = useQuasar()
const store = useFlowEditorStore();
const appDialog = ref();
const showSelectApp=ref(false);
const showVersionHistory=ref(false);
const isAdding = ref(false);
const deleteDialog = ref(false);
const deleteUserLoading = ref(false);
const actionList = [
{ label: 'フローの編集', icon: 'account_tree', action: toEditFlowPage },
{ label: 'バージョンの管理', icon: 'history', action: toVersionHistoryPage },
{ label: '設定', icon: 'account_tree', action: toEditFlowPage },
{ label: '履歴', icon: 'history', action: showHistory },
{ separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const getApps = async () => {
loading.value = true;
await appStore.loadApps();
loading.value = false;
rowIds.clear();
try {
const { data } = await api.get('api/apps');
rows.value = data.data.map((item: IManagedApp) => {
rowIds.add(item.appid);
return appToAppDisplay(item)
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
} catch (error) {
$q.notify({
icon: 'error',
color: 'negative',
message: 'アプリ一覧の読み込みに失敗しました'
});
} finally {
loading.value = false;
}
}
function isVersionEditing(app: IAppDisplay) {
return !!app.versionChanged;
};
onMounted(async () => {
await getApps();
});
watch(() => authStore.currentDomain.id, async () => {
await getApps();
});
const filterInitRows = (row: {id: string}) => {
return !appStore.rowIds.has(row.id);
return !rowIds.has(row.id);
}
const showAddAppDialog = () => {
@@ -158,23 +151,45 @@ const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
function removeRow(app:IAppDisplay) {
targetRow.value = app;
deleteDialog.value = true;
return
}
const deleteApp = async () => {
if (targetRow.value?.id) {
deleteUserLoading.value = true;
await appStore.deleteApp(targetRow.value)
await getApps();
deleteUserLoading.value = false;
deleteDialog.value = false;
function showHistory(app:IAppDisplay) {
targetRow.value = app;
showVersionHistory.value = true;
dgFilter.value = ''
}
const closeHistoryDg = async (val: 'OK'|'Cancel') => {
showSelectApp.value = true;
if (val == 'OK' && appDialog.value.selected[0]) {
isAdding.value = true;
await getApps();
}
showSelectApp.value = false;
isAdding.value = false;
}
async function toVersionHistoryPage(app:IAppDisplay) {
await router.push('/app/version/' + app.id).catch(err => {
console.error(err);
});
const appToAppDisplay = (app: IManagedApp) => {
const user = app.updateuser;
return {
id: app.appid,
sortId: parseInt(app.appid, 10),
name: app.appname,
url: `${app.domainurl}/k/${app.appid}`,
version: app.version,
updateTime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'),
updateUser: {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
fullName: user.last_name + ' ' + user.first_name,
email: user.email,
isSuperuser: user.is_superuser,
isActive: user.is_active,
}
} as IAppDisplay
}
async function toEditFlowPage(app:IAppDisplay) {
@@ -183,8 +198,6 @@ async function toEditFlowPage(app:IAppDisplay) {
name: app.name
});
store.selectFlow(undefined);
await router.push('/FlowChart/' + app.id).catch(err => {
console.error(err);
});
await router.push('/FlowChart/' + app.id);
};
</script>

View File

@@ -1,207 +0,0 @@
<template>
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="widgets" label="アプリ" to="/app" />
<q-breadcrumbs-el>
<template v-slot>
<a class="full-width" :href="app?.url" target="_blank" title="Kiontoneへ">
{{ app?.name }}
<q-icon
class="q-ma-xs"
name="open_in_new"
color="grey-9"
/>
</a>
</template>
</q-breadcrumbs-el>
</q-breadcrumbs>
</div>
<q-table :rows="rows" title="バージョン履歴" :columns="columns" :loading="versionLoading" :pagination="pagination" :filter="filter" >
<template v-slot:top-right>
<q-input borderless dense filled clearable debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
<q-icon name="search"/>
</template>
</q-input>
</template>
<template v-slot:body-cell-id="p">
<q-td :props="p">
<div class="">
<span>{{ p.row.id }}</span>
<span class="q-ml-md" v-if="p.row.id === app.version">
<q-badge color="primary">適用中</q-badge>
<q-badge class="q-ml-xs" v-if="isVersionEditing()" color="orange-7">変更あり</q-badge>
</span>
</div>
</q-td>
</template>
<template v-slot:body-cell-comment="p">
<q-td :props="p">
<q-scroll-area class="multiline-cell">
<div v-html="p.row['comment']"></div>
</q-scroll-area>
</q-td>
</template>
<template v-slot:body-cell-creator="p">
<q-td auto-width :props="p">
<q-badge v-if="p.row.creator.id == Number(authStore.userId)" color="purple">自分</q-badge>
<span v-else>{{ p.row.creator.fullName }}</span>
</q-td>
</template>
<template v-slot:body-cell-actions="p">
<q-td :props="p">
<table-action-menu :row="p.row" minWidth="140px" :actions="actionList" />
</q-td>
</template>
</q-table>
<q-dialog v-model="confirmDialog" persistent>
<q-card>
<q-card-section class="q-pb-none">
<q-list>
<q-item class="q-px-none">
<q-item-section avatar class="items-center">
<q-icon name="warning" color="warning" size="2em" />
</q-item-section>
<q-item-section>
<div>現在のバージョンは未保存です</div>
<div>プルすると上書されますのでよろしいでしょうか</div>
</q-item-section>
</q-item>
</q-list>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="キャンセル" color="primary" v-close-popup />
<q-btn flat label="上書きする" color="primary" :loading="deleteUserLoading" @click="doChangeVersion()" />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { useQuasar } from 'quasar';
import { ref, onMounted } from 'vue';
import { useAppStore } from 'stores/useAppStore';
import { useAuthStore } from 'stores/useAuthStore';
import { router } from 'src/router';
import { useRoute } from 'vue-router';
import { IAppDisplay, IAppVersionDisplay } from 'src/types/AppTypes';
import TableActionMenu from 'components/TableActionMenu.vue';
const authStore = useAuthStore();
const appStore = useAppStore();
const route = useRoute()
const $q = useQuasar();
const app = ref<IAppDisplay>({} as IAppDisplay);
const rows = ref<IAppVersionDisplay[]>([]);
const columns = [
{ name: 'id', label: 'バージョン番号', field: 'version', align: 'left', sortable: true },
{ name: 'name', label: 'バージョン名', field: 'name', align: 'left', sortable: true },
{ name: 'comment', label: 'コメント', field: 'comment', align: 'left', sortable: true },
{ name: 'creator', label: '作成者', field: '', align: 'left', sortable: true },
{ name: 'createTime', label: '作成日時', field: 'createTime', align: 'left', sortable: true },
// { name: 'updater', label: '更新者', field: (row: IVersionDisplay) => row.updater.fullName, align: 'left', sortable: true },
// { name: 'updateTime', label: '更新日時', field: 'updateTime', align: 'left', sortable: true },
{ name: 'actions', label: '', field: 'actions' }
];
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const filter = ref('');
const versionLoading = ref(false);
const target = ref<IAppVersionDisplay>();
const confirmDialog = ref(false);
const deleteUserLoading = ref(false);
const actionList = ref([
{ label: '回復する', icon: 'flag', action: changeVersion },
// { label: 'プレビュー', icon: 'visibility', action: toVersionHistoryPage },
// { separator: true },
// { label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
]);
const getApps = async () => {
await appStore.loadApps();
}
const getAppById = () => {
let res = appStore.getAppById(route.params.id as string);
if (res != null) {
app.value = res;
return true;
}
return false;
}
const getVersions = async () => {
versionLoading.value = true;
rows.value = await appStore.getVersionsByAppId(app.value);
versionLoading.value = false;
}
function isVersionEditing() {
return !!app.value.versionChanged;
};
onMounted(async () => {
versionLoading.value = true;
let isSuccess = getAppById();
if (!isSuccess) {
await getApps();
isSuccess = getAppById();
if (!isSuccess) {
$q.notify({
icon: 'error',
color: 'negative',
message: 'バージョン一覧の読み込みに失敗しました'
})
await router.push('/app');
}
}
await getVersions();
});
async function changeVersion(version: IAppVersionDisplay) {
target.value = version;
if (!isVersionEditing()) {
await doChangeVersion(version);
} else {
confirmDialog.value = true;
}
}
async function doChangeVersion(version?: IAppVersionDisplay) {
if (!version) {
version = target.value as IAppVersionDisplay;
}
confirmDialog.value = false;
versionLoading.value = true;
await appStore.changeVersion(app.value, version);
await getApps();
getAppById();
versionLoading.value = false;
}
</script>
<style lang="scss">
.multiline-cell {
height: 45px;
min-width: 300px;
max-height: 45px;
white-space: break-spaces;
.q-scrollarea__content {
display: flex;
align-items: center;
}
}
</style>

View File

@@ -19,7 +19,7 @@
<q-icon name="bookmark_border"></q-icon>
</q-item-section>
<q-item-section>
<q-item-label>保存して新バージョン</q-item-label>
<q-item-label>新バージョン保存</q-item-label>
</q-item-section>
</q-item>
@@ -51,10 +51,10 @@
@click="drawerLeft=!drawerLeft" class="expand" />
<q-breadcrumbs v-if="store.appInfo" class="fixed q-pl-md"
:style="{'left': fixedLeftPosition}">
<q-breadcrumbs-el icon="widgets" label="アプリ" to="/app" />
<q-breadcrumbs-el icon="widgets" label="アプリ管理" to="/app" />
<q-breadcrumbs-el>
<template v-slot>
<a class="full-width" :href="store.appInfo ? `${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}` : ''" target="_blank" title="Kiontoneへ">
<a class="full-width" :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
{{ store.appInfo?.name }}
<q-icon
class="q-ma-xs"
@@ -85,8 +85,8 @@
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
</ShowDialog>
<!-- save version dialog -->
<ShowDialog v-model:visible="saveVersionAction" name="保存して新バージョン" @close="closeSaveVersionDg" :ok-btn-auto-close="false" min-width="500px">
<version-input ref="versionInputRef" v-model="versionSubmit" />
<ShowDialog v-model:visible="saveVersionAction" name="新バージョン保存" @close="closeSaveVersionDg" min-width="500px">
<version-input v-model="versionInfo" />
</ShowDialog>
<q-inner-loading
:showing="initLoading"
@@ -99,12 +99,11 @@
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty, AppInfo } from 'src/types/ActionTypes';
import { IAppDisplay, IManagedApp, IVersionSubmit } from 'src/types/AppTypes';
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
import { IAppDisplay, IManagedApp, IVersionInfo } from 'src/types/AppTypes';
import { storeToRefs } from 'pinia';
import { useFlowEditorStore } from 'stores/flowEditor';
import { useAuthStore } from 'stores/useAuthStore';
import { useAppStore } from 'stores/useAppStore';
import { api } from 'boot/axios';
import NodeItem from 'src/components/main/NodeItem.vue';
@@ -120,25 +119,23 @@ const deployLoading = ref(false);
const saveLoading = ref(false);
const initLoading = ref(true);
const drawerLeft = ref(false);
const versionSubmit = ref<IVersionSubmit>({} as IVersionSubmit);
const versionInfo = ref<IVersionInfo>();
const $q = useQuasar();
const store = useFlowEditorStore();
const authStore = useAuthStore();
const appStore = useAppStore();
const route = useRoute()
const appDg = ref();
const prevNodeIfo = ref({
prevNode: {} as IActionNode,
inputPoint: ''
inputPoint: ""
});
// const refFlow = ref<ActionFlow|null>(null);
const showAddAction = ref(false);
const saveVersionAction = ref(false);
const versionInputRef = ref();
const drawerRight = ref(false);
const filter=ref('');
const model = ref('');
const filter=ref("");
const model = ref("");
const rootNode = computed(()=>{
return store.currentFlow?.getRoot();
@@ -148,11 +145,11 @@ const minPanelWidth=computed(()=>{
if(store.currentFlow && root){
return store.currentFlow?.getColumns(root) * 300 + 'px';
}else{
return '300px';
return "300px";
}
});
const fixedLeftPosition = computed(()=>{
return drawerLeft.value?'300px':'0px';
return drawerLeft.value?"300px":"0px";
});
const addNode = (node: IActionNode, inputPoint: string) => {
@@ -195,12 +192,12 @@ const onDeleteAllNextNodes = (node: IActionNode) => {
store.currentFlow?.removeAllNext(node.id);
}
const closeDg = (val: any) => {
console.log('Dialog closed->', val);
console.log("Dialog closed->", val);
if (val == 'OK' && appDg?.value?.selected?.length > 0) {
const data = appDg.value.selected[0];
const actionProps = JSON.parse(data.property);
const outputPoint = JSON.parse(data.outputPoints);
const action = new ActionNode(data.name, data.desc, '', outputPoint, actionProps);
const action = new ActionNode(data.name, data.desc, "", outputPoint, actionProps);
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode, prevNodeIfo.value.inputPoint);
}
}
@@ -227,8 +224,8 @@ const onDeploy = async () => {
if (store.appInfo === undefined || store.flows?.length === 0) {
$q.notify({
type: 'negative',
caption: 'エラー',
message: '設定されたフローがありません。'
caption: "エラー",
message: `設定されたフローがありません。`
});
return;
}
@@ -238,16 +235,16 @@ const onDeploy = async () => {
deployLoading.value = false;
$q.notify({
type: 'positive',
caption: '通知',
message: 'デプロイを成功しました。'
caption: "通知",
message: `デプロイを成功しました。`
});
} catch (error) {
console.error(error);
deployLoading.value = false;
$q.notify({
type: 'negative',
caption: 'エラー',
message: 'デプロイが失敗しました。'
caption: "エラー",
message: `デプロイが失敗しました。`
})
}
return;
@@ -258,30 +255,23 @@ const onSaveActionProps=(props:IActionProperty[])=>{
store.activeNode.actionProps=props;
$q.notify({
type: 'positive',
caption: '通知',
caption: "通知",
message: `${store.activeNode?.subTitle}の属性を設定しました。(保存はされていません)`
});
}
};
const onSaveVersion = async () => {
if (!store.appInfo) return;
versionSubmit.value = { appId: store.appInfo.appId }
versionInfo.value = {
id: '1' // TODO
}
saveVersionAction.value = true;
// await onSaveAllFlow();
}
const closeSaveVersionDg = async (val: 'OK'|'CANCEL') => {
saveVersionAction.value = true;
const closeSaveVersionDg = (val: 'OK'|'CANCEL') => {
if (val == 'OK') {
if (versionInputRef?.value?.isValid()) {
saveVersionAction.value = false;
await onSaveAllFlow();
await appStore.createVersion(versionSubmit.value);
} else {
saveVersionAction.value = true;
}
} else {
saveVersionAction.value = false;
console.log(versionInfo.value);
}
}
@@ -291,7 +281,7 @@ const onSaveFlow = async () => {
$q.notify({
type: 'negative',
caption: 'エラー',
message: '選択中のフローがありません。'
message: `選択中のフローがありません。`
});
return;
}
@@ -301,7 +291,7 @@ const onSaveFlow = async () => {
saveLoading.value = false;
$q.notify({
type: 'positive',
caption: '通知',
caption: "通知",
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
});
} catch (error) {
@@ -309,7 +299,7 @@ const onSaveFlow = async () => {
saveLoading.value = false;
$q.notify({
type: 'negative',
caption: 'エラー',
caption: "エラー",
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
})
}
@@ -324,7 +314,7 @@ const onSaveAllFlow= async ()=>{
$q.notify({
type: 'negative',
caption: 'エラー',
message: '設定されたフローがありません。'
message: `設定されたフローがありません。`
});
return;
}
@@ -338,8 +328,8 @@ const onSaveAllFlow= async ()=>{
}
$q.notify({
type: 'positive',
caption: '通知',
message: 'すべてのフロー設定を保存しました。'
caption: "通知",
message: `すべてのフロー設定を保存しました。`
});
saveLoading.value = false;
}catch (error) {
@@ -347,8 +337,8 @@ const onSaveAllFlow= async ()=>{
saveLoading.value = false;
$q.notify({
type: 'negative',
caption: 'エラー',
message: 'フローの設定の保存が失敗しました。'
caption: "エラー",
message: `フローの設定の保存が失敗しました。`
});
}
}
@@ -403,9 +393,9 @@ const onClearFilter=()=>{
filter.value='';
}
onMounted(async () => {
onMounted(() => {
authStore.setLeftMenu(false);
await fetchData();
fetchData();
});
</script>

View File

@@ -27,23 +27,23 @@ import ActionSelect from 'components/ActionSelect.vue';
import PropertyPanel from 'components/right/PropertyPanel.vue';
const rootNode:RootAction =new RootAction('app.record.create.submit','レコード追加画面','保存するとき');
const rootNode:RootAction =new RootAction("app.record.create.submit","レコード追加画面","保存するとき");
const actionFlow: ActionFlow = new ActionFlow(rootNode);
const saibanProps:IActionProperty[]=[{
component:'InputText',
component:"InputText",
props:{
displayName:'フォーマット',
modelValue:'',
name:'format',
placeholder:'フォーマットを入力してください',
displayName:"フォーマット",
modelValue:"",
name:"format",
placeholder:"フォーマットを入力してください",
}
},{
component:'FieldInput',
component:"FieldInput",
props:{
displayName:'採番項目',
modelValue:'',
name:'field',
placeholder:'採番項目を選択してください',
displayName:"採番項目",
modelValue:"",
name:"field",
placeholder:"採番項目を選択してください",
}
}];
@@ -91,7 +91,7 @@ const onDeleteAllNextNodes=(node:IActionNode)=>{
refFlow.value.removeAllNext(node.id);
}
const closeDg=(val :any)=>{
console.log('Dialog closed->',val);
console.log("Dialog closed->",val);
}
</script>

View File

@@ -1,11 +1,12 @@
<template>
<q-page>
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="home" label="ホーム" />
</q-breadcrumbs>
</div>
</div>
<div class="q-gutter-sm row items-start">
<doc-uploader @uploaded="onAppUploaded"></doc-uploader>
</div>
@@ -15,26 +16,28 @@
</template>
<script setup lang="ts">
import { ref } from 'vue';
import {ref} from 'vue'
import DocUploader from 'components/DocUpload.vue';
import AppInfo from 'components/AppInfo.vue';
import { AppSeed } from 'src/components/models';
interface AppInfo {
app: string;
revision: string;
app:string,
revision:string
}
const appseed = withDefaults(defineProps<AppSeed>(), {
app: '',
const appseed = withDefaults( defineProps<AppSeed>(),{
app:''
});
// const appseed = defineProps<AppSeed>();
const props = ref(appseed);
const props = ref(appseed);
function onAppUploaded(responseText: string) {
let json: AppInfo = JSON.parse(responseText);
props.value = json;
function onAppUploaded(responseText :string){
let json:AppInfo = JSON.parse(responseText);
props.value=json;
}
</script>

View File

@@ -48,20 +48,20 @@
</q-layout>>
</template>
<script setup lang="ts">
import { useQuasar, QForm } from 'quasar'
import { useQuasar } from 'quasar'
// import { useRouter } from 'vue-router';
import { ref } from 'vue';
// import { Auth } from '../control/auth'
import { useAuthStore } from 'stores/useAuthStore';
const authStore = useAuthStore();
const $q = useQuasar()
const loginForm = ref<QForm>();
const loginForm = ref(null);
const loading = ref(false);
let title = ref('ログイン');
let email = ref('');
let password = ref('');
let visibility = ref(false);
let passwordFieldType = ref<'text'|'password'>('password');
let passwordFieldType = ref('password');
let visibilityIcon = ref('visibility');
const required = (val:string) => {
return (val && val.length > 0 || '必須項目')
@@ -78,41 +78,35 @@ import { useAuthStore } from 'stores/useAuthStore';
passwordFieldType.value = visibility.value ? 'text' : 'password'
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
}
const submit = () => {
if (!loginForm.value) {
return;
}
loginForm.value.validate().then(async (success) => {
if (success) {
loading.value=true;
try {
const result = await authStore.login(email.value,password.value);
loading.value=false;
if(result){
$q.notify({
icon: 'done',
color: 'positive',
message: 'ログイン成功'
});
}
else{
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
} catch (error) {
console.error(error);
loading.value=false;
const submit = async () =>{
loading.value=true;
try {
const result = await authStore.login(email.value,password.value);
loading.value=false;
if(result){
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
icon: 'done',
color: 'positive',
message: 'ログイン成功'
});
}
}
})
else{
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
}catch (error) {
console.error(error);
loading.value=false;
$q.notify({
icon: 'error',
color: 'negative',
message: 'ログイン失敗'
});
}
}
</script>

View File

@@ -1,250 +0,0 @@
<template>
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="work" label="ロールの割り当て" />
</q-breadcrumbs>
</div>
<div class="row" style="min-height: 80vh;">
<!-- left role panel -->
<div class="col-auto">
<div class="q-pa-md" style="width: 250px">
<q-list bordered separator class="rounded-borders">
<q-item active v-if="allLoading" active-class="menu-active">
<q-item-section class="text-weight-bold"> 読み込み中... </q-item-section>
</q-item>
<q-item v-else v-for="item in roles" :key="item.id" clickable v-ripple :active="selected?.id === item.id" @click="roleClicked(item)" active-class="menu-active">
<q-item-section class="text-weight-bold"> {{ item.name }} </q-item-section>
</q-item>
</q-list>
</div>
</div>
<!-- right table panel -->
<div class="col">
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="allLoading || loading"
:pagination="pagination" >
<template v-slot:top>
<q-btn color="primary" :disable="allLoading || loading || selected?.id == EMPTY_ROLE.id" label="追加" @click="showAddRoleDialog" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
<q-icon name="search" />
</template>
</q-input>
</template>
<template v-slot:body-cell-status="props">
<q-td :props="props">
<div class="row">
<div v-if="props.row.isActive">
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
</div>
<div v-else>
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
</div>
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
label="システム管理者" size="sm" />
</div>
</q-td>
</template>
<template v-slot:header-cell-status="p">
<q-th :props="p">
<div class="row items-center">
<label class="q-mr-md">{{ p.col.label }}</label>
<q-select v-model="statusFilter" :options="statusFilterOptions" borderless
dense options-dense style="font-size: 12px; padding-top: 1px;" />
</div>
</q-th>
</template>
<template v-if="selected && selected.id > 0" v-slot:body-cell-actions="p">
<q-td auto-width :props="p">
<table-action-menu :row="p.row" minWidth="180px" max-width="200px" :actions="actionList" />
</q-td>
</template>
</q-table>
</div>
</div>
<show-dialog v-model:visible="showSelectUser" name="ユーザー選択" @close="closeSelectUserDialog" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
<template v-slot:toolbar>
<q-input dense debounce="300" v-model="dgFilter" placeholder="検索" clearable>
<template v-slot:before>
<q-icon name="search" />
</template>
</q-input>
</template>
<user-select-box ref="userDialog" name="ユーザー" type="single" :filter="dgFilter" :filterInitRowsFunc="filterInitRows" />
</show-dialog>
<q-dialog v-model="deleteDialog" persistent>
<q-card>
<q-card-section class="row items-center">
<q-icon name="warning" color="warning" size="2em" />
<div class="q-ml-sm text-weight-bold">ロールメンバーを削除</div>
</q-card-section>
<q-card-section class="q-py-none">
<!-- <span class="q-ml-sm">この役割を与えられたユーザーはメンバー役に再配置されます</span> -->
<div class="q-mx-sm">ユーザー{{targetRow?.email}}{{selected?.name}}の役割から</div>
<div class="q-mx-sm">本当に外しますか</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn flat label="OK" color="primary" :loading="deleteUserRoleLoading" @click="deleteUserRole" />
</q-card-actions>
</q-card>
</q-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed, watch } from 'vue';
import { IRolesDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
import { useUserStore } from 'stores/useUserStore';
import ShowDialog from 'src/components/ShowDialog.vue';
import UserSelectBox from 'src/components/dialog/UserSelectBox.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
const userStore = useUserStore();
const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
{ name: 'lastName', label: '氏名', field: 'lastName', align: 'left', sortable: true },
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' },
{ name: 'actions', label: '', field: 'actions', align: 'right' }
];
const statusFilterOptions = [
{ label: '全データ', filter: () => true },
{ label: 'システム管理者のみ', filter: (row: IUserRolesDisplay) => row.isSuperuser },
{ label: '使用可能', filter: (row: IUserRolesDisplay) => row.isActive },
{ label: '使用不可', filter: (row: IUserRolesDisplay) => !row.isActive },
]
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const roles = computed(() => {
if (userStore.roles.length > 0) {
return userStore.roles.concat(EMPTY_ROLE);
}
return userStore.roles;
});
const selected = ref<IRolesDisplay>();
const allLoading = ref(true);
const loading = ref(false);
const filter = ref('');
const statusFilter = ref(statusFilterOptions[0]);
const dgFilter = ref('');
const allUsers = computed(() => userStore.users.filter(statusFilter.value.filter));
const targetRow = ref<IUserRolesDisplay>();
const userDialog = ref();
const showSelectUser=ref(false);
const isAdding = ref(false);
const deleteDialog = ref(false);
const deleteUserRoleLoading = ref(false);
const EMPTY_ROLE: IRolesDisplay = {
id: -2,
name: 'ロールなし',
key: 'dummy',
level: -1
}
const actionList = [
// { label: '移動', icon: 'account_tree', action: toEditFlowPage },
// { separator: true },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const rows = computed(() => allUsers.value.filter((item) => {
if (!selected.value) {
return false;
}
if (selected.value.id == -2) {
return !item.roles || item.roles.length == 0;
}
return item.roleIds?.includes(selected.value.id);
}));
const rowIds = computed(() => new Set(rows.value?.map((item) => item.id)));
const getUsers = async () => {
loading.value = true;
await userStore.loadUsers();
loading.value = false;
}
onMounted(async () => {
allLoading.value = true;
await Promise.all([
userStore.loadRoles(),
getUsers()
]);
allLoading.value = false;
});
watch(roles, (newRoles) => {
if (newRoles.length > 0) {
selected.value = newRoles[0];
}
});
const roleClicked = async (role: IRolesDisplay) => {
selected.value = role;
}
const filterInitRows = (user: IUserRolesDisplay) => {
return !rowIds.value.has(user.id);
}
const showAddRoleDialog = () => {
showSelectUser.value = true;
isAdding.value = false;
dgFilter.value = ''
}
const closeSelectUserDialog = async (val: 'OK'|'Cancel') => {
showSelectUser.value = true;
if (val == 'OK' && userDialog.value.selected[0] && selected.value && selected.value.id >= 0) {
isAdding.value = true;
await userStore.addRole(userDialog.value.selected[0], selected.value);
await getUsers();
}
showSelectUser.value = false;
isAdding.value = false;
}
function removeRow(user: IUserRolesDisplay) {
targetRow.value = user;
deleteDialog.value = true;
}
const deleteUserRole = async () => {
if (targetRow.value?.id && selected.value && selected.value.id >= 0) {
deleteUserRoleLoading.value = true;
await userStore.removeRole(targetRow.value, selected.value);
await getUsers();
deleteUserRoleLoading.value = false;
deleteDialog.value = false;
}
}
</script>
<style lang="scss" scoped>
.menu-active {
color: white;
background: var(--q-primary)
}
</style>

View File

@@ -66,8 +66,8 @@ interface Props {
actions: string[];
}
const props = withDefaults(defineProps<Props>(), {
title: 'ルールエディター',
actions: () => ['フィールド制御', '一覧画面', 'その他']
title: "ルールエディター",
actions: () => ["フィールド制御", "一覧画面", "その他"]
});
function onItemClick(evt: Event) {
return;

View File

@@ -2,13 +2,13 @@
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="domain" label="接続先管理" />
<q-breadcrumbs-el icon="domain" label="ドメイン管理" />
</q-breadcrumbs>
</div>
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
<template v-slot:top>
<q-btn v-permissions="Actions.domain.add" color="primary" :disable="loading" label="新規" @click="addRow" />
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
@@ -31,13 +31,6 @@
</q-td>
</template>
<template v-slot:body-cell-owner="p">
<q-td auto-width :props="p">
<q-badge v-if="isOwner(p.row)" color="purple">自分</q-badge>
<span v-else>{{ p.row.owner.fullName }}</span>
</q-td>
</template>
<template v-slot:body-cell-actions="p">
<q-td :props="p">
<table-action-menu :row="p.row" :actions="actionList" />
@@ -119,34 +112,28 @@
<q-dialog v-model="confirm" persistent>
<q-card>
<!-- -1 loading -->
<q-card-section v-if="deleteLoadingState == -1" class="row items-center">
<q-spinner color="primary" size="2em"/>
<span class="q-ml-sm">ドメイン利用権限を確認中</span>
</q-card-section>
<!-- > 0 can't delete -->
<q-card-section v-else-if="deleteLoadingState > 0" class="row items-center">
<q-icon name="error" color="negative" size="2em" />
<span class="q-ml-sm">ドメインは使用中です。削除してもよろしいですか?</span>
</q-card-section>
<!-- 0/-2 can delete -->
<q-card-section v-else class="row items-center">
<q-card-section v-else-if="deleteLoadingState == 0" class="row items-center">
<q-icon name="warning" color="warning" size="2em" />
<span class="q-ml-sm">削除してもよろしいですか</span>
</q-card-section>
<q-card-section v-else class="row items-center">
<q-icon name="error" color="negative" size="2em" />
<span class="q-ml-sm">ドメイン利用権限が存在しキャンセルする必要がある</span>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="キャンセル" color="primary" v-close-popup />
<!-- > 0 can't delete -->
<q-btn v-if="deleteLoadingState > 0" label="実行" color="primary" v-close-popup @click="openShareDg(SHARE_USE, editId)" />
<!-- 0/-2 can delete -->
<q-btn flat v-else label="OK" :disabled="deleteLoadingState == -1" :loading="deleteLoadingState == -2" color="primary" @click="deleteDomain()" />
<q-btn flat label="Cancel" color="primary" v-close-popup />
<q-btn v-if="deleteLoadingState > 0" label="処理に行く" color="primary" v-close-popup @click="openShareDg(editId)" />
<q-btn flat v-else label="OK" :disabled="deleteLoadingState" color="primary" v-close-popup @click="deleteDomain()" />
</q-card-actions>
</q-card>
</q-dialog>
<share-usage-dialog v-model="shareDg" :domain="shareDomain" @close="shareDg = false" />
<share-manage-dialog v-model="shareManageDg" :domain="shareDomain" @close="shareManageDg = false" />
<share-domain-dialog v-model="shareDg" :domain="shareDomain" @close="closeShareDg()" />
</div>
</template>
@@ -154,14 +141,14 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { api } from 'boot/axios';
import { Actions } from 'boot/permissions';
import { useAuthStore } from 'stores/useAuthStore';
import ShareUsageDialog from 'components/ShareDomain/ShareUsageDialog.vue';
import ShareManageDialog from 'components/ShareDomain/ShareManageDialog.vue';
import { useDomainStore } from 'stores/useDomainStore';
import ShareDomainDialog from 'components/ShareDomain/ShareDomainDialog.vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import { IDomain, IDomainDisplay, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes';
const authStore = useAuthStore();
const domainStore = useDomainStore();
const inactiveRowClass = (row: IDomainOwnerDisplay) => row.domainActive ? '' : 'inactive-row';
const columns = [
@@ -176,17 +163,17 @@ const columns = [
// classes: inactiveRowClass
// },
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
{ name: 'active', label: '', align: 'left', field: 'domainActive', classes: inactiveRowClass },
{ name: 'active', label: 'x', align: 'left', field: 'domainActive', classes: inactiveRowClass },
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true, classes: inactiveRowClass },
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', classes: inactiveRowClass },
{ name: 'owner', label: '所有者', field: '', align: 'left', classes: inactiveRowClass },
{ name: 'owner', label: '所有者', field: (row: IDomainOwnerDisplay) => row.owner.fullName, align: 'left', classes: inactiveRowClass },
{ name: 'actions', label: '', field: 'actions', classes: inactiveRowClass }
];
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const loading = ref(false);
const addEditLoading = ref(false);
const deleteLoadingState = ref<number>(-1); // -2: deleteLoading, -1: loading, 0: allow, > 0: user count
const deleteLoadingState = ref<number>(-1); // -1: loading, 0: allow, > 0: user count
const filter = ref('');
const rows = ref<IDomainOwnerDisplay[]>([]);
@@ -206,11 +193,10 @@ const domainActive = ref(true);
const isCreate = ref(true);
let editId = ref(0);
const shareDg = ref(false);
const shareManageDg = ref(false);
const shareDomain = ref<IDomainOwnerDisplay>({} as IDomainOwnerDisplay);
const activeOptions = [
{ value: 0, label: 'すべて' },
{ value: 0, label: '全状態' },
{ value: 1, label: '使用' },
{ value: 2, label: '未使用'}
]
@@ -230,27 +216,16 @@ const activeFilterUpdate = (option: {value: number}) => {
}
}
const SHARE_USE = 'use';
const SHARE_MANAGE = 'manage';
const actionList = [
{ label: '編集', icon: 'edit_note', permission: Actions.domain.edit, action: editRow },
{ label: '利用権限設定', icon: 'person_add_alt', permission: Actions.domain.grantUse,
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_USE, row)} },
{ label: '管理権限設定', icon: 'add_moderator', permission: Actions.domain.grantManage,
disable: (row: IDomainOwnerDisplay) => !isOwner(row),
tooltip: (row: IDomainOwnerDisplay) => isOwner(row) ? '' : 'ドメイン所有者でないため、操作できません',
action: (row: IDomainOwnerDisplay) => {openShareDg(SHARE_MANAGE, row)}
},
{ label: '編集', icon: 'edit_note', action: editRow },
{ label: '利用権限設定', icon: 'person_add_alt', action: openShareDg },
{ separator: true },
{ label: '削除', icon: 'delete_outline', permission: Actions.domain.delete, class: 'text-red', action: removeRow },
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
];
const isOwner = (row: IDomainOwnerDisplay) => row.owner.id === Number(authStore.userId);
const getDomain = async (filter?: (row: IDomainOwnerDisplay) => boolean) => {
loading.value = true;
const { data } = await api.get<{data:IDomain[]}>('api/domains');
const { data } = await api.get<{data:IDomain[]}>(`api/domains`);
rows.value = data.data.map((item) => {
return {
id: item.id,
@@ -296,20 +271,18 @@ async function removeRow(row: IDomainOwnerDisplay) {
}
const deleteDomain = () => {
deleteLoadingState.value = -2;
api.delete(`api/domain/${editId.value}`).then(({ data }) => {
if (!data.data) {
// TODO dialog
}
confirm.value = false;
deleteLoadingState.value = -1;
getDomain();
// authStore.setCurrentDomain();
})
editId.value = 0; // set in removeRow()
deleteLoadingState.value = -1;
};
function editRow(row: any) {
function editRow(row) {
isCreate.value = false
editId.value = row.id;
// tenantid.value = row.tenantid;
@@ -350,30 +323,31 @@ const onSubmit = () => {
'ownerid': authStore.userId || ''
}
// for search: api.put(`api/domain`)、api.post(`api/domain`)
api[method].apply(api, ['api/domain', param]).then(async (resp: any) => {
api[method].apply(api, [`api/domain`, param]).then(async (resp: any) => {
const res = resp.data;
if (res.data.id === currentDomainId.value && !res.data.is_active) {
await authStore.setCurrentDomain();
}
getDomain();
domainStore.loadUserDomains();
closeDg();
onReset();
addEditLoading.value = false;
})
}
function openShareDg(type: typeof SHARE_MANAGE|typeof SHARE_USE, row: IDomainOwnerDisplay|number) {
function openShareDg(row: IDomainOwnerDisplay|number) {
if (typeof row === 'number') {
row = rows.value.find(item => item.id === row) as IDomainOwnerDisplay;
}
shareDomain.value = row;
if (type === SHARE_USE) {
shareDg.value = true;
} else if (type === SHARE_MANAGE) {
shareManageDg.value = true;
}
shareDomain.value = row ;
shareDg.value = true;
};
function closeShareDg() {
shareDg.value = false;
}
const onReset = () => {
name.value = '';
url.value = '';

View File

@@ -3,7 +3,7 @@
<div class="q-pa-md">
<div class="q-gutter-sm row items-start">
<q-breadcrumbs>
<q-breadcrumbs-el icon="assignment_ind" label="接続先の割り当て" />
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
</q-breadcrumbs>
</div>
@@ -14,7 +14,7 @@
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
<q-space />
<div class="row q-gutter-md">
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="検索">
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
<template v-slot:append>
<q-icon name="search" />
</template>
@@ -36,9 +36,7 @@
既定
</q-chip>
<q-btn flat v-else :loading="activeDomainLoadingId === props.row.id" :disable="deleteDomainLoadingId === props.row.id" @click="activeDomain(props.row)">既定にする</q-btn>
<q-btn flat :disable="activeDomainLoadingId === props.row.id" :loading="deleteDomainLoadingId === props.row.id" @click="clickDeleteConfirm(props.row)">
削除
</q-btn>
<q-btn flat :disable="isNotOwner(props.row.owner.id) || activeDomainLoadingId === props.row.id" :text-color="isNotOwner(props.row.owner.id)?'grey':''" :loading="deleteDomainLoadingId === props.row.id" @click="clickDeleteConfirm(props.row)">削除</q-btn>
</q-card-actions>
</template>
</domain-card>
@@ -114,10 +112,7 @@ const addUserDomainFinished = async (val: string) => {
const selected = addDomainRef.value.selected;
if (val == 'OK' && selected.length > 0) {
addUserDomainLoading.value = true;
const { data } = await api.post('api/userdomain', {
userid: authStore.userId,
domainid: selected[0].id,
});
const { data } = await api.post(`api/domain/${authStore.userId}?domainid=${selected[0].id}`)
if (rows.value.length === 0 && data.data) {
const domain = data.data;
await authStore.setCurrentDomain({
@@ -167,11 +162,15 @@ const isActive = computed(() => (id: number) => {
return id == activeDomainId.value;
});
const isNotOwner = computed(() => (ownerId: string) => {
return ownerId !== authStore.userId;
});
const getDomain = async (userId? : string) => {
rowIds.clear();
const resp = await api.get('api/defaultdomain');
const resp = await api.get(`api/defaultdomain`);
activeDomainId.value = resp?.data?.data?.id;
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get('api/domain');
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
const domains = domainResult.data as any[];
rows.value = domains.sort((a, b) => a.id - b.id).reduce((acc, item) => {
rowIds.add(item.id);

View File

@@ -9,7 +9,7 @@
:pagination="pagination" >
<template v-slot:top>
<q-btn v-permissions="Actions.user.add" color="primary" :disable="loading" label="新規" @click="addRow" />
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
<q-space />
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
<template v-slot:append>
@@ -27,18 +27,11 @@
<div v-else>
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
</div>
</div>
</q-td>
</template>
<template v-slot:body-cell-roles="props">
<q-td :props="props">
<div class="row">
<q-chip v-if="(props.row as IUserRolesDisplay).isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
label="システム管理者" size="sm" />
<q-chip v-else v-for="(item) in (props.row as IUserRolesDisplay).roles" square color="primary" text-color="white" :key="item.id" :label="item.name" size="sm" />
</div>
</q-td>
</template>
@@ -46,15 +39,18 @@
<q-th :props="p">
<div class="row items-center">
<label class="q-mr-md">{{ p.col.label }}</label>
<q-select v-model="statusFilter" :options="statusFilterOptions" borderless
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
dense options-dense style="font-size: 12px; padding-top: 1px;" />
</div>
</q-th>
</template>
<template v-slot:body-cell-actions="p">
<q-td auto-width :props="p">
<table-action-menu :row="p.row" minWidth="180px" max-width="200px" :actions="actionList" />
<q-td :props="p">
<q-btn-group flat>
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
</q-btn-group>
</q-td>
</template>
</q-table>
@@ -157,13 +153,8 @@
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import TableActionMenu from 'components/TableActionMenu.vue';
import { useUserStore } from 'stores/useUserStore';
import { IUserDisplay, IUserRolesDisplay } from 'src/types/UserTypes';
import { Actions } from 'boot/permissions';
const userStore = useUserStore();
import { ref, onMounted } from 'vue';
import { api } from 'boot/axios';
const columns = [
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
@@ -171,25 +162,16 @@ const columns = [
{ name: 'firstName', label: '苗字', field: 'firstName', align: 'left', sortable: true },
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
{ name: 'status', label: '状況', field: 'status', align: 'left' },
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
{ name: 'actions', label: '', field: 'actions' }
{ name: 'actions', label: '操作', field: 'actions' }
];
const statusFilterOptions = [
{ label: '全データ', filter: () => true },
{ label: 'システム管理者のみ', filter: (row: IUserRolesDisplay) => row.isSuperuser },
{ label: '使用可能', filter: (row: IUserRolesDisplay) => row.isActive },
{ label: '使用不可', filter: (row: IUserRolesDisplay) => !row.isActive },
]
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
const loading = ref(false);
const addEditLoading = ref(false);
const filter = ref('');
const statusFilter = ref(statusFilterOptions[0]);
const rows = computed(() => userStore.users.filter(statusFilter.value.filter));
const statusFilter = ref('全データ');
const rows = ref([]);
const show = ref(false);
const confirm = ref(false);
const resetPsw = ref(false);
@@ -202,47 +184,69 @@ const isActive = ref(true);
const isPwd = ref(true);
const pwd = ref('');
const editId = ref(0);
const isCreate = computed(() => editId.value <= 0);
const isCreate = ref(true);
let editId = ref(0);
const actionList = [
{ label: '編集', icon: 'edit_note', permission: Actions.user.edit, action: editRow },
{ separator: true },
{ label: '削除', icon: 'delete_outline', permission: Actions.user.delete, class: 'text-red', action: showDeleteUserConfirm },
];
const getUsers = async () => {
const getUsers = async (filter = () => true) => {
loading.value = true;
await userStore.loadUsers();
const result = await api.get(`api/v1/users`);
rows.value = result.data.data.map((item) => {
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
}).filter(filter);
loading.value = false;
}
const updateStatusFilter = (status) => {
switch (status) {
case 'システム管理者のみ':
getUsers((row) => row.isSuperuser)
break;
case '使用可能':
getUsers((row) => row.isActive)
break;
case '使用不可':
getUsers((row) => !row.isActive)
break;
default:
getUsers()
break;
}
}
onMounted(async () => {
await getUsers();
})
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
// emulate fetching data from server
const addRow = () => {
// editId.value
onReset();
show.value = true;
}
function showDeleteUserConfirm(row: IUserDisplay) {
const removeRow = (row) => {
confirm.value = true;
editId.value = row.id;
}
const deleteUser = async () => {
await userStore.deleteUser(editId.value);
getUsers();
onReset();
const deleteUser = () => {
api.delete(`api/v1/users/${editId.value}`).then(() => {
getUsers();
})
editId.value = 0;
};
function editRow(row: IUserDisplay) {
const editRow = (row) => {
isCreate.value = false
editId.value = row.id;
firstName.value = row.firstName;
lastName.value = row.lastName;
email.value = row.email;
pwd.value = row.password;
isSuperuser.value = row.isSuperuser;
isActive.value = row.isActive;
@@ -256,25 +260,38 @@ const closeDg = () => {
onReset();
}
const onSubmit = async () => {
const onSubmit = () => {
addEditLoading.value = true;
const param = {
id: editId.value,
first_name: firstName.value,
last_name: lastName.value,
is_superuser: isSuperuser.value,
is_active: isActive.value,
email: email.value,
password: (isCreate.value || resetPsw.value) ? pwd.value : undefined
if (editId.value !== 0) {
api.put(`api/v1/users/${editId.value}`, {
'first_name': firstName.value,
'last_name': lastName.value,
'is_superuser': isSuperuser.value,
'is_active': isActive.value,
'email': email.value,
...(isCreate.value || resetPsw.value ? { password: pwd.value } : {})
}).then(() => {
getUsers();
closeDg();
onReset();
})
}
if (isCreate.value) {
await userStore.addUser(param);
} else {
await userStore.editUser(param);
else {
api.post(`api/v1/users`, {
'id': 0,
'first_name': firstName.value,
'last_name': lastName.value,
'is_superuser': isSuperuser.value,
'is_active': isActive.value,
'email': email.value,
'password': pwd.value
}).then(() => {
getUsers();
closeDg();
onReset();
})
}
getUsers();
closeDg();
onReset();
}
const onReset = () => {
@@ -286,6 +303,7 @@ const onReset = () => {
isSuperuser.value = false;
isPwd.value = true;
editId.value = 0;
isCreate.value = true;
resetPsw.value = false;
addEditLoading.value = false;
}

View File

@@ -54,8 +54,8 @@ const mouseenter = (event: Event) => {
let oDivs = oDiv1?.getElementsByClassName('add');
if (oDivs.length === 0) {
let oDiv2 = document.createElement('div');
oDiv2.className = 'add';
oDiv2.setAttribute('style', 'display:table-row;height:inherit;position: absolute;left:calc(50% - 19px);');
oDiv2.className = "add";
oDiv2.setAttribute("style", "display:table-row;height:inherit;position: absolute;left:calc(50% - 19px);");
oDiv2.innerHTML = oAdd;
oDiv1?.append(oDiv2);
}

View File

@@ -34,14 +34,13 @@ const routerInstance = createRouter({
export default route(function (/* { store, ssrContext } */) {
routerInstance.beforeEach(async (to, from) => {
routerInstance.beforeEach(async (to) => {
// clear alert on route change
//const alertStore = useAlertStore();
//alertStore.clear();
// redirect to login page if not logged in and trying to access a restricted page
const loginPage = '/login';
const publicPages = [loginPage];
const publicPages = ['/login'];
const authRequired = !publicPages.includes(to.path);
const authStore = useAuthStore();
@@ -50,16 +49,12 @@ export default route(function (/* { store, ssrContext } */) {
return '/login';
}
if (authStore.token && to.path === loginPage) {
return from.path == '/' ? '/' : false;
}
// redirect to domain setting page if no domain exist
const domainPages = [...publicPages, '/domain', '/userDomain', '/user', '/role'];
const domainPages = [...publicPages, '/domain', '/userDomain', '/user'];
if (!authStore.hasDomain && !domainPages.includes(to.path)) {
Dialog.create({
title: '注意',
message: '既定/利用可能なドメインはありません。<br>接続先管理ページに遷移して処理します。',
message: '既定/利用可能なドメインはありません。<br>ドメイン管理ページに遷移して処理します。',
html: true,
persistent: true,
})

View File

@@ -16,18 +16,18 @@ const routes: RouteRecordRaw[] = [
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue'), props: { app: '' } },
{ path: '', component: () => import('pages/IndexPage.vue') },
{ path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') },
{ path: 'test', component: () => import('pages/testQursar.vue') },
{ path: 'flow', component: () => import('pages/testFlow.vue') },
{ path: 'FlowChartTest', component: () => import('pages/FlowChartTest.vue') },
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
// { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
{ path: 'right', component: () => import('pages/testRight.vue') },
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
{ path: 'user', component: () => import('pages/UserManagement.vue')},
{ path: 'app', component: () => import('pages/AppManagement.vue')},
{ path: 'app/version/:id', component: () => import('pages/AppVersionManagement.vue')},
{ path: 'role', component: () => import('pages/RoleManagement.vue')},
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
],
},

View File

@@ -8,11 +8,3 @@ declare module '*.vue' {
const component: DefineComponent<{}, {}, any>;
export default component;
}
import { ComponentCustomProperties } from 'vue';
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$hasPermission: (permission: any) => boolean;
}
}

View File

@@ -1,107 +0,0 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { IAppDisplay, IAppVersion, IAppVersionDisplay, IManagedApp, IVersionSubmit } from 'src/types/AppTypes';
import { date, Notify } from 'quasar'
import { userToUserDisplay } from './useUserStore';
export const useAppStore = defineStore('app', {
state: () => ({
apps: [] as IAppDisplay[],
rowIds: new Set<string>(),
}),
actions: {
async loadApps() {
this.reset();
try {
const { data } = await api.get('api/apps');
this.apps = data.data.map((item: IManagedApp) => {
this.rowIds.add(item.appid);
return appToAppDisplay(item)
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
} catch (error) {
Notify.create({
icon: 'error',
color: 'negative',
message: 'アプリ一覧の読み込みに失敗しました'
})
}
},
getAppById(id: string) {
if (!this.rowIds.has(id)) {
return null;
}
return this.apps.find((item: IAppDisplay) => item.id === id);
},
async deleteApp(app: IAppDisplay) {
try {
await api.delete(`api/apps/${app.id}`);
} catch (error) {
console.error(error);
Notify.create({
icon: 'error',
color: 'negative',
message: 'アプリの削除に失敗しました'
});
return false;
}
return true;
},
async getVersionsByAppId(app: IAppDisplay) {
const { data } = await api.get(`api/appversions/${app.id}`);
return data.data.map((item: IAppVersion) => versionToVersionDisplay(item));
},
async changeVersion(app: IAppDisplay, version: IAppVersionDisplay) {
await api.put(`api/appversions/${app.id}/${version.id}`);
},
async createVersion(versionSubmit: IVersionSubmit) {
await api.post('api/apps', {
'appid': versionSubmit.appId,
'versionname': versionSubmit.name,
'comment': versionSubmit.comment
})
},
reset() {
this.apps = [];
this.rowIds.clear();
},
},
});
function versionToVersionDisplay(item: IAppVersion) {
return {
id: item.version,
version: item.version,
appid: item.appid,
name: item.versionname,
comment: item.comment,
updater: userToUserDisplay(item.updateuser),
updateTime: formatDate(item.update_time),
creator: userToUserDisplay(item.createuser),
createTime: formatDate(item.create_time),
} as IAppVersionDisplay;
}
function appToAppDisplay(app: IManagedApp) {
return {
id: app.appid,
sortId: parseInt(app.appid, 10),
name: app.appname,
url: `${app.domainurl}/k/${app.appid}`,
version: app.version,
versionName: app.versionname,
updateTime: formatDate(app.update_time),
updateUser: userToUserDisplay(app.updateuser),
versionChanged: app.is_saved
} as IAppDisplay
}
function formatDate(data: string) {
return date.formatDate(new Date(data + 'Z'), 'YYYY/MM/DD HH:mm');
}

View File

@@ -1,31 +1,23 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { router } from 'src/router';
import { IDomain, IDomainInfo } from '../types/DomainTypes';
import { IDomainInfo } from '../types/DomainTypes';
import { jwtDecode } from 'jwt-decode';
import { useAppStore } from './useAppStore';
import { userToUserRolesDisplay, useUserStore } from './useUserStore';
import { IRolesDisplay, IUser, IUserRolesDisplay } from 'src/types/UserTypes';
import { IResponse } from 'src/types/BaseTypes';
interface IPermission {
id: number,
menu: string,
function: string,
privilege: string,
link: string
interface UserInfo {
firstName: string;
lastName: string;
email: string;
}
type IPermissions = { [key: string]: string };
export interface IUserState {
token?: string;
returnUrl: string;
currentDomain: IDomainInfo;
LeftDrawer: boolean;
userInfo: IUserRolesDisplay;
tenant: string;
permissions: IPermissions;
userId?: string;
userInfo: UserInfo;
roles:string,
permissions: string;
}
export const useAuthStore = defineStore('auth', {
@@ -34,22 +26,17 @@ export const useAuthStore = defineStore('auth', {
returnUrl: '',
LeftDrawer: false,
currentDomain: {} as IDomainInfo,
userInfo: {} as IUserRolesDisplay,
tenant: '',
permissions: {} as IPermissions
userId: '',
userInfo: {} as UserInfo,
roles:'',
permissions: '',
}),
getters: {
userId(): number {
return this.userInfo.id;
toggleLeftDrawer(): boolean {
return this.LeftDrawer;
},
hasDomain(): boolean {
return this.currentDomain.id !== undefined;
},
roles(): IRolesDisplay[] {
return this.userInfo.roles;
},
isSuperAdmin(): boolean {
return this.userInfo.isSuperuser;
}
},
actions: {
@@ -64,62 +51,43 @@ export const useAuthStore = defineStore('auth', {
params.append('username', username);
params.append('password', password);
try {
const result = await api.post('api/token', params);
// console.info(result);
const result = await api.post(`api/token`, params);
console.info(result);
this.token = result.data.access_token;
const tokenJson = jwtDecode<{sub: number, tenant: string, roles: string}>(result.data.access_token);
this.tenant = tokenJson.tenant;
this.userInfo.id = tokenJson.sub;
this.userInfo.isSuperuser = tokenJson.roles === 'super';
const tokenJson = jwtDecode(result.data.access_token);
this.userId = tokenJson.sub;
this.permissions = (tokenJson as any).permissions==='ALL' ? 'admin': 'user';
api.defaults.headers['Authorization'] = 'Bearer ' + this.token;
await Promise.all([
this.loadCurrentDomain(),
this.loadUserInfo(),
this.loadPermission()
]);
await router.push(this.returnUrl || '/');
this.returnUrl = '';
this.currentDomain = await this.getCurrentDomain();
this.userInfo = await this.getUserInfo();
router.push(this.returnUrl || '/');
return true;
} catch (e) {
console.error(e);
return false;
}
},
async loadCurrentDomain() {
const resp = await api.get<IResponse<IDomain>>('api/defaultdomain');
async getCurrentDomain(): Promise<IDomainInfo> {
const resp = await api.get(`api/defaultdomain`);
const activedomain = resp?.data?.data;
if (!activedomain) {
this.currentDomain = {} as IDomainInfo;
} else {
this.currentDomain = {
id: activedomain.id,
domainName: activedomain.name,
kintoneUrl: activedomain.url,
};
return {
id: activedomain?.id,
domainName: activedomain?.name,
kintoneUrl: activedomain?.url,
};
},
async getUserInfo():Promise<UserInfo>{
const resp = (await api.get(`api/v1/users/me`)).data.data;
return {
firstName: resp.first_name,
lastName: resp.last_name,
email: resp.email,
}
},
async loadUserInfo() {
const resp = (await api.get<IResponse<IUser>>('api/v1/users/me'))?.data?.data;
this.userInfo = userToUserRolesDisplay(resp)
},
async loadPermission() {
this.permissions = {} as IPermissions;
if (this.isSuperAdmin) return;
const resp = (await api.get<IResponse<IPermission[]>>('api/v1/userpermssions')).data.data;
resp.forEach((permission) => {
this.permissions[permission.link] = permission.menu;
this.permissions[permission.privilege] = permission.function;
});
},
async logout() {
logout() {
this.token = '';
this.currentDomain = {} as IDomainInfo; // 清空当前域
useAppStore().reset();
useUserStore().reset();
await router.push('/login');
this.tenant = '';
this.userInfo = {} as IUserRolesDisplay;
this.permissions = {} as IPermissions;
router.push('/login');
},
async setCurrentDomain(domain?: IDomainInfo) {
if (!domain) {

View File

@@ -0,0 +1,23 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { IDomainInfo, IDomain } from '../types/DomainTypes';
export const useDomainStore = defineStore('domain', {
state: () => ({
userDomains: [] as IDomainInfo[],
}),
actions: {
async loadUserDomains(): Promise<IDomainInfo[]> {
const resp = await api.get(`api/domain`);
const domains = resp.data as IDomain[];
this.userDomains = domains
.filter(data => data.is_active)
.map((data) => ({
id: data.id,
domainName: data.name,
kintoneUrl: data.url,
}));
return this.userDomains;
},
},
});

View File

@@ -1,147 +0,0 @@
import { defineStore } from 'pinia';
import { api } from 'boot/axios';
import { IRoles, IRolesDisplay, IUser, IUserDisplay, IUserRolesDisplay, IUserSubmit } from 'src/types/UserTypes';
import { Notify } from 'quasar'
import { IResponse } from 'src/types/BaseTypes';
export const useUserStore = defineStore('user', {
state: () => ({
users: [] as IUserRolesDisplay[],
userIds: new Set<number>(),
roles: [] as IRolesDisplay[],
}),
actions: {
// -------------------------- users --------------------------
async loadUsers() {
this.reset('users');
try {
const { data } = await api.get<IResponse<IUser[]>>('api/v1/users');
this.users = data.data.map((item) => {
this.userIds.add(item.id);
return userToUserRolesDisplay(item)
}).sort((a, b) => a.id - b.id); // set default order
} catch (error) {
Notify.create({
icon: 'error',
color: 'negative',
message: 'ユーザー一覧の読み込みに失敗しました'
})
}
},
getUserById(id: number) {
if (!this.userIds.has(id)) {
return null;
}
return this.users.find((item: IUserDisplay) => item.id === id);
},
async addUser(user: IUserSubmit) {
return await api.post('api/v1/users', user);
},
async editUser(user: IUserSubmit) {
const id = user.id;
delete user['id']
return await api.put(`api/v1/users/${id}`, user);
},
async deleteUser(userId: number) {
try {
await api.delete(`api/v1/users/${userId}`)
} catch (error) {
console.error(error);
Notify.create({
icon: 'error',
color: 'negative',
message: 'ユーザーの削除に失敗しました'
});
return false;
}
return true;
},
// -------------------------- roles --------------------------
async loadRoles() {
this.reset('roles');
try {
const { data } = await api.get<IResponse<IRoles[]>>('api/v1/roles');
this.roles = data.data.map((item) => {
return roleToRoleDisplay(item)
}).sort((a, b) => a.id - b.id); // set default order
} catch (error) {
Notify.create({
icon: 'error',
color: 'negative',
message: 'ロール一覧の読み込みに失敗しました'
})
}
},
async addRole(user: IUserRolesDisplay, role: IRolesDisplay) {
return await this.updateUserRole(user, user.roleIds ? user.roleIds.concat(role.id) : [role.id]);
},
async removeRole(user: IUserRolesDisplay, role: IRolesDisplay) {
return await this.updateUserRole(user, user.roleIds ? user.roleIds.filter(e => e !== role.id) : []);
},
async updateUserRole(user: IUserRolesDisplay, roleids: number[]) {
return await api.post('api/v1/userrole', {
userid: user.id,
roleids
});
},
reset(target?: 'users'|'roles') {
if (!target) {
this.reset('users');
this.reset('roles');
return;
}
if (target == 'roles') {
this.roles = [];
} else if (target == 'users') {
this.users = [];
this.userIds.clear();
}
},
},
});
export function userToUserDisplay(user: IUser): IUserDisplay {
return {
id: user.id,
firstName: user.first_name,
lastName: user.last_name,
fullNameSearch: (user.last_name + user.first_name).toLowerCase(),
fullName: user.last_name + ' ' + user.first_name,
email: user.email,
isActive: user.is_active,
isSuperuser: user.is_superuser,
};
}
export function userToUserRolesDisplay(user: IUser): IUserRolesDisplay {
if (!user) return {} as IUserRolesDisplay;
const userRolesDisplay = userToUserDisplay(user) as IUserRolesDisplay;
const roles: IRolesDisplay[] = [];
const roleIds: number[] = [];
user.roles.sort((a, b) => a.level - b.level).forEach((role) => {
roles.push(roleToRoleDisplay(role));
roleIds.push(role.id);
});
userRolesDisplay.roles = roles;
userRolesDisplay.roleIds = roleIds;
return userRolesDisplay;
}
export function roleToRoleDisplay(roles: IRoles): IRolesDisplay {
return {
id: roles.id,
name: roles.description,
key: roles.name,
level: roles.level
};
}

View File

@@ -129,7 +129,7 @@ export class ActionNode implements IActionNode {
id: string;
name: string;
get title(): string {
const prop = this.actionProps.find((prop) => prop.props.name === 'displayName');
const prop = this.actionProps.find((prop) => prop.props.name === "displayName");
return prop?.props.modelValue;
};
get subTitle(): string {
@@ -138,7 +138,7 @@ export class ActionNode implements IActionNode {
//変数名
get varName():IProp|undefined{
const prop = this.actionProps.find((prop) => prop.props.name === 'verName');
const prop = this.actionProps.find((prop) => prop.props.name === "verName");
return prop?.props;
}

View File

@@ -4,13 +4,22 @@ export interface IManagedApp {
appid: string;
appname: string;
domainurl: string;
version: number;
versionname?: string;
version: string;
user: IUser;
updateuser: IUser;
create_time: string;
update_time: string;
}
export interface IManagedApp {
appid: string;
appname: string;
domainurl: string;
version: string;
user: IUser;
updateuser: IUser;
create_time: string;
update_time: string;
is_saved: boolean;
}
export interface IAppDisplay{
@@ -20,30 +29,16 @@ export interface IAppDisplay{
url:string;
updateUser: IUserDisplay;
updateTime:string;
version:number;
versionName?: string;
versionChanged: boolean;
version:string;
}
export interface IVersionSubmit {
appId: string;
export interface IVersionInfo {
id: string;
name?: string;
comment?: string;
desc?: string;
}
export interface IAppVersion {
id: number;
version: number;
appid: string;
versionname: string
comment: string;
updateuser: IUser;
update_time: string;
createuser: IUser;
create_time: string;
}
export interface IAppVersionDisplay {
id: number;
version: number;
appid: string;

View File

@@ -1,12 +0,0 @@
export interface IResponse<T = any> {
code: number;
data: T;
msg: string;
}
export interface IResponsePage<T = any> extends IResponse<T> {
page: number;
size: number;
total: number;
total_pages: number;
}

View File

@@ -136,7 +136,7 @@ export class KintoneEventManager {
const flows:IActionFlow[]=[];
for (const screen of this.screens) {
for (const event of screen.events) {
if (event.header === 'EVENT') {
if (event.header === "EVENT") {
const eventNode = event as IKintoneEvent;
if(eventNode.flowData!==undefined){
flows.push(eventNode.flowData);
@@ -144,7 +144,7 @@ export class KintoneEventManager {
}else if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
const eventGroup = event as IKintoneEventGroup;
eventGroup.events.forEach((ev) => {
if (ev.header === 'EVENT' || ev.header === 'DELETABLE') {
if (ev.header === "EVENT" || ev.header === "DELETABLE") {
const eventNode = ev as IKintoneEvent;
if(eventNode.flowData!==undefined){
flows.push(eventNode.flowData);

Some files were not shown because too many files have changed in this diff Show More