Compare commits
162 Commits
bugfix-ord
...
dev3
| Author | SHA1 | Date | |
|---|---|---|---|
| ca54f9d7a7 | |||
| dfa0842208 | |||
| b475b7fc99 | |||
| e4556a0d13 | |||
| 078929a254 | |||
| a8027a05bb | |||
| c0672f2487 | |||
| 14191e4f1e | |||
| a7788c87be | |||
| b95d81405d | |||
| f70a2cfde6 | |||
| f27c0728b7 | |||
| 2627c57b30 | |||
| af959469de | |||
| b502a3ba8f | |||
|
|
160367f91b | ||
| dec42a505e | |||
| 65b82949e6 | |||
| 95154907a4 | |||
| 59bddd4421 | |||
| 3f1accc32e | |||
|
|
dcbfb851ec | ||
|
|
4b6472f48e | ||
| 3eedbf7564 | |||
| 9c9b5aca95 | |||
| d31d3d0910 | |||
|
|
9d0cabcffa | ||
|
|
a4d59de2bc | ||
|
|
b1c55e3c31 | ||
|
|
d254cb7e54 | ||
|
|
a92873b971 | ||
| 5ebfd22652 | |||
| be203cb715 | |||
|
|
84ba118bb1 | ||
|
|
5d7ffa0138 | ||
|
|
972bbf9013 | ||
| 57af07ba73 | |||
| 8996a4c836 | |||
| fb0674ecff | |||
| 1da6a0c42b | |||
|
|
e9fa013d7d | ||
|
|
354abf252b | ||
| 8c481ecf4c | |||
| 76784b2683 | |||
| a5f5b3fccf | |||
|
|
ef9ed68468 | ||
| 1420773548 | |||
| 27ae3e186a | |||
| d3d3aa2d18 | |||
| c2a7ead1e3 | |||
|
|
d7280d66b2 | ||
|
|
e7f4078ca3 | ||
|
|
7cac64ced8 | ||
|
|
fef9e74ba1 | ||
|
|
736c722eb7 | ||
| 51e15287f5 | |||
| 0f639cdfa0 | |||
|
|
c0bda31353 | ||
|
|
78e7f1c840 | ||
|
|
35270e32f5 | ||
|
|
6b94af76c1 | ||
|
|
1135361b00 | ||
| 39775a5179 | |||
| 2823364148 | |||
|
|
40cadc82d0 | ||
| b928f2f3ef | |||
| 7b0b77dcb3 | |||
| 64aa2de133 | |||
| eea3761e52 | |||
|
|
74e8b78f6d | ||
| 3c4766cdad | |||
|
|
163e14022a | ||
|
|
8e0a9287e9 | ||
|
|
76643d280a | ||
|
|
f33fd0c64b | ||
| d6bd8fdee0 | |||
| c684105c2c | |||
| 9eec7e835d | |||
|
|
3ecb08b872 | ||
|
|
b95548e7f7 | ||
|
|
305868f091 | ||
|
|
7221f97139 | ||
| a3df6c4b37 | |||
| b874d0c776 | |||
| 21e0b9d6df | |||
| 9b1ae3bb5b | |||
| 8c4aa3119a | |||
| 62b6d7a878 | |||
| c5de6ace46 | |||
| 198e442292 | |||
|
|
cba365af9c | ||
| 91df7ed0fa | |||
| 3aec075927 | |||
| 29501f785f | |||
| 7e9654ab4c | |||
| 155cbd43e8 | |||
|
|
1786ea920a | ||
|
|
c8bb551ed1 | ||
|
|
e616f0c142 | ||
|
|
bfa85fab41 | ||
|
|
d3478ef851 | ||
|
|
b26877ef58 | ||
|
|
4336462ff1 | ||
| a6576827fd | |||
| b98c20d7ff | |||
|
|
93f44282d3 | ||
| adb0df3b17 | |||
| 4f17a6952d | |||
| c5c4f79e4f | |||
| 6504d8d29f | |||
| 9f61ab300c | |||
| 0c384bf57b | |||
|
|
a7860ed94a | ||
| 25ee2f8747 | |||
| 0d396024cb | |||
|
|
2a76f5a4c7 | ||
|
|
dcfe0d44fd | ||
|
|
660ffe36c2 | ||
|
|
8a3aaec8d5 | ||
| f13d1d51ca | |||
| bc52daa46a | |||
|
|
e3d842de15 | ||
| ff46485498 | |||
| d23e16d1eb | |||
| 8ee013527a | |||
| 39b02e0a8e | |||
| 647a5f4b8e | |||
| f3b93dc426 | |||
| c0feb74a13 | |||
| 77516b8814 | |||
|
|
bca2f46ea5 | ||
|
|
024645e16a | ||
|
|
df5b012bcd | ||
| 49d9475304 | |||
| 41aa11720d | |||
| 01b3e8b8b5 | |||
| 3726c8f342 | |||
| 2b4f4292a8 | |||
| 3b15dabedc | |||
| aa7daf4447 | |||
|
|
c5048a2ac3 | ||
| 0232e0d2c2 | |||
| 97d1232def | |||
| 3447c7832c | |||
| 65a7db7b3f | |||
| 1f176d40bc | |||
|
|
af77632da5 | ||
|
|
7a1ff8ac30 | ||
| cc726c7f68 | |||
| 0d825f6387 | |||
| 1db7d66648 | |||
| 440a0bd647 | |||
|
|
0fddeaa036 | ||
|
|
4cd3aff868 | ||
|
|
055ec1aeaf | ||
|
|
321f14b229 | ||
|
|
1626091e36 | ||
| fa1d3b01b0 | |||
|
|
bf4abe3cad | ||
|
|
3f98e17215 | ||
|
|
4563274789 | ||
|
|
3b9f08b43d |
File diff suppressed because one or more lines are too long
@@ -1,40 +1,58 @@
|
||||
|
||||
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(
|
||||
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
||||
):
|
||||
user = authenticate_user(db, form_data.username, form_data.password)
|
||||
if not user:
|
||||
async def login(request: Request,db:Session= Depends(get_db) ,form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
if not db :
|
||||
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
|
||||
)
|
||||
if user.is_superuser:
|
||||
permissions = "admin"
|
||||
roles = "super"
|
||||
permissions = "ALL"
|
||||
else:
|
||||
permissions = "user"
|
||||
roles = ";".join(role.name for role in user.roles)
|
||||
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
|
||||
permissions =";".join(list(set(perlst)))
|
||||
|
||||
access_token = security.create_access_token(
|
||||
data={"sub": user.id, "permissions": permissions},
|
||||
data={"sub": user.id,"roles":roles,"permissions": permissions,"tenant":user.tenantid,},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
request.state.user = user.id
|
||||
|
||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||
)
|
||||
|
||||
@r.post("/signup")
|
||||
async def signup(
|
||||
|
||||
@@ -8,25 +8,26 @@ import deepdiff
|
||||
import app.core.config as config
|
||||
import os
|
||||
from pathlib import Path
|
||||
from app.db.session import SessionLocal
|
||||
from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat
|
||||
from app.core.dbmanager import get_db
|
||||
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
|
||||
|
||||
kinton_router = r = APIRouter()
|
||||
|
||||
def getkintoneenv(user = Depends(get_current_user)):
|
||||
db = SessionLocal()
|
||||
domain = get_activedomain(db, user.id)
|
||||
db.close()
|
||||
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()
|
||||
kintoneevn = config.KINTONE_ENV(domain)
|
||||
return kintoneevn
|
||||
|
||||
|
||||
def getkintoneformat():
|
||||
db = SessionLocal()
|
||||
def getkintoneformat(db,user = Depends(get_current_user)):
|
||||
#db = SessionLocal()
|
||||
formats = get_kintoneformat(db)
|
||||
db.close()
|
||||
#db.close()
|
||||
return formats
|
||||
|
||||
|
||||
@@ -156,10 +157,10 @@ def getsettingfromexcel(df):
|
||||
des = df.iloc[2,2]
|
||||
return {"name":appname,"description":des}
|
||||
|
||||
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -171,24 +172,24 @@ def analysesettings(excel,kintone):
|
||||
updatesettings[key] = excel[key]
|
||||
return updatesettings
|
||||
|
||||
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||
data = {"app":app}
|
||||
data.update(updates)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
if revision != None:
|
||||
data = {"app":app,"revision":revision,"properties":fields}
|
||||
else:
|
||||
@@ -197,43 +198,43 @@ def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = N
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
params = {"app":app,"revision":revision,"fields":fields}
|
||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||
return r.json()
|
||||
|
||||
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
|
||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||
# スペース、枠線、ラベルを含まない
|
||||
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
# フォームに配置するフィールドのみ取得する
|
||||
# スペース、枠線、ラベルも含める
|
||||
def getformfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -286,10 +287,10 @@ def analysefields(excel,kintone):
|
||||
|
||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||
|
||||
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
@@ -374,24 +375,24 @@ def getkintoneorgs(c:config.KINTONE_ENV):
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
def uploadkintonefiles(file,c:config.KINTONE_ENV):
|
||||
def uploadkintonefiles(file,env:config.KINTONE_ENV):
|
||||
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
return {'fileKey':file}
|
||||
upload_files = {'file': open(file,'rb')}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
data ={'name':'file','filename':os.path.basename(file)}
|
||||
url = f"{c.BASE_URL}/k/v1/file.json"
|
||||
url = f"{env.BASE_URL}/k/v1/file.json"
|
||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||
#{"name":data['filename'],'fileKey':r['fileKey']}
|
||||
return r.json()
|
||||
|
||||
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||
def updateappjscss(app,uploads,env:config.KINTONE_ENV):
|
||||
dsjs = []
|
||||
dscss = []
|
||||
#mobile側
|
||||
mbjs = []
|
||||
mbcss = []
|
||||
customize = getappcustomize(app, c)
|
||||
customize = getappcustomize(app, env)
|
||||
current_js = customize['desktop'].get('js', [])
|
||||
current_css = customize['desktop'].get('css', [])
|
||||
current_mobile_js = customize['mobile'].get('js', [])
|
||||
@@ -430,16 +431,16 @@ def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||
ds ={'js':dsjs,'css':dscss}
|
||||
mb ={'js':mbjs,'css':mbcss}
|
||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
print(json.dumps(data))
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
#kintone カスタマイズ情報
|
||||
def getappcustomize(app,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
def getappcustomize(app,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
@@ -451,10 +452,10 @@ def getTempPath(filename):
|
||||
fpath = os.path.join(rootdir,"Temp",filename)
|
||||
return fpath
|
||||
|
||||
def createappjs(domainid,app):
|
||||
db = SessionLocal()
|
||||
flows = get_flows_by_app(db,domainid,app)
|
||||
db.close()
|
||||
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()
|
||||
content={}
|
||||
for flow in flows:
|
||||
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
||||
@@ -521,7 +522,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
||||
return {"files": [file.filename for file in files]}
|
||||
|
||||
@r.post("/updatejscss")
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
for file in files:
|
||||
@@ -542,21 +543,21 @@ async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env
|
||||
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
|
||||
|
||||
@r.get("/app")
|
||||
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
params ={"id":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/allapps")
|
||||
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
offset = 0
|
||||
limit = 100
|
||||
all_apps = []
|
||||
@@ -572,17 +573,17 @@ async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
return {"apps": all_apps}
|
||||
|
||||
except Exception as e:
|
||||
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAME}):", e)
|
||||
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({env.DOMAIN_NAME}):", e)
|
||||
|
||||
@r.get("/appfields")
|
||||
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
return getfieldsfromkintone(app,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/allfields")
|
||||
async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
field_resp = getfieldsfromkintone(app,env)
|
||||
form_resp = getformfromkintone(app,env)
|
||||
@@ -591,44 +592,44 @@ async def allfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/appprocess")
|
||||
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
return getprocessfromkintone(app,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/alljscss")
|
||||
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({env.DOMAIN_NAME}->{app}):",e)
|
||||
|
||||
@r.post("/createapp",)
|
||||
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
result = r.json()
|
||||
if result.get("app") != None:
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
data = {"apps":[result],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAME}->{name}):",e)
|
||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({env.DOMAIN_NAME}->{name}):",e)
|
||||
|
||||
|
||||
@r.post("/createappfromexcel",)
|
||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
try:
|
||||
mapping = getkintoneformat()[format]
|
||||
mapping = getkintoneformat(db)[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
@@ -665,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)):
|
||||
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
try:
|
||||
mapping = getkintoneformat()[format]
|
||||
mapping = getkintoneformat(db)[format]
|
||||
except Exception as e:
|
||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
||||
|
||||
@@ -757,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)):
|
||||
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv),db = Depends(get_db)):
|
||||
try:
|
||||
jscs=[]
|
||||
files=[]
|
||||
files.append(createappjs(env.DOMAIN_ID, app))
|
||||
files.append(createappjs(env.BASE_URL, app, db))
|
||||
files.append(getTempPath('alc_runtime.js'))
|
||||
files.append(getTempPath('alc_runtime.css'))
|
||||
for file in files:
|
||||
|
||||
@@ -2,34 +2,52 @@ 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.db.session import get_db
|
||||
# from app.db import Base,engine
|
||||
from app.core.dbmanager import get_db
|
||||
from app.db.crud import *
|
||||
from app.db.schemas import *
|
||||
from typing import List, Optional
|
||||
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
|
||||
|
||||
import httpx
|
||||
import app.core.config as config
|
||||
from app.core import domainCacheService,tenantCacheService
|
||||
|
||||
platform_router = r = APIRouter()
|
||||
|
||||
@r.get(
|
||||
"/test",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def test(
|
||||
request: Request,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
domainService.select(db,{"tenantid":1,"name":["b","c"]})
|
||||
|
||||
|
||||
@r.get(
|
||||
"/apps",
|
||||
response_model=List[AppList],
|
||||
"/apps",tags=["App"],
|
||||
response_model=ApiReturnModel[List[AppList]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def apps_list(
|
||||
request: Request,
|
||||
user = Depends(get_current_user),
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
|
||||
domain = get_activedomain(db, user.id)
|
||||
platformapps = get_apps(db,domain.url)
|
||||
|
||||
domain = domainService.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)
|
||||
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"
|
||||
@@ -47,28 +65,88 @@ async def apps_list(
|
||||
offset += limit
|
||||
|
||||
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
||||
filtered_apps = []
|
||||
for papp in platformapps:
|
||||
if papp.appid in kintone_apps_dict:
|
||||
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
||||
filtered_apps.append(papp)
|
||||
return filtered_apps
|
||||
return ApiReturnModel(data = filtered_apps)
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
|
||||
|
||||
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
|
||||
@r.post("/apps", tags=["App"],
|
||||
response_model=ApiReturnModel[AppList|None],
|
||||
response_model_exclude_none=True)
|
||||
async def apps_update(
|
||||
request: Request,
|
||||
app: AppVersion,
|
||||
user=Depends(get_current_user),
|
||||
app: VersionUpdate,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return update_appversion(db, app,user.id)
|
||||
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))
|
||||
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
|
||||
)
|
||||
async def apps_delete(
|
||||
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 ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data =appService.delete_app(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)
|
||||
|
||||
@r.get(
|
||||
"/appsettings/{id}",
|
||||
response_model=App,
|
||||
@@ -159,128 +237,177 @@ async def action_data(
|
||||
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
|
||||
|
||||
@r.get(
|
||||
"/flow/{flowid}",
|
||||
response_model=Flow,
|
||||
"/flow/{appid}",tags=["App"],
|
||||
response_model=ApiReturnModel[List[Flow]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_details(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
app = get_flow(db, flowid)
|
||||
return app
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.get_flow(db, domainurl, appid))
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/flows/{appid}",
|
||||
response_model=List[Flow],
|
||||
"/flows/{appid}", tags=["App"],
|
||||
response_model=List[Flow|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def flow_list(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_user),
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = get_activedomain(db, user.id)
|
||||
print("domain=>",domain)
|
||||
flows = get_flows_by_app(db, domain.url, appid)
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id) #get_activedomain(db, user.id)
|
||||
if not domainurl:
|
||||
return []
|
||||
#flows = get_flows_by_app(db, domainurl, appid)
|
||||
flows = appService.get_flow(db,domainurl,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", response_model=Flow, response_model_exclude_none=True)
|
||||
@r.post("/flow", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True)
|
||||
async def flow_create(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
user=Depends(get_current_user),
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = get_activedomain(db, user.id)
|
||||
return create_flow(db, domain.url, flow)
|
||||
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))
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
"/flow", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def flow_edit(
|
||||
request: Request,
|
||||
flow: FlowBase,
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return edit_flow(db, flow)
|
||||
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))
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
||||
"/flow/{flowid}", tags=["App"],
|
||||
response_model=ApiReturnModel[Flow|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def flow_delete(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return delete_flow(db, flowid)
|
||||
domainurl = domainCacheService.get_default_domainurl(db,user.id)
|
||||
if not domainurl:
|
||||
return ApiReturnModel(data = None)
|
||||
return ApiReturnModel(data = appService.delete_flow(db, flowid))
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
||||
|
||||
@r.get(
|
||||
"/domains/{tenantid}",
|
||||
response_model=List[Domain],
|
||||
"/domains",tags=["Domain"],
|
||||
response_model=ApiReturnPage[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_details(
|
||||
async def domain_list(
|
||||
request: Request,
|
||||
tenantid:str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domains = get_domains(db,tenantid)
|
||||
return domains
|
||||
if user.is_superuser:
|
||||
domains = domainService.get_domains(db)
|
||||
else:
|
||||
domains = domainService.get_domains_by_manage(db,user.id)
|
||||
return domains
|
||||
except Exception as e:
|
||||
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
|
||||
|
||||
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
|
||||
async def domain_create(
|
||||
@r.get(
|
||||
"/domain/{domain_id}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[Domain|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_detail(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
user=Depends(get_current_user),
|
||||
domain_id:int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return create_domain(db, domain,user.id)
|
||||
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)
|
||||
async def domain_create(
|
||||
request: Request,
|
||||
domain: DomainIn,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = domainService.create_domain(db, domain,user.id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/domain", response_model=Domain, response_model_exclude_none=True
|
||||
"/domain", tags=["Domain"],
|
||||
response_model=ApiReturnModel[Domain|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def domain_edit(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
domain: DomainIn,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return edit_domain(db, domain)
|
||||
domain = domainService.edit_domain(db, domain,user.id)
|
||||
if domain :
|
||||
domainCacheService.clear_default_domainurl()
|
||||
return ApiReturnModel(data = domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
||||
|
||||
|
||||
@r.delete(
|
||||
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
|
||||
"/domain/{id}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def domain_delete(
|
||||
request: Request,
|
||||
@@ -288,94 +415,175 @@ async def domain_delete(
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return delete_domain(db,id)
|
||||
return ApiReturnModel(data = domainService.delete_domain(db,id))
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain({id}):",e)
|
||||
|
||||
@r.get(
|
||||
"/domain",
|
||||
# response_model=List[Domain],
|
||||
response_model=List[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def userdomain_details(
|
||||
request: Request,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_user),
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domains = get_domain(db, userId if userId is not None else user.id)
|
||||
return domains
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||
|
||||
@r.post(
|
||||
"/domain/{userid}",
|
||||
"/userdomain",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def create_userdomain(
|
||||
request: Request,
|
||||
userid: int,
|
||||
domainids:List[int] ,
|
||||
userdomain:UserDomainParam,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = add_userdomain(db, userid,domainids)
|
||||
return domain
|
||||
userid = userdomain.userid
|
||||
domainid = userdomain.domainid
|
||||
if user.is_superuser:
|
||||
domain = domainService.add_userdomain(db,user.id,userid,domainid)
|
||||
else:
|
||||
domain = domainService.add_userdomain_by_owner(db,user.id,userid,domainid)
|
||||
return ApiReturnModel(data = domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while add user({userid}) domain({domainid}):",e)
|
||||
|
||||
@r.delete(
|
||||
"/domain/{domainid}/{userid}", response_model_exclude_none=True
|
||||
"/domain/{domainid}/{userid}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def userdomain_delete(
|
||||
async def delete_userdomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userid: int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return delete_userdomain(db, userid,domainid)
|
||||
return ApiReturnModel(data = domainService.delete_userdomain(db,userid,domainid))
|
||||
except Exception as e:
|
||||
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
||||
raise APIException('platform:userdomain',request.url._url,f"Error occurred while delete user({userid}) domain({domainid}):",e)
|
||||
|
||||
|
||||
|
||||
@r.get(
|
||||
"/activedomain",
|
||||
response_model=Domain,
|
||||
"/managedomainuser/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnPage[UserOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_useractivedomain(
|
||||
request: Request,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
# domain = get_activedomain(db, user.id)
|
||||
domain = get_activedomain(db, userId if userId is not None else user.id)
|
||||
if domain is None:
|
||||
return JSONResponse(content=None,status_code=HTTPStatus.OK)
|
||||
return domain
|
||||
except Exception as e:
|
||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
||||
|
||||
@r.put(
|
||||
"/activedomain/{domainid}",
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def update_activeuserdomain(
|
||||
async def get_managedomainuser(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_user),
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = active_userdomain(db, userId if userId is not None else user.id,domainid)
|
||||
return domain
|
||||
return domainService.get_managedomain_users(db,domainid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",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],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_defaultuserdomain(
|
||||
request: Request,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data =domainService.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)
|
||||
|
||||
@r.put(
|
||||
"/defaultdomain/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnModel[DomainOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def set_defualtuserdomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = domainCacheService.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)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/domainshareduser/{domainid}",tags=["Domain"],
|
||||
response_model=ApiReturnPage[UserOut|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def get_domainshareduser(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return domainService.get_shareddomain_users(db,domainid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:sharedomain',request.url._url,f"Error occurred while get user({user.id}) sharedomain:",e)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/events",
|
||||
|
||||
@@ -1,107 +1,187 @@
|
||||
from fastapi import APIRouter, Request, Depends, Response, encoders
|
||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
||||
import typing as t
|
||||
|
||||
from app.db.session import get_db
|
||||
from app.core.common import ApiReturnModel,ApiReturnPage
|
||||
from app.core.apiexception import APIException
|
||||
from app.core.dbmanager import get_db
|
||||
from app.db.crud import (
|
||||
get_allusers,
|
||||
get_users,
|
||||
get_user,
|
||||
create_user,
|
||||
delete_user,
|
||||
edit_user,
|
||||
assign_userrole,
|
||||
get_roles,
|
||||
)
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut
|
||||
from app.core.auth import get_current_active_user, get_current_active_superuser
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut,RoleBase,AssignUserRoles,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
|
||||
|
||||
users_router = r = APIRouter()
|
||||
|
||||
|
||||
@r.get(
|
||||
"/users",
|
||||
response_model=t.List[User],
|
||||
"/users",tags=["User"],
|
||||
response_model=ApiReturnPage[User],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def users_list(
|
||||
response: Response,
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
current_user=Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
Get all users
|
||||
"""
|
||||
users = get_users(db)
|
||||
# This is necessary for react-admin to work
|
||||
response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||
return users
|
||||
try:
|
||||
if current_user.is_superuser:
|
||||
users = userService.get_users(db)
|
||||
else:
|
||||
users = userService.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)
|
||||
|
||||
|
||||
@r.get("/users/me", response_model=User, response_model_exclude_none=True)
|
||||
@r.get("/users/me", tags=["User"],
|
||||
response_model=ApiReturnModel[User],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def user_me(current_user=Depends(get_current_active_user)):
|
||||
"""
|
||||
Get own user
|
||||
"""
|
||||
return current_user
|
||||
return ApiReturnModel(data = current_user)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/users/{user_id}",
|
||||
response_model=User,
|
||||
"/users/{user_id}",tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def user_details(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
current_user=Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
Get any user details
|
||||
"""
|
||||
user = get_user(db, user_id)
|
||||
return user
|
||||
# return encoders.jsonable_encoder(
|
||||
# user, skip_defaults=True, exclude_none=True,
|
||||
# )
|
||||
try:
|
||||
user = userService.get(db, user_id)
|
||||
if user:
|
||||
if user.is_superuser and not current_user.is_superuser:
|
||||
user = None
|
||||
return ApiReturnModel(data = user)
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while get user({user_id}) detail:",e)
|
||||
|
||||
|
||||
@r.post("/users", response_model=User, response_model_exclude_none=True)
|
||||
@r.post("/users", tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def user_create(
|
||||
request: Request,
|
||||
user: UserCreate,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
current_user=Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
Create a new user
|
||||
"""
|
||||
return create_user(db, user)
|
||||
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))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while create user({user.email}):",e)
|
||||
|
||||
|
||||
@r.put(
|
||||
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||
"/users/{user_id}", tags=["User"],
|
||||
response_model=ApiReturnModel[User|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def user_edit(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
user: UserEdit,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
current_user=Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
Update existing user
|
||||
"""
|
||||
return edit_user(db, user_id, user)
|
||||
|
||||
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))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while edit user({user_id}):",e)
|
||||
|
||||
@r.delete(
|
||||
"/users/{user_id}", response_model=User, response_model_exclude_none=True
|
||||
"/users/{user_id}", tags=["User"],
|
||||
response_model=ApiReturnModel[UserOut|None],
|
||||
response_model_exclude_none=True
|
||||
)
|
||||
async def user_delete(
|
||||
request: Request,
|
||||
user_id: int,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
current_user=Depends(get_current_active_user),
|
||||
):
|
||||
"""
|
||||
Delete existing user
|
||||
"""
|
||||
return delete_user(db, user_id)
|
||||
try:
|
||||
user = userService.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))
|
||||
except Exception as e:
|
||||
raise APIException('user:users',request.url._url,f"Error occurred while delete user({user_id}):",e)
|
||||
|
||||
|
||||
@r.post("/userrole",tags=["User"],
|
||||
response_model=ApiReturnModel[User],
|
||||
response_model_exclude_none=True,)
|
||||
async def assign_role(
|
||||
request: Request,
|
||||
userroles:AssignUserRoles,
|
||||
db=Depends(get_db)
|
||||
):
|
||||
try:
|
||||
return ApiReturnModel(data = userService.assign_userrole(db,userroles.userid,userroles.roleids))
|
||||
except Exception as e:
|
||||
raise APIException('user:userrole',request.url._url,f"Error occurred while assign user({userroles.userid}) roles({userroles.roleids}):",e)
|
||||
|
||||
@r.get(
|
||||
"/roles",tags=["User"],
|
||||
response_model=ApiReturnModel[t.List[RoleBase]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def roles_list(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
#current_user=Security(get_current_active_user, scopes=["role_list"]),
|
||||
):
|
||||
try:
|
||||
|
||||
if current_user.is_superuser:
|
||||
roles = userService.get_roles(db)
|
||||
else:
|
||||
if len(current_user.roles)>0:
|
||||
roles = userService.get_roles_by_level(db,current_user.roles)
|
||||
else:
|
||||
roles = []
|
||||
return ApiReturnModel(data = roles)
|
||||
except Exception as e:
|
||||
raise APIException('user:roles',request.url._url,f"Error occurred while get roles:",e)
|
||||
|
||||
@r.get(
|
||||
"/userpermssions",tags=["User"],
|
||||
response_model=ApiReturnModel[t.List[Permission]|None],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def permssions_list(
|
||||
request: Request,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
#current_user=Security(get_current_active_user, scopes=["role_list"]),
|
||||
):
|
||||
try:
|
||||
if current_user.is_superuser:
|
||||
permissions = userService.get_permissions(db)
|
||||
else:
|
||||
if len(current_user.roles)>0:
|
||||
permissions = userService.get_user_permissions(db,current_user.id)
|
||||
else:
|
||||
permissions = []
|
||||
return ApiReturnModel(data = permissions)
|
||||
except Exception as e:
|
||||
raise APIException('user:userpermssions',request.url._url,f"Error occurred while get user({current_user.id}) permissions:",e)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from app.core.cache import domainCacheService
|
||||
from app.core.cache import tenantCacheService
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import HTTPException, status
|
||||
from fastapi import HTTPException, status,Depends
|
||||
import httpx
|
||||
from app.db.schemas import ErrorCreate
|
||||
from app.db.session import SessionLocal
|
||||
from app.core.dbmanager import get_log_db
|
||||
from app.db.crud import create_log
|
||||
|
||||
class APIException(Exception):
|
||||
@@ -19,7 +19,7 @@ class APIException(Exception):
|
||||
elif hasattr(e, 'detail'):
|
||||
self.detail = e.detail
|
||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
||||
content += e.detail
|
||||
content += str(e.detail)
|
||||
else:
|
||||
self.detail = str(e)
|
||||
self.status_code = 500
|
||||
@@ -31,9 +31,10 @@ class APIException(Exception):
|
||||
self.error = ErrorCreate(location=location, title=title, content=content)
|
||||
super().__init__(self.error)
|
||||
|
||||
def writedblog(exc: APIException):
|
||||
db = SessionLocal()
|
||||
def writedblog(exc: APIException,):
|
||||
#db = SessionLocal()
|
||||
db = get_log_db()
|
||||
try:
|
||||
create_log(db,exc.error)
|
||||
finally:
|
||||
db.close()
|
||||
db.close()
|
||||
@@ -1,14 +1,16 @@
|
||||
from fastapi.security import SecurityScopes
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi import Depends, HTTPException, Request, Security, status
|
||||
from jwt import PyJWTError
|
||||
|
||||
from app.db import models, schemas, session
|
||||
from app.db import models, schemas
|
||||
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
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
||||
async def get_current_user(request: Request,security_scopes: SecurityScopes,
|
||||
db=Depends(get_db), token: str = Depends(security.oauth2_scheme)
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
@@ -22,13 +24,25 @@ async def get_current_user(
|
||||
id: int = payload.get("sub")
|
||||
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:
|
||||
if scope not in permissions.split(";"):
|
||||
raise HTTPException(
|
||||
status_code=403, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||
except PyJWTError:
|
||||
raise credentials_exception
|
||||
user = get_user(db, token_data.id)
|
||||
user = userService.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(
|
||||
@@ -50,11 +64,11 @@ async def get_current_active_superuser(
|
||||
|
||||
|
||||
def authenticate_user(db, email: str, password: str):
|
||||
user = get_user_by_email(db, email)
|
||||
user = userService.get_user_by_email(db,email) #get_user_by_email(db, email)
|
||||
if not user:
|
||||
return False
|
||||
return None
|
||||
if not security.verify_password(password, user.hashed_password):
|
||||
return False
|
||||
return None
|
||||
return user
|
||||
|
||||
|
||||
|
||||
72
backend/app/core/cache.py
Normal file
72
backend/app/core/cache.py
Normal file
@@ -0,0 +1,72 @@
|
||||
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()
|
||||
49
backend/app/core/common.py
Normal file
49
backend/app/core/common.py
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
import math
|
||||
from fastapi import Query
|
||||
from fastapi_pagination.bases import AbstractPage,AbstractParams,RawParams
|
||||
from pydantic import BaseModel
|
||||
from typing import Any, Generic, List, Type,TypeVar,Generic,Sequence
|
||||
from fastapi_pagination import Page,utils
|
||||
|
||||
T = TypeVar('T')
|
||||
|
||||
class ApiReturnModel(BaseModel,Generic[T]):
|
||||
code:int = 0
|
||||
msg:str ="OK"
|
||||
data:T
|
||||
|
||||
class ApiReturnError(BaseModel):
|
||||
code:int = -1
|
||||
msg:str =""
|
||||
|
||||
|
||||
class Params(BaseModel, AbstractParams):
|
||||
page:int = Query(1,get=1, description="Page number")
|
||||
size:int = Query(20,get=0, le=100,description="Page size")
|
||||
|
||||
def to_raw_params(self) -> RawParams:
|
||||
return RawParams(
|
||||
limit=self.size,
|
||||
offset=self.size*(self.page-1)
|
||||
)
|
||||
|
||||
class ApiReturnPage(AbstractPage[T],Generic[T]):
|
||||
code:int =0
|
||||
msg:str ="OK"
|
||||
data:Sequence[T]
|
||||
total:int
|
||||
page:int
|
||||
size:int
|
||||
# next:str
|
||||
# previous:str
|
||||
total_pages:int
|
||||
|
||||
__params_type__ =Params
|
||||
|
||||
@classmethod
|
||||
def create(cls,items:Sequence[T],params:Params,**kwargs: Any) -> Type[Page[T]]:
|
||||
total = kwargs.get('total', 0)
|
||||
total_pages = math.ceil(total/params.size)
|
||||
|
||||
return utils.create_pydantic_model(cls,data=items,total=total,page=params.page,size=params.size,total_pages=total_pages)
|
||||
20
backend/app/core/dbmanager.py
Normal file
20
backend/app/core/dbmanager.py
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
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
|
||||
75
backend/app/core/operation.py
Normal file
75
backend/app/core/operation.py
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import jwt
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from passlib.context import CryptContext
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta,timezone
|
||||
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.utcnow() + expires_delta
|
||||
expire = datetime.now(timezone.utc) + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
@@ -18,10 +19,15 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase:
|
||||
return db.query(models.User).filter(models.User.email == email).first()
|
||||
|
||||
|
||||
def get_users(
|
||||
db: Session, skip: int = 0, limit: int = 100
|
||||
def get_allusers(
|
||||
db: Session
|
||||
) -> t.List[schemas.UserOut]:
|
||||
return db.query(models.User).offset(skip).limit(limit).all()
|
||||
return db.query(models.User).all()
|
||||
|
||||
def get_users(
|
||||
db: Session
|
||||
) -> t.List[schemas.UserOut]:
|
||||
return db.query(models.User).filter(models.User.is_superuser == False)
|
||||
|
||||
|
||||
def create_user(db: Session, user: schemas.UserCreate):
|
||||
@@ -69,28 +75,48 @@ def edit_user(
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
|
||||
def get_roles(
|
||||
db: Session
|
||||
) -> t.List[schemas.RoleBase]:
|
||||
return db.query(models.Role).all()
|
||||
|
||||
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
||||
db_user = db.query(models.User).get(user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
db_user.roles.remove(role)
|
||||
for roleid in roles:
|
||||
role = db.query(models.Role).get(roleid)
|
||||
if role:
|
||||
db_user.roles.append(role)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
def get_apps(
|
||||
db: Session,
|
||||
domain_url:str
|
||||
domainurl:str
|
||||
) -> t.List[schemas.AppList]:
|
||||
return db.query(models.App).filter(models.App.domainurl == domain_url).all()
|
||||
return db.query(models.App).filter(models.App.domainurl == domainurl).all()
|
||||
|
||||
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||
app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
||||
if app:
|
||||
app.version = app.version + 1
|
||||
db_app = app
|
||||
appver = app.version
|
||||
else:
|
||||
appver = 1
|
||||
db_app = models.App(
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
db_app.version = db_app.version + 1
|
||||
appversion = models.AppVersion(
|
||||
domainurl = appedit.domainurl,
|
||||
appid=appedit.appid,
|
||||
appname=appedit.appname,
|
||||
version = 1,
|
||||
updateuser= userid
|
||||
)
|
||||
|
||||
appname=db_app.appname,
|
||||
version = db_app.version,
|
||||
versionname = appedit.versionname,
|
||||
comment = appedit.comment,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(appversion)
|
||||
db.add(db_app)
|
||||
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
|
||||
@@ -103,7 +129,9 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||
name = flow.name,
|
||||
content = flow.content,
|
||||
createuser = userid,
|
||||
version = appver
|
||||
version = db_app.version,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(db_flowhistory)
|
||||
|
||||
@@ -111,6 +139,17 @@ def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||
db.refresh(db_app)
|
||||
return db_app
|
||||
|
||||
def delete_apps(db: Session, domainurl: str,appid: str ):
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found")
|
||||
db.delete(db_app)
|
||||
db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid))
|
||||
for flow in db_flows:
|
||||
db.delete(flow)
|
||||
db.commit()
|
||||
return db_app
|
||||
|
||||
def get_appsetting(db: Session, id: int):
|
||||
app = db.query(models.AppSetting).get(id)
|
||||
if not app:
|
||||
@@ -166,16 +205,28 @@ def get_actions(db: Session):
|
||||
return actions
|
||||
|
||||
|
||||
def create_flow(db: Session, domainurl: str, flow: schemas.FlowBase):
|
||||
def create_flow(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
|
||||
content=flow.content,
|
||||
createuserid = userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.add(db_flow)
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first()
|
||||
if not db_app:
|
||||
db_app = models.App(
|
||||
domainurl = domainurl,
|
||||
appid=flow.appid,
|
||||
appname=flow.appname,
|
||||
version = 0,
|
||||
createuserid= userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
@@ -190,16 +241,20 @@ def delete_flow(db: Session, flowid: str):
|
||||
|
||||
|
||||
def edit_flow(
|
||||
db: Session, flow: schemas.FlowBase
|
||||
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int
|
||||
) -> schemas.Flow:
|
||||
db_flow = get_flow(db, flow.flowid)
|
||||
if not db_flow:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||
update_data = flow.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
setattr(db_flow, key, value)
|
||||
|
||||
#見つからない時新規作成
|
||||
return 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.commit()
|
||||
db.refresh(db_flow)
|
||||
@@ -214,8 +269,8 @@ def get_flows(db: Session, flowid: str):
|
||||
|
||||
def get_flow(db: Session, flowid: str):
|
||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
# if not flow:
|
||||
# raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flow
|
||||
|
||||
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
||||
@@ -224,54 +279,78 @@ def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
||||
raise Exception("Data not found")
|
||||
return flows
|
||||
|
||||
def create_domain(db: Session, domain: schemas.DomainBase,userid:int):
|
||||
def create_domain(db: Session, domain: schemas.DomainIn,userid:int):
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain = models.Domain(
|
||||
tenantid = domain.tenantid,
|
||||
name=domain.name,
|
||||
url=domain.url,
|
||||
is_active=domain.is_active,
|
||||
kintoneuser=domain.kintoneuser,
|
||||
kintonepwd=domain.kintonepwd
|
||||
kintonepwd=domain.kintonepwd,
|
||||
createuserid = userid,
|
||||
updateuserid = userid,
|
||||
ownerid = domain.ownerid
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.flush()
|
||||
add_userdomain(db,userid,db_domain.id)
|
||||
#add_userdomain(db,userid,db_domain.id)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
def delete_domain(db: Session,id: int):
|
||||
db_domain = db.query(models.Domain).get(id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
#if not db_domain:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
if db_domain:
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def edit_domain(
|
||||
db: Session, domain: schemas.DomainBase
|
||||
db: Session, domain: schemas.DomainIn,userid:int
|
||||
) -> schemas.Domain:
|
||||
domain.encrypt_kintonepwd()
|
||||
if domain.kintonepwd != "":
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain = db.query(models.Domain).get(domain.id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
update_data = domain.dict(exclude_unset=True)
|
||||
|
||||
for key, value in update_data.items():
|
||||
if key != "id" and not (key == "kintonepwd" and (value is None or value == "")):
|
||||
setattr(db_domain, key, value)
|
||||
print(str(db_domain))
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db_domain.tenantid = domain.tenantid
|
||||
db_domain.name=domain.name
|
||||
db_domain.url=domain.url
|
||||
if db_domain.is_active == True and domain.is_active == False:
|
||||
db_userdomains = db.query(models.UserDomain).filter(and_(models.UserDomain.domainid == db_domain.id,models.UserDomain.active == True)).all()
|
||||
for userdomain in db_userdomains:
|
||||
userdomain.active = False
|
||||
db.add(userdomain)
|
||||
db_domain.is_active=domain.is_active
|
||||
db_domain.kintoneuser=domain.kintoneuser
|
||||
if domain.kintonepwd != "":
|
||||
db_domain.kintonepwd = domain.kintonepwd
|
||||
db_domain.updateuserid = userid
|
||||
db_domain.ownerid = domain.ownerid
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
|
||||
|
||||
def add_userdomain(db: Session, userid:int,domainid:int):
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
return user_domain
|
||||
def add_admindomain(db: Session,userid:int,domainid:int):
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def add_userdomain(db: Session,ownerid:int, userid:int,domainid:int):
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.ownerid == ownerid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
||||
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
|
||||
@@ -281,35 +360,40 @@ def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
||||
|
||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
#if not db_domain:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
if db_domain:
|
||||
db.delete(db_domain)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||
if not db_userdomains:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
for domain in db_userdomains:
|
||||
if domain.domainid == domainid:
|
||||
domain.active = True
|
||||
else:
|
||||
domain.active = False
|
||||
db.add(domain)
|
||||
db.commit()
|
||||
return db_userdomains
|
||||
db_domain = db.query(models.Domain).filter(and_(models.Domain.id == domainid,models.Domain.is_active)).first()
|
||||
if db_domain:
|
||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
||||
# if not db_userdomains:
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
|
||||
for domain in db_userdomains:
|
||||
if domain.domainid == domainid:
|
||||
domain.active = True
|
||||
else:
|
||||
domain.active = False
|
||||
db.add(domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
|
||||
def get_activedomain(db: Session, userid: int)-> t.Optional[models.Domain]:
|
||||
user_domains = (db.query(models.Domain,models.UserDomain.active)
|
||||
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
||||
.filter(models.UserDomain.userid == userid)
|
||||
.all())
|
||||
db_domain=None
|
||||
if len(user_domains)==1:
|
||||
db_domain = user_domains[0][0];
|
||||
else:
|
||||
db_domain = next((domain for domain,active in user_domains if active),None)
|
||||
def get_activedomain(db: Session, userid: int):
|
||||
# user_domains = (db.query(models.Domain,models.UserDomain.active)
|
||||
# .join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
||||
# .filter(models.UserDomain.userid == userid)
|
||||
# .all())
|
||||
db_domain=(db.query(models.Domain).filter(models.Domain.is_active)
|
||||
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id).filter(and_(models.UserDomain.active,models.UserDomain.userid == userid)).first())
|
||||
# if len(user_domains)==1:
|
||||
# db_domain = user_domains[0][0];
|
||||
# else:
|
||||
# db_domain = next((domain for domain,active in user_domains if active),None)
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
return db_domain
|
||||
|
||||
@@ -322,13 +406,12 @@ def get_domain(db: Session, userid: str):
|
||||
# domain.kintonepwd = decrypted_pwd
|
||||
return domains
|
||||
|
||||
def get_domains(db: Session,tenantid:str):
|
||||
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
||||
if not domains:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
# for domain in domains:
|
||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||
# domain.kintonepwd = decrypted_pwd
|
||||
def get_alldomains(db: Session):
|
||||
domains = db.query(models.Domain).all()
|
||||
return domains
|
||||
|
||||
def get_domains(db: Session,userid:int):
|
||||
domains = db.query(models.Domain).filter(models.Domain.ownerid == userid ).all()
|
||||
return domains
|
||||
|
||||
def get_events(db: Session):
|
||||
|
||||
4
backend/app/db/cruddb/__init__.py
Normal file
4
backend/app/db/cruddb/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
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
|
||||
104
backend/app/db/cruddb/crudbase.py
Normal file
104
backend/app/db/cruddb/crudbase.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from sqlalchemy import asc, desc, select
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm.query import Query
|
||||
from typing import Type, List, Optional
|
||||
from app.core.common import ApiReturnPage
|
||||
from sqlalchemy import and_ ,or_
|
||||
from pydantic import BaseModel
|
||||
from app.db import models
|
||||
|
||||
class crudbase:
|
||||
def __init__(self, model: Type[models.Base]):
|
||||
self.model = model
|
||||
|
||||
def _apply_filters(self, query: Query, filters: dict) -> Query:
|
||||
and_conditions = []
|
||||
or_conditions = []
|
||||
for column_name, value in filters.items():
|
||||
column = getattr(self.model, column_name, None)
|
||||
if column:
|
||||
if isinstance(value, dict):
|
||||
if 'operator' in value:
|
||||
operator = value['operator']
|
||||
filter_value = value['value']
|
||||
if operator == '!=':
|
||||
and_conditions.append(column != filter_value)
|
||||
elif operator == 'like':
|
||||
and_conditions.append(column.like(f"%{filter_value}%"))
|
||||
elif operator == '=':
|
||||
and_conditions.append(column == filter_value)
|
||||
elif operator == '>':
|
||||
and_conditions.append(column > filter_value)
|
||||
elif operator == '>=':
|
||||
and_conditions.append(column >= filter_value)
|
||||
elif operator == '<':
|
||||
and_conditions.append(column < filter_value)
|
||||
elif operator == '<=':
|
||||
and_conditions.append(column <= filter_value)
|
||||
elif operator == 'in':
|
||||
if isinstance(filter_value, list):
|
||||
or_conditions.append(column.in_(filter_value))
|
||||
else:
|
||||
and_conditions.append(column == filter_value)
|
||||
else:
|
||||
and_conditions.append(column == value)
|
||||
else:
|
||||
and_conditions.append(column == value)
|
||||
|
||||
if and_conditions:
|
||||
query = query.where(and_(*and_conditions))
|
||||
if or_conditions:
|
||||
query = query.where(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)
|
||||
if column:
|
||||
if sort_order == "desc":
|
||||
query = query.order_by(desc(column))
|
||||
else:
|
||||
query = query.order_by(asc(column))
|
||||
return query
|
||||
|
||||
def get_all(self) -> Query:
|
||||
return select(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()
|
||||
|
||||
def create(self, db: Session, obj_in: BaseModel) -> models.Base:
|
||||
db_obj = self.model(**obj_in.model_dump())
|
||||
db.add(db_obj)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
def update(self, db: Session, item_id: int, obj_in: BaseModel) -> Optional[models.Base]:
|
||||
db_obj = self.get(db,item_id)
|
||||
if db_obj:
|
||||
for key, value in obj_in.model_dump(exclude_unset=True).items():
|
||||
setattr(db_obj, key, value)
|
||||
db.commit()
|
||||
db.refresh(db_obj)
|
||||
return db_obj
|
||||
return None
|
||||
|
||||
def delete(self, db: Session, item_id: int) -> Optional[models.Base]:
|
||||
db_obj = self.get(db,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,
|
||||
sort_order: Optional[str] = "asc") -> Query:
|
||||
query = select(self.model)
|
||||
if filters:
|
||||
query = self._apply_filters(query, filters)
|
||||
if sort_by:
|
||||
query = self._apply_sorting(query, sort_by, sort_order)
|
||||
print(str(query))
|
||||
return query
|
||||
216
backend/app/db/cruddb/dbapp.py
Normal file
216
backend/app/db/cruddb/dbapp.py
Normal file
@@ -0,0 +1,216 @@
|
||||
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()
|
||||
218
backend/app/db/cruddb/dbdomain.py
Normal file
218
backend/app/db/cruddb/dbdomain.py
Normal file
@@ -0,0 +1,218 @@
|
||||
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 dbuserdomain(crudbase):
|
||||
def __init__(self):
|
||||
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()
|
||||
|
||||
def get_userdomain_by_domainid(self,db: Session,ownerid:int,domainid:int):
|
||||
return super().get_by_conditions({"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()
|
||||
|
||||
def get_user_default_domain(self,db: Session,userid:int):
|
||||
return db.execute(super().get_by_conditions({"userid":userid,"is_default":True})).scalars().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)
|
||||
|
||||
def get_domains_by_owner(self,db: Session,ownerid:int)-> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_by_conditions({"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
|
||||
|
||||
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:
|
||||
db_domain = super().get(db,domain.id)
|
||||
if db_domain:
|
||||
db_domain.tenantid = domain.tenantid
|
||||
db_domain.name=domain.name
|
||||
db_domain.url=domain.url
|
||||
if db_domain.is_active == True and domain.is_active == False:
|
||||
db_userdomains = dbuserdomain.get_default_domains(db,domain.id)
|
||||
for userdomain in db_userdomains:
|
||||
userdomain.is_default = False
|
||||
db.add(userdomain)
|
||||
db_domain.is_active=domain.is_active
|
||||
db_domain.kintoneuser=domain.kintoneuser
|
||||
if domain.kintonepwd != "" and domain.kintonepwd != None:
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain.kintonepwd = domain.kintonepwd
|
||||
db_domain.updateuserid = userid
|
||||
db.add(db_domain)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
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_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if not db_userdomain:
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid ,createuserid = ownerid,updateuserid = ownerid)
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
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()
|
||||
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)
|
||||
db.add(user_domain)
|
||||
db.commit()
|
||||
return db_domain
|
||||
return None
|
||||
|
||||
def delete_userdomain(self,db: Session, userid: int,domainid: int) -> schemas.DomainOut:
|
||||
db_userdomain = dbuserdomain.get_userdomain(db,userid,domainid)
|
||||
if db_userdomain:
|
||||
domain = db_userdomain.domain
|
||||
db.delete(db_userdomain)
|
||||
db.commit()
|
||||
return domain
|
||||
return None
|
||||
|
||||
def get_default_domain(self,db: Session, userid: int) -> schemas.DomainOut:
|
||||
userdomain = dbuserdomain.get_user_default_domain(db,userid)
|
||||
if userdomain:
|
||||
return userdomain.domain
|
||||
else:
|
||||
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()
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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 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()
|
||||
13
backend/app/db/cruddb/dbtenant.py
Normal file
13
backend/app/db/cruddb/dbtenant.py
Normal file
@@ -0,0 +1,13 @@
|
||||
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()
|
||||
98
backend/app/db/cruddb/dbuser.py
Normal file
98
backend/app/db/cruddb/dbuser.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import 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 dbpermission(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Permission)
|
||||
|
||||
dbpermission = dbpermission()
|
||||
|
||||
class dbrole(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.Role)
|
||||
|
||||
dbrole = dbrole()
|
||||
|
||||
class dbuser(crudbase):
|
||||
def __init__(self):
|
||||
super().__init__(model=models.User)
|
||||
|
||||
def get_user(self,db: Session, user_id: int) -> schemas.User:
|
||||
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()
|
||||
|
||||
def get_users(self,db: Session) -> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_all())
|
||||
|
||||
def get_users_not_admin(self,db: Session) -> ApiReturnPage[models.Base]:
|
||||
return paginate(db,super().get_by_conditions({"is_superuser":False}))
|
||||
|
||||
def create_user(self,db: Session, user: schemas.UserCreate,userid:int):
|
||||
hashed_password = get_password_hash(user.password)
|
||||
user.hashed_password = hashed_password
|
||||
user.createuserid = userid
|
||||
user.updateuserid = userid
|
||||
del user.password
|
||||
return super().create(db,user)
|
||||
|
||||
def delete_user(self,db: Session, user_id: int):
|
||||
return super().delete(db,user_id)
|
||||
|
||||
|
||||
def edit_user(self,db: Session, user_id:int,user: schemas.UserEdit,userid: int) -> schemas.User:
|
||||
if not user.password is None and user.password != "":
|
||||
user.hashed_password = get_password_hash(user.password)
|
||||
del user.password
|
||||
user.updateuserid = userid
|
||||
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()
|
||||
|
||||
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 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)
|
||||
for roleid in roles:
|
||||
role = dbrole.get(db,roleid)
|
||||
if role not in db_user.roles:
|
||||
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_user_permissions(self,db: Session,user_id: int) -> t.List[schemas.Permission]:
|
||||
permissions =[]
|
||||
db_user = super().get(db,user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
permissions += role.permissions
|
||||
return list(set(permissions))
|
||||
|
||||
userService = dbuser()
|
||||
@@ -1,160 +1,256 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
|
||||
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 app.core.security import chacha20Decrypt
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
create_time = Column(DateTime, default=datetime.now)
|
||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
create_time = Column(DateTime, default=datetime.now(timezone.utc))
|
||||
update_time = Column(DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc))
|
||||
|
||||
|
||||
userrole = Table(
|
||||
"userrole",
|
||||
Base.metadata,
|
||||
Column("userid",Integer,ForeignKey("user.id")),
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
)
|
||||
|
||||
rolepermission = Table(
|
||||
"rolepermission",
|
||||
Base.metadata,
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
Column("permissionid",Integer,ForeignKey("permission.id")),
|
||||
)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
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)
|
||||
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"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
roles = relationship("Role",secondary=userrole,back_populates="users")
|
||||
|
||||
|
||||
class Role(Base):
|
||||
__tablename__ = "role"
|
||||
|
||||
name = mapped_column(String(100))
|
||||
description = mapped_column(String(255))
|
||||
level = mapped_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))
|
||||
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
|
||||
|
||||
|
||||
class App(Base):
|
||||
__tablename__ = "app"
|
||||
|
||||
domainurl = Column(String(200), nullable=False)
|
||||
appname = Column(String(200), nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
version = Column(Integer)
|
||||
updateuser = Column(Integer,ForeignKey("user.id"))
|
||||
user = relationship('User')
|
||||
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"))
|
||||
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"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
|
||||
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "appsetting"
|
||||
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
setting = Column(String(1000))
|
||||
appid = mapped_column(String(100), index=True, nullable=False)
|
||||
setting = mapped_column(String(1000))
|
||||
|
||||
class Kintone(Base):
|
||||
__tablename__ = "kintone"
|
||||
|
||||
type = Column(Integer, index=True, nullable=False)
|
||||
name = Column(String(100), nullable=False)
|
||||
desc = Column(String)
|
||||
content = Column(String)
|
||||
type = mapped_column(Integer, index=True, nullable=False)
|
||||
name = mapped_column(String(100), nullable=False)
|
||||
desc = mapped_column(String)
|
||||
content = mapped_column(String)
|
||||
|
||||
class Action(Base):
|
||||
__tablename__ = "action"
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
class Flow(Base):
|
||||
__tablename__ = "flow"
|
||||
|
||||
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)
|
||||
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"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class FlowHistory(Base):
|
||||
__tablename__ = "flowhistory"
|
||||
|
||||
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)
|
||||
createuser = Column(Integer,ForeignKey("user.id"))
|
||||
version = Column(Integer)
|
||||
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"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class Tenant(Base):
|
||||
__tablename__ = "tenant"
|
||||
|
||||
tenantid = Column(String(100), index=True, nullable=False)
|
||||
name = Column(String(200))
|
||||
licence = Column(String(200))
|
||||
startdate = Column(DateTime)
|
||||
enddate = Column(DateTime)
|
||||
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))
|
||||
|
||||
|
||||
class Domain(Base):
|
||||
__tablename__ = "domain"
|
||||
|
||||
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)
|
||||
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)
|
||||
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"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
owner = relationship('User',foreign_keys=[ownerid])
|
||||
|
||||
|
||||
class UserDomain(Base):
|
||||
__tablename__ = "userdomain"
|
||||
|
||||
userid = Column(Integer,ForeignKey("user.id"))
|
||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||
active = Column(Boolean, default=False)
|
||||
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"))
|
||||
domain = relationship("Domain")
|
||||
user = relationship("User",foreign_keys=[userid])
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "event"
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
class EventAction(Base):
|
||||
__tablename__ = "eventaction"
|
||||
|
||||
eventid = Column(String(100),ForeignKey("event.eventid"))
|
||||
actionid = Column(Integer,ForeignKey("action.id"))
|
||||
eventid = mapped_column(String(100),ForeignKey("event.eventid"))
|
||||
actionid = mapped_column(Integer,ForeignKey("action.id"))
|
||||
|
||||
|
||||
class ErrorLog(Base):
|
||||
__tablename__ = "errorlog"
|
||||
|
||||
title = Column(String(50))
|
||||
location = Column(String(500))
|
||||
content = Column(String(5000))
|
||||
title = mapped_column(String(50))
|
||||
location = mapped_column(String(500))
|
||||
content = mapped_column(String(5000))
|
||||
|
||||
class OperationLog(Base):
|
||||
__tablename__ = "operationlog"
|
||||
|
||||
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))
|
||||
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))
|
||||
user = relationship('User')
|
||||
|
||||
class KintoneFormat(Base):
|
||||
__tablename__ = "kintoneformat"
|
||||
|
||||
name = Column(String(50))
|
||||
startrow =Column(Integer)
|
||||
startcolumn =Column(Integer)
|
||||
typecolumn =Column(Integer)
|
||||
codecolumn =Column(Integer)
|
||||
field = Column(String(5000))
|
||||
trueformat = Column(String(10))
|
||||
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))
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = "category"
|
||||
|
||||
categoryname = Column(String(20))
|
||||
nosort = Column(Integer)
|
||||
categoryname = mapped_column(String(20))
|
||||
nosort = mapped_column(Integer)
|
||||
@@ -8,25 +8,57 @@ class Base(BaseModel):
|
||||
create_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
|
||||
class Permission(BaseModel):
|
||||
id: int
|
||||
menu:str
|
||||
function:str
|
||||
link:str
|
||||
privilege:str
|
||||
|
||||
class RoleBase(BaseModel):
|
||||
id: int
|
||||
name:str
|
||||
description:str
|
||||
level:int
|
||||
|
||||
|
||||
class RoleWithPermission(RoleBase):
|
||||
permissions:t.List[Permission] = []
|
||||
|
||||
class AssignUserRoles(BaseModel):
|
||||
userid:int
|
||||
roleids:t.List[int]
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
first_name: str = None
|
||||
last_name: str = None
|
||||
roles:t.List[RoleBase] = []
|
||||
|
||||
|
||||
class UserOut(BaseModel):
|
||||
id: int
|
||||
email: str
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
first_name: str = None
|
||||
last_name: str = None
|
||||
|
||||
|
||||
class UserOut(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
email:str
|
||||
password: str
|
||||
hashed_password :str = None
|
||||
first_name: str
|
||||
last_name: str
|
||||
is_active:bool
|
||||
is_superuser:bool
|
||||
tenantid:t.Optional[str] = "1"
|
||||
createuserid:t.Optional[int] = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
@@ -34,6 +66,8 @@ class UserCreate(UserBase):
|
||||
|
||||
class UserEdit(UserBase):
|
||||
password: t.Optional[str] = None
|
||||
hashed_password :str = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
@@ -55,12 +89,27 @@ class AppList(Base):
|
||||
appname: str
|
||||
appid:str
|
||||
version:int
|
||||
user:UserOut
|
||||
is_saved:bool
|
||||
versionname: t.Optional[str] = None
|
||||
updateuser: UserOut
|
||||
createuser: UserOut
|
||||
|
||||
class AppVersion(BaseModel):
|
||||
|
||||
class AppVersion(Base):
|
||||
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
|
||||
@@ -106,9 +155,11 @@ class Action(BaseModel):
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
|
||||
class FlowBase(BaseModel):
|
||||
class FlowIn(BaseModel):
|
||||
flowid: str
|
||||
# domainurl:str
|
||||
appid: str
|
||||
appname:str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
@@ -125,28 +176,63 @@ class Flow(Base):
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
|
||||
class DomainBase(BaseModel):
|
||||
class DomainIn(BaseModel):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
kintonepwd: t.Optional[str] = None
|
||||
is_active: bool
|
||||
createuserid:t.Optional[int] = None
|
||||
updateuserid:t.Optional[int] = None
|
||||
ownerid:t.Optional[int] = None
|
||||
|
||||
def encrypt_kintonepwd(self):
|
||||
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
||||
self.kintonepwd = encrypted_pwd
|
||||
|
||||
class DomainOut(BaseModel):
|
||||
id: int
|
||||
tenantid: str
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
is_active: bool
|
||||
ownerid:int
|
||||
|
||||
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
|
||||
name: str
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
is_active: bool
|
||||
updateuser:UserOut
|
||||
owner:UserOut
|
||||
|
||||
class ConfigDict:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class Event(Base):
|
||||
id: int
|
||||
category: str
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from sqlalchemy.orm import sessionmaker, declarative_base, Session
|
||||
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)
|
||||
|
||||
# Dependency
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
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()
|
||||
try:
|
||||
yield db
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
db.close()
|
||||
|
||||
def get_user_db(database_url: str):
|
||||
database = Database(database_url)
|
||||
db = database.get_db()
|
||||
return db
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
from fastapi import FastAPI, Depends
|
||||
from fastapi_pagination import add_pagination
|
||||
from starlette.requests import Request
|
||||
import uvicorn
|
||||
from app.api.api_v1.routers.kintone import kinton_router
|
||||
@@ -7,21 +8,29 @@ 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
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import logging
|
||||
from app.core.apiexception import APIException, writedblog
|
||||
from app.core.common import ApiReturnError
|
||||
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):
|
||||
startup_event()
|
||||
yield
|
||||
|
||||
app = FastAPI(
|
||||
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
|
||||
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api",lifespan=lifespan
|
||||
)
|
||||
|
||||
origins = [
|
||||
@@ -36,6 +45,10 @@ app.add_middleware(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.add_middleware(LoggingMiddleware)
|
||||
|
||||
add_pagination(app)
|
||||
|
||||
# @app.middleware("http")
|
||||
# async def db_session_middleware(request: Request, call_next):
|
||||
# request.state.db = SessionLocal()
|
||||
@@ -43,12 +56,11 @@ app.add_middleware(
|
||||
# request.state.db.close()
|
||||
# return response
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
def startup_event():
|
||||
log_dir="log"
|
||||
if not os.path.exists(log_dir):
|
||||
os.makedirs(log_dir)
|
||||
|
||||
|
||||
logger = logging.getLogger("uvicorn.access")
|
||||
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
|
||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
||||
@@ -60,7 +72,7 @@ async def api_exception_handler(request: Request, exc: APIException):
|
||||
loop.run_in_executor(None,writedblog,exc)
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content={"detail": f"{exc.detail}"},
|
||||
content= ApiReturnError(msg = f"{exc.detail}").model_dump(),
|
||||
)
|
||||
|
||||
@app.get("/api/v1")
|
||||
|
||||
252
backend/app/tests/conftest.py
Normal file
252
backend/app/tests/conftest.py
Normal file
@@ -0,0 +1,252 @@
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
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.main import app
|
||||
|
||||
|
||||
from app.core import security
|
||||
import jwt
|
||||
|
||||
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")
|
||||
def test_db():
|
||||
connection = engine.connect()
|
||||
transaction = connection.begin()
|
||||
test_session = test_session_maker(bind=connection)
|
||||
yield test_session
|
||||
test_session.close()
|
||||
transaction.rollback()
|
||||
#transaction.commit()
|
||||
connection.close()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_client(test_db):
|
||||
def get_test_db():
|
||||
try:
|
||||
yield test_db
|
||||
finally:
|
||||
test_db.close()
|
||||
|
||||
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:
|
||||
# return "securepassword"
|
||||
|
||||
|
||||
# def get_password_hash() -> str:
|
||||
# """
|
||||
# Password hashing can be expensive so a mock will be much faster
|
||||
# """
|
||||
# return "supersecrethash"
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def test_user(test_db) -> models.User:
|
||||
# """
|
||||
# Make a test user in the database
|
||||
# """
|
||||
|
||||
# user = models.User(
|
||||
# email="fake@email.com",
|
||||
# hashed_password=get_password_hash(),
|
||||
# is_active=True,
|
||||
# )
|
||||
# test_db.add(user)
|
||||
# test_db.commit()
|
||||
# return user
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def test_superuser(test_db) -> models.User:
|
||||
# """
|
||||
# Superuser for testing
|
||||
# """
|
||||
|
||||
# user = models.User(
|
||||
# email="fakeadmin@email.com",
|
||||
# hashed_password=get_password_hash(),
|
||||
# is_superuser=True,
|
||||
# )
|
||||
# test_db.add(user)
|
||||
# test_db.commit()
|
||||
# return user
|
||||
|
||||
|
||||
# def verify_password_mock(first: str, second: str) -> bool:
|
||||
# return True
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def user_token_headers(
|
||||
# client: TestClient, test_user, test_password, monkeypatch
|
||||
# ) -> t.Dict[str, str]:
|
||||
# monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
# login_data = {
|
||||
# "username": test_user.email,
|
||||
# "password": test_password,
|
||||
# }
|
||||
# r = client.post("/api/token", data=login_data)
|
||||
# tokens = r.json()
|
||||
# a_token = tokens["access_token"]
|
||||
# headers = {"Authorization": f"Bearer {a_token}"}
|
||||
# return headers
|
||||
|
||||
|
||||
# @pytest.fixture
|
||||
# def superuser_token_headers(
|
||||
# client: TestClient, test_superuser, test_password, monkeypatch
|
||||
# ) -> t.Dict[str, str]:
|
||||
# monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
# login_data = {
|
||||
# "username": test_superuser.email,
|
||||
# "password": test_password,
|
||||
# }
|
||||
# r = client.post("/api/token", data=login_data)
|
||||
# tokens = r.json()
|
||||
# a_token = tokens["access_token"]
|
||||
# headers = {"Authorization": f"Bearer {a_token}"}
|
||||
# return headers
|
||||
5
backend/app/tests/pytest.ini
Normal file
5
backend/app/tests/pytest.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[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
|
||||
11
backend/app/tests/test_auth.py
Normal file
11
backend/app/tests/test_auth.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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"]
|
||||
|
||||
175
backend/app/tests/test_domain.py
Normal file
175
backend/app/tests/test_domain.py
Normal file
@@ -0,0 +1,175 @@
|
||||
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"]
|
||||
@@ -1,4 +1,6 @@
|
||||
def test_read_main(client):
|
||||
response = client.get("/api/v1")
|
||||
|
||||
|
||||
def test_read_main(test_client):
|
||||
response = test_client.get("/api/v1")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"message": "Hello World"}
|
||||
assert response.json() == {"message": "success"}
|
||||
|
||||
156
backend/app/tests/test_user.py
Normal file
156
backend/app/tests/test_user.py
Normal file
@@ -0,0 +1,156 @@
|
||||
|
||||
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
|
||||
121
backend/app/tests/test_user_app.py
Normal file
121
backend/app/tests/test_user_app.py
Normal file
@@ -0,0 +1,121 @@
|
||||
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
|
||||
9
backend/app/tests/test_user_kintone.py
Normal file
9
backend/app/tests/test_user_kintone.py
Normal file
@@ -0,0 +1,9 @@
|
||||
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
|
||||
@@ -1,169 +0,0 @@
|
||||
import pytest
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy_utils import database_exists, create_database, drop_database
|
||||
from fastapi.testclient import TestClient
|
||||
import typing as t
|
||||
|
||||
from app.core import config, security
|
||||
from app.db.session import Base, get_db
|
||||
from app.db import models
|
||||
from app.main import app
|
||||
|
||||
|
||||
def get_test_db_url() -> str:
|
||||
return f"{config.SQLALCHEMY_DATABASE_URI}_test"
|
||||
|
||||
|
||||
@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()
|
||||
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()
|
||||
trans.rollback()
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def create_test_db():
|
||||
"""
|
||||
Create a test database and use it for the whole test session.
|
||||
"""
|
||||
|
||||
test_db_url = get_test_db_url()
|
||||
|
||||
# Create the test database
|
||||
assert not database_exists(
|
||||
test_db_url
|
||||
), "Test database already exists. Aborting tests."
|
||||
create_database(test_db_url)
|
||||
test_engine = create_engine(test_db_url)
|
||||
Base.metadata.create_all(test_engine)
|
||||
|
||||
# Run the tests
|
||||
yield
|
||||
|
||||
# Drop the test database
|
||||
drop_database(test_db_url)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(test_db):
|
||||
"""
|
||||
Get a TestClient instance that reads/write to the test database.
|
||||
"""
|
||||
|
||||
def get_test_db():
|
||||
yield test_db
|
||||
|
||||
app.dependency_overrides[get_db] = get_test_db
|
||||
|
||||
yield TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_password() -> str:
|
||||
return "securepassword"
|
||||
|
||||
|
||||
def get_password_hash() -> str:
|
||||
"""
|
||||
Password hashing can be expensive so a mock will be much faster
|
||||
"""
|
||||
return "supersecrethash"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_user(test_db) -> models.User:
|
||||
"""
|
||||
Make a test user in the database
|
||||
"""
|
||||
|
||||
user = models.User(
|
||||
email="fake@email.com",
|
||||
hashed_password=get_password_hash(),
|
||||
is_active=True,
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_superuser(test_db) -> models.User:
|
||||
"""
|
||||
Superuser for testing
|
||||
"""
|
||||
|
||||
user = models.User(
|
||||
email="fakeadmin@email.com",
|
||||
hashed_password=get_password_hash(),
|
||||
is_superuser=True,
|
||||
)
|
||||
test_db.add(user)
|
||||
test_db.commit()
|
||||
return user
|
||||
|
||||
|
||||
def verify_password_mock(first: str, second: str) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user_token_headers(
|
||||
client: TestClient, test_user, test_password, monkeypatch
|
||||
) -> t.Dict[str, str]:
|
||||
monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
login_data = {
|
||||
"username": test_user.email,
|
||||
"password": test_password,
|
||||
}
|
||||
r = client.post("/api/token", data=login_data)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
headers = {"Authorization": f"Bearer {a_token}"}
|
||||
return headers
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def superuser_token_headers(
|
||||
client: TestClient, test_superuser, test_password, monkeypatch
|
||||
) -> t.Dict[str, str]:
|
||||
monkeypatch.setattr(security, "verify_password", verify_password_mock)
|
||||
|
||||
login_data = {
|
||||
"username": test_superuser.email,
|
||||
"password": test_password,
|
||||
}
|
||||
r = client.post("/api/token", data=login_data)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
headers = {"Authorization": f"Bearer {a_token}"}
|
||||
return headers
|
||||
@@ -29,3 +29,11 @@ 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
1958
document/ALCKintone_20231208.drawio
Normal file
1958
document/ALCKintone_20231208.drawio
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
211
document/ナレッジ構成.drawio
Normal file
211
document/ナレッジ構成.drawio
Normal file
@@ -0,0 +1,211 @@
|
||||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" version="24.9.2">
|
||||
<diagram name="ページ1" id="oKiyF3b1qzm0IX1SNAWJ">
|
||||
<mxGraphModel dx="1395" dy="1078" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="0" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-73" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d0cee2;strokeColor=#56517e;" vertex="1" parent="1">
|
||||
<mxGeometry x="92" y="645" width="720" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-71" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="110" y="605" width="720" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-53" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcccc;strokeColor=#36393d;" vertex="1" parent="1">
|
||||
<mxGeometry x="140" y="570" width="720" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-52" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#cdeb8b;strokeColor=#36393d;" vertex="1" parent="1">
|
||||
<mxGeometry x="170" y="530" width="720" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-51" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#ffcc99;strokeColor=#36393d;" vertex="1" parent="1">
|
||||
<mxGeometry x="210" y="490" width="720" height="180" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-49" value="" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" vertex="1" parent="1">
|
||||
<mxGeometry x="90" y="20" width="1080" height="290" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-10" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-1" target="Clf12EPbcqlM1huzJpPN-9">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-1" value="タスク開始" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="215" width="90" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-9">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-47" value="DBからクロールのタスクを取得する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-45">
|
||||
<mxGeometry x="0.2449" y="53" relative="1" as="geometry">
|
||||
<mxPoint x="17" y="-44" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-79" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-8" target="Clf12EPbcqlM1huzJpPN-78">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-8" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#cce5ff;strokeColor=#36393d;" vertex="1" parent="1">
|
||||
<mxGeometry x="570" y="-110" width="100" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-9" value="<div style="background-color: rgb(255, 255, 254); font-family: Consolas, &quot;Courier New&quot;, monospace; font-size: 14px; line-height: 19px; white-space: pre;"><span style="">タスクスケジューラ</span></div>" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="217.5" width="140" height="45" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;curved=1;startArrow=block;startFill=1;endArrow=none;endFill=0;dashed=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-9">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-18" value="タスクの割り当て" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-17">
|
||||
<mxGeometry x="0.04" relative="1" as="geometry">
|
||||
<mxPoint x="-18" y="30" as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.75;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-31">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-15" value="<b>RabbitMQ</b>" style="whiteSpace=wrap;html=1;rounded=1;fillColor=#1ba1e2;fontColor=#ffffff;strokeColor=#006EAF;" vertex="1" parent="1">
|
||||
<mxGeometry x="510" y="380" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-22" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-19" target="Clf12EPbcqlM1huzJpPN-21">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-19" value="MQコマンドの受信" style="rounded=0;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="250" y="560" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-15" target="Clf12EPbcqlM1huzJpPN-19">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-21" target="Clf12EPbcqlM1huzJpPN-23">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-21" value="データ収集" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="490" y="560" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-15">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-23" value="結果をMQメッセージを変換" style="whiteSpace=wrap;html=1;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="740" y="560" width="170" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;dashed=1;curved=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-26" target="Clf12EPbcqlM1huzJpPN-31">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="980" y="390" />
|
||||
<mxPoint x="980" y="350" />
|
||||
<mxPoint x="745" y="350" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-26" value="データ収集結果<br>一時保存" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#eeeeee;strokeColor=#36393d;" vertex="1" parent="1">
|
||||
<mxGeometry x="970" y="390" width="100" height="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-27" value="タスクコマンド" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="1">
|
||||
<mxGeometry x="340" y="450" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-23" target="Clf12EPbcqlM1huzJpPN-26">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-29" value="コンテンツなど大きい情報を一時保存" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="950" y="500" width="230" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-30" value="収集結果通知MQ" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="740" y="450" width="110" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-37" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-31" target="Clf12EPbcqlM1huzJpPN-36">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-31" value="メッセージキューから<div>データ受信</div>" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="677" y="212.5" width="150" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-34" value="MQのキーで収集結果を取得" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="760" y="350" width="170" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-39" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-36" target="Clf12EPbcqlM1huzJpPN-38">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-36" value="<div style="background-color: rgb(255, 255, 254); font-family: Consolas, &quot;Courier New&quot;, monospace; line-height: 19px; white-space: pre;"><span>データクリーニング<br>と重複排除</span></div>" style="whiteSpace=wrap;html=1;rounded=1;fontColor=default;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="890" y="207.5" width="148" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-43" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-42">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-38" value="データベースに保存" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="904" y="60" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-42" target="Clf12EPbcqlM1huzJpPN-31">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-42" value="収集結果からサブ項目を抽出" style="whiteSpace=wrap;html=1;fontSize=11;rounded=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="692" y="60" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;curved=1;dashed=1;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-38" target="Clf12EPbcqlM1huzJpPN-8">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-48" value="クロール結果をDBに登録する" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="Clf12EPbcqlM1huzJpPN-46">
|
||||
<mxGeometry x="0.1149" y="13" relative="1" as="geometry">
|
||||
<mxPoint as="offset" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-50" value="<b>メインプログラム</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="540" y="20" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-54" value="<b>クローラー サブプログラム</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="485" y="490" width="185" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-55" value="<b>SharePointクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="220" y="640" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-60" value="MQ情報" style="swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;align=center;fontSize=14;fillColor=#dae8fc;strokeColor=#6c8ebf;gradientColor=#7ea6e0;" vertex="1" parent="1">
|
||||
<mxGeometry x="1024" y="630" width="160" height="236" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-61" value="MQキー" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="26" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-70" value="クローラーID" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="56" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-62" value="分類" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="86" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-63" value="タイトル" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="116" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-64" value="URL" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="146" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-65" value="サブ項目" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="176" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-66" value="コンテンツ" style="text;strokeColor=none;fillColor=none;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;fontSize=12;whiteSpace=wrap;html=1;" vertex="1" parent="Clf12EPbcqlM1huzJpPN-60">
|
||||
<mxGeometry y="206" width="160" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-67" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;rounded=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-60" target="Clf12EPbcqlM1huzJpPN-23">
|
||||
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||
<mxPoint x="930" y="840" as="sourcePoint" />
|
||||
<mxPoint x="980" y="790" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-68" value="<b>OneNodeクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="180" y="680" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-69" value="<b>EIM クローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="150" y="720" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-72" value="<b>Coredasuクローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="120" y="755" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-74" value="<b>その他クローラー</b>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="92" y="795" width="130" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-75" value="PostGre SQL" style="text;html=1;align=center;verticalAlign=middle;resizable=0;points=[];autosize=1;strokeColor=none;fillColor=none;" vertex="1" parent="1">
|
||||
<mxGeometry x="585" y="-40" width="90" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-76" value="再帰的な処理" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="1030" y="140" width="80" height="50" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-77" value="NEO4J" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
||||
<mxGeometry x="570" y="-390" width="100" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-78" value="データ抽出" style="rounded=1;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="560" y="-250" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Clf12EPbcqlM1huzJpPN-80" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="Clf12EPbcqlM1huzJpPN-78" target="Clf12EPbcqlM1huzJpPN-77">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "k-tune",
|
||||
"version": "0.2.0",
|
||||
"version": "2.0.0 Beta",
|
||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||
"productName": "k-tune | kintoneジェネレーター",
|
||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||
|
||||
@@ -37,7 +37,8 @@ module.exports = configure(function (/* ctx */) {
|
||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||
boot: [
|
||||
'axios',
|
||||
'error-handler'
|
||||
'error-handler',
|
||||
'permissions'
|
||||
],
|
||||
|
||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||
@@ -104,7 +105,7 @@ module.exports = configure(function (/* ctx */) {
|
||||
config: {},
|
||||
|
||||
// iconSet: 'material-icons', // Quasar icon set
|
||||
// lang: 'en-US', // Quasar language pack
|
||||
lang: 'ja', // 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),
|
||||
@@ -115,7 +116,8 @@ module.exports = configure(function (/* ctx */) {
|
||||
|
||||
// Quasar plugins
|
||||
plugins: [
|
||||
'Notify'
|
||||
'Notify',
|
||||
'Dialog'
|
||||
]
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import {router} from 'src/router';
|
||||
import { IResponse } from 'src/types/BaseTypes';
|
||||
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
75
frontend/src/boot/permissions.ts
Normal file
75
frontend/src/boot/permissions.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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';
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
filter:String
|
||||
},
|
||||
emits:[
|
||||
"clearFilter"
|
||||
'clearFilter'
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
const isLoaded=ref(false);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1,64 +1,58 @@
|
||||
<template>
|
||||
<div class="q-px-xs">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<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;">
|
||||
<template v-slot:body-cell-description="props">
|
||||
<q-td :props="props">
|
||||
<q-scroll-area class="description-cell">
|
||||
<div v-html="props.row.description"></div>
|
||||
</q-scroll-area>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
<detail-field-table
|
||||
detailField="description"
|
||||
:name="name"
|
||||
:type="type"
|
||||
:filter="filter"
|
||||
:columns="columns"
|
||||
:fetchData="fetchApps"
|
||||
@update:selected="(item) => { selected = item }"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { ref, PropType } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import DetailFieldTable from './dialog/DetailFieldTable.vue';
|
||||
|
||||
interface IAppDisplay {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AppSelectBox',
|
||||
components: {
|
||||
DetailFieldTable
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
updateSelectApp: {
|
||||
type: Function
|
||||
filterInitRowsFunc: {
|
||||
type: Function as PropType<(app: IAppDisplay) => boolean>,
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const selected = ref<IAppDisplay[]>([]);
|
||||
const columns = [
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true },
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true, sort: (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10) },
|
||||
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
|
||||
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
||||
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
||||
]
|
||||
const isLoaded = ref(false);
|
||||
const rows: any[] = reactive([]);
|
||||
const selected = ref([])
|
||||
];
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||
props.updateSelectApp(selected.value[0])
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
api.get('api/v1/allapps').then(res => {
|
||||
res.data.apps.forEach((item: any) => {
|
||||
rows.push({
|
||||
id: item.appId,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createdate: dateFormat(item.createdAt)
|
||||
});
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
});
|
||||
const fetchApps = async () => {
|
||||
const res = await api.get('api/v1/allapps');
|
||||
return res.data.apps.map((item: any) => ({
|
||||
id: item.appId,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createdate: dateFormat(item.createdAt)
|
||||
})).filter(app => !props.filterInitRowsFunc || props.filterInitRowsFunc(app));
|
||||
};
|
||||
|
||||
const dateFormat = (dateStr: string) => {
|
||||
const date = new Date(dateStr);
|
||||
@@ -70,31 +64,13 @@ export default {
|
||||
const minutes = pad(date.getMinutes());
|
||||
const seconds = pad(date.getSeconds());
|
||||
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
selected,
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
rowsPerPage: 10
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.description-cell {
|
||||
height: 60px;
|
||||
width: 300px;
|
||||
max-height: 60px;
|
||||
max-width: 300px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
min-height: 300px;
|
||||
min-width: 400px;
|
||||
}
|
||||
</style>
|
||||
fetchApps,
|
||||
selected
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
//条件式をコピーする
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
//グループ化解散
|
||||
|
||||
@@ -1,98 +1,105 @@
|
||||
<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 { createUploaderComponent, useQuasar } from 'quasar';
|
||||
import { 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ファイルを選択してください。`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* ファイルアップロード成功時の処理
|
||||
*/
|
||||
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);
|
||||
}
|
||||
const $q = useQuasar();
|
||||
const authStore = useAuthStore();
|
||||
const emit = defineEmits(['uploaded']);
|
||||
|
||||
/**
|
||||
* 例外発生時、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;
|
||||
}
|
||||
}
|
||||
interface Props {
|
||||
title?: string;
|
||||
uploadUrl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 headers = ref([
|
||||
{ name: 'Authorization', value: 'Bearer ' + authStore.token },
|
||||
]);
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
uploadUrl:string;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: '設計書から導入する(Excel)',
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`,
|
||||
});
|
||||
|
||||
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`
|
||||
/**
|
||||
* ファイルアップロードを拒否する時の処理
|
||||
* @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ファイルを選択してください。',
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
/**
|
||||
* ファイルアップロード成功時の処理
|
||||
*/
|
||||
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>
|
||||
|
||||
@@ -1,37 +1,78 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||
<q-table :loading="loading" :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows">
|
||||
<template v-slot:body-cell-name="p">
|
||||
<q-td class="content-box flex justify-between items-center" :props="p">
|
||||
{{ p.row.name }}
|
||||
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
|
||||
<q-badge v-if="p.row.id == currentDomainId" color="primary">現在</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref,onMounted,reactive } from 'vue'
|
||||
import { ref,onMounted,reactive, computed } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
|
||||
export default {
|
||||
name: 'DomainSelect',
|
||||
props: {
|
||||
name: String,
|
||||
type: String
|
||||
type: String,
|
||||
filterInitRowsFunc: {
|
||||
type: Function,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const columns = [
|
||||
{ name: 'id'},
|
||||
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
||||
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
||||
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
||||
]
|
||||
const rows = reactive([])
|
||||
onMounted( () => {
|
||||
api.get(`api/domains/1`).then(res =>{
|
||||
res.data.forEach((item) =>
|
||||
{
|
||||
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
||||
}
|
||||
)
|
||||
});
|
||||
setup(props) {
|
||||
const authStore = useAuthStore();
|
||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
||||
const loading = ref(true);
|
||||
const inactiveRowClass = (row) => row.domainActive ? '' : 'inactive-row';
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||
{ name: 'name', align: 'left', label: 'ドメイン', field: 'name', sortable: true, 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: row => row.owner.fullName, align: 'left', classes: inactiveRowClass },
|
||||
]
|
||||
const rows = reactive([]);
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
api.get('api/domains').then(res =>{
|
||||
res.data.data.forEach((data) => {
|
||||
const item = {
|
||||
id: data.id,
|
||||
tenantid: data.tenantid,
|
||||
domainActive: data.is_active,
|
||||
name: data.name,
|
||||
url: data.url,
|
||||
user: data.kintoneuser,
|
||||
owner: {
|
||||
id: data.owner.id,
|
||||
firstName: data.owner.first_name,
|
||||
lastName: data.owner.last_name,
|
||||
fullNameSearch: (data.owner.last_name + data.owner.first_name).toLowerCase(),
|
||||
fullName: data.owner.last_name + ' ' + data.owner.first_name,
|
||||
email: data.owner.email,
|
||||
isActive: data.owner.is_active,
|
||||
isSuperuser: data.owner.is_superuser,
|
||||
}
|
||||
}
|
||||
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(item)) {
|
||||
return;
|
||||
}
|
||||
rows.push(item);
|
||||
})
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
return {
|
||||
loading,
|
||||
currentDomainId,
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
@@ -40,3 +81,11 @@ export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.q-table td.inactive-row {
|
||||
color: #aaa;
|
||||
}
|
||||
.q-table .content-box {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
</style>
|
||||
@@ -7,54 +7,26 @@
|
||||
icon="share"
|
||||
size="md"
|
||||
:label="userStore.currentDomain.domainName"
|
||||
:disable-dropdown="isUnclickable"
|
||||
:dropdown-icon="isUnclickable ? 'none' : ''"
|
||||
:disable="isUnclickable"
|
||||
:disable-dropdown="true"
|
||||
dropdown-icon="none"
|
||||
:disable="true"
|
||||
>
|
||||
<q-list>
|
||||
<q-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="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" >
|
||||
import { IDomainInfo } from 'src/types/ActionTypes';
|
||||
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const userStore = useAuthStore();
|
||||
const route = useRoute()
|
||||
const domains = ref<IDomainInfo[]>([]);
|
||||
(async ()=>{
|
||||
domains.value = await userStore.getUserDomains();
|
||||
})();
|
||||
|
||||
const isUnclickable = computed(()=>{
|
||||
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
|
||||
});
|
||||
|
||||
|
||||
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-btn.disabled.customized-disabled-btn * {
|
||||
cursor: default !important;
|
||||
.q-icon.q-btn-dropdown__arrow {
|
||||
display: none;
|
||||
}
|
||||
* {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<template>
|
||||
<q-item
|
||||
v-permissions="permission"
|
||||
clickable
|
||||
tag="a"
|
||||
:target="target?target:'_blank'"
|
||||
:href="link"
|
||||
:disable="disable"
|
||||
v-if="!isSeparator"
|
||||
>
|
||||
<q-item-section
|
||||
@@ -33,6 +35,8 @@ export interface EssentialLinkProps {
|
||||
icon?: string;
|
||||
isSeparator?: boolean;
|
||||
target?:string;
|
||||
disable?:boolean;
|
||||
permission?: string|null;
|
||||
}
|
||||
withDefaults(defineProps<EssentialLinkProps>(), {
|
||||
caption: '',
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
24
frontend/src/components/ShareDomain/RoleLabel.vue
Normal file
24
frontend/src/components/ShareDomain/RoleLabel.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<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>
|
||||
276
frontend/src/components/ShareDomain/ShareDomainDialog.vue
Normal file
276
frontend/src/components/ShareDomain/ShareDomainDialog.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<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-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
|
||||
dense
|
||||
v-model="canSharedUserFilter"
|
||||
use-input
|
||||
input-debounce="0"
|
||||
: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>
|
||||
|
||||
<template v-slot:after>
|
||||
<q-btn :disable="!canSharedUserFilter" :loading="addLoading" label="付与" color="primary" @click="shareTo(canSharedUserFilter as IUserDisplayWithShareRole)" />
|
||||
</template>
|
||||
|
||||
<template v-slot:option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<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>
|
||||
|
||||
<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)" />
|
||||
</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-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } 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>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const addLoading = ref(false);
|
||||
const loading = ref(true);
|
||||
const visible = ref(props.modelValue);
|
||||
|
||||
const allUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const sharedUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const sharedUsersIdSet = new Set<number>();
|
||||
|
||||
const canSharedUsers = ref<IUserDisplayWithShareRole[]>([]);
|
||||
const canSharedUserFilter = ref<IUserDisplayWithShareRole>();
|
||||
const canSharedUserFilteredOptions = ref<IUserDisplayWithShareRole[]>([]);
|
||||
|
||||
const filterFn = (val:string, update: (cb: () => void) => void) => {
|
||||
update(() => {
|
||||
if (val === '') {
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||
return;
|
||||
}
|
||||
const needle = val.toLowerCase();
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value.filter(v =>
|
||||
v.email.toLowerCase().indexOf(needle) > -1 || v.fullNameSearch.toLowerCase().indexOf(needle) > -1);
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
async (newValue) => {
|
||||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(newValue) => {
|
||||
emit('update:modelValue', newValue);
|
||||
}
|
||||
);
|
||||
|
||||
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) => {
|
||||
addLoading.value = true;
|
||||
loading.value = true;
|
||||
await props.shareApi(user, props.domain);
|
||||
await loadShared();
|
||||
canSharedUserFilter.value = undefined;
|
||||
loading.value = false;
|
||||
addLoading.value = false;
|
||||
}
|
||||
|
||||
const removeShareTo = async (user: IUserDisplayWithShareRole) => {
|
||||
loading.value = true;
|
||||
user.isRemoving = true;
|
||||
await props.removeSharedApi(user, props.domain);
|
||||
if (isCurrentDomain()) {
|
||||
await authStore.loadCurrentDomain();
|
||||
}
|
||||
await loadShared();
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
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 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);
|
||||
|
||||
canSharedUsers.value = allUsers.value.filter((item) => !sharedUsersIdSet.has(item.id));
|
||||
canSharedUserFilteredOptions.value = canSharedUsers.value;
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function isOwner(userId: number) {
|
||||
return userId === props.domain?.owner?.id
|
||||
}
|
||||
|
||||
function isManager(userId: number) {
|
||||
return false // TODO
|
||||
}
|
||||
|
||||
const getUsers = async () => {
|
||||
loading.value = true;
|
||||
const result = await api.get('api/v1/users');
|
||||
allUsers.value = result.data.data.map(itemToDisplay);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const itemToDisplay = (item: IUser) => {
|
||||
return {
|
||||
id: item.id,
|
||||
firstName: item.first_name,
|
||||
lastName: item.last_name,
|
||||
fullNameSearch: (item.last_name + item.first_name).toLowerCase(),
|
||||
fullName: item.last_name + ' ' + item.first_name,
|
||||
email: item.email,
|
||||
isSuperuser: item.is_superuser,
|
||||
isActive: item.is_active,
|
||||
role: 0,
|
||||
} as IUserDisplayWithShareRole
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dialog-content {
|
||||
width: 700px !important;
|
||||
max-width: 80vw !important;
|
||||
max-height: 80vh;
|
||||
.q-select {
|
||||
min-width: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
63
frontend/src/components/ShareDomain/ShareManageDialog.vue
Normal file
63
frontend/src/components/ShareDomain/ShareManageDialog.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<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>
|
||||
58
frontend/src/components/ShareDomain/ShareUsageDialog.vue
Normal file
58
frontend/src/components/ShareDomain/ShareUsageDialog.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<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>
|
||||
53
frontend/src/components/ShareDomain/SharingUserList.vue
Normal file
53
frontend/src/components/ShareDomain/SharingUserList.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<q-table :rows="users" :filter="filter" dense :columns="columns" row-key="id" :loading="loading" :pagination="pagination">
|
||||
<template v-slot:top>
|
||||
<div class="h6 text-weight-bold">{{props.title}}</div>
|
||||
<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-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>
|
||||
</q-table>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, PropType } from 'vue';
|
||||
import { IUserDisplay } from '../../types/UserTypes';
|
||||
|
||||
const props = defineProps({
|
||||
users: {
|
||||
type: Array as PropType<IUserDisplay[]>,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: String
|
||||
});
|
||||
|
||||
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 });
|
||||
</script>
|
||||
@@ -4,7 +4,7 @@
|
||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||
<q-toolbar class="bg-grey-4">
|
||||
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
||||
<q-space></q-space>
|
||||
<q-space v-if="$slots.toolbar"></q-space>
|
||||
<slot name="toolbar"></slot>
|
||||
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
||||
</q-toolbar>
|
||||
@@ -12,8 +12,8 @@
|
||||
<slot></slot>
|
||||
</q-card-section>
|
||||
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
||||
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||
<q-btn flat :label="okBtnLabel || '確定'" :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>
|
||||
</q-dialog>
|
||||
@@ -30,13 +30,20 @@ export default {
|
||||
height:String,
|
||||
minWidth:String,
|
||||
minHeight:String,
|
||||
okBtnLabel:String,
|
||||
okBtnLoading:Boolean,
|
||||
okBtnAutoClose:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'close'
|
||||
'close',
|
||||
'update:visible'
|
||||
],
|
||||
setup(props, context) {
|
||||
const CloseDialogue = (val) => {
|
||||
|
||||
92
frontend/src/components/TableActionMenu.vue
Normal file
92
frontend/src/components/TableActionMenu.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<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 }">
|
||||
<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-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>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, getCurrentInstance } 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;
|
||||
}
|
||||
interface Separator {
|
||||
separator: boolean;
|
||||
}
|
||||
type MenuItem = Action | Separator;
|
||||
|
||||
export default {
|
||||
name: 'TableActionMenu',
|
||||
props: {
|
||||
row: {
|
||||
type: Object as PropType<IDomainOwnerDisplay>,
|
||||
required: true
|
||||
},
|
||||
maxWidth: {
|
||||
type: String,
|
||||
default: '150px'
|
||||
},
|
||||
minWidth: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
actions: {
|
||||
type: Array as PropType<MenuItem[]>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
.q-table tr:hover > td:last-child .action-menu:not([disabled]) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
</style>
|
||||
74
frontend/src/components/UserDomain/DomainCard.vue
Normal file
74
frontend/src/components/UserDomain/DomainCard.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<q-card :class="['domain-card', item.id == activeId ? 'default': '']">
|
||||
<q-card-section>
|
||||
<div class="row no-wrap">
|
||||
<div class="col">
|
||||
<div class="text-h6 ellipsis">{{ item.name }}</div>
|
||||
<div class="text-subtitle2">{{ item.url }}</div>
|
||||
</div>
|
||||
<div 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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap">
|
||||
<div class="col">
|
||||
<div class="text-grey-7 text-caption text-weight-medium">
|
||||
アカウント
|
||||
</div>
|
||||
<div class="smaller-font-size">{{ item.user }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<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>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator v-if="$slots.actions" />
|
||||
<slot name="actions" :item="item"></slot>
|
||||
</q-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, computed } from 'vue';
|
||||
import { IDomainOwnerDisplay } from 'src/types/DomainTypes';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const props = defineProps<{
|
||||
item: IDomainOwnerDisplay;
|
||||
activeId: number;
|
||||
}>();
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const isOwnerFunc = computed(() => (ownerId: string) => {
|
||||
return ownerId == authStore.userId;
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.domain-card.default {
|
||||
box-shadow: 0 6px 6px -3px rgba(0, 0, 0, 0.2),
|
||||
0 10px 14px 1px rgba(0, 0, 0, 0.14),
|
||||
0 4px 18px 3px rgba(0, 0, 0, 0.12),
|
||||
inset 0 0 0px 2px #1976D2;
|
||||
}
|
||||
.domain-card {
|
||||
width: 22rem;
|
||||
word-break: break-word;
|
||||
|
||||
.smaller-font-size {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
40
frontend/src/components/UserInfoButton.vue
Normal file
40
frontend/src/components/UserInfoButton.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<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>
|
||||
@@ -9,8 +9,8 @@ import { api } from 'boot/axios';
|
||||
const props = defineProps<{filter:string}>()
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '苗字', field: 'lastName', 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 },
|
||||
];
|
||||
|
||||
@@ -23,8 +23,8 @@ defineExpose({
|
||||
})
|
||||
const getUsers = async (filter = () => true) => {
|
||||
loading.value = true;
|
||||
const result = await api.get(`api/v1/users`);
|
||||
rows.value = result.data.map((item) => {
|
||||
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;
|
||||
|
||||
105
frontend/src/components/dialog/DetailFieldTable.vue
Normal file
105
frontend/src/components/dialog/DetailFieldTable.vue
Normal file
@@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div class="q-px-xs">
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
<div v-html="props.row[detailField]"></div>
|
||||
</q-scroll-area>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, PropType } from 'vue'
|
||||
|
||||
interface IRow {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DetailFieldTable',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
detailField: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
columns: {
|
||||
type: Array as PropType<any[]>,
|
||||
required: true
|
||||
},
|
||||
fetchData: {
|
||||
type: Function as PropType<() => Promise<IRow[]>>,
|
||||
required: true
|
||||
},
|
||||
sortBy: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
sortDesc: {
|
||||
type: Boolean,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
emits: ['update:selected'],
|
||||
setup(props, { emit }) {
|
||||
const isLoaded = ref(false);
|
||||
const rows = reactive<IRow[]>([]);
|
||||
const selected = ref([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await props.fetchData();
|
||||
rows.push(...data);
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
const emitSelected = (selectedItems: any[]) => {
|
||||
emit('update:selected', selectedItems);
|
||||
};
|
||||
|
||||
return {
|
||||
rows,
|
||||
selected,
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
sortBy: props.sortBy || undefined,
|
||||
descending: props.sortDesc || undefined,
|
||||
rowsPerPage: 10
|
||||
}),
|
||||
emitSelected
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.description-cell {
|
||||
height: 60px;
|
||||
width: 300px;
|
||||
max-height: 60px;
|
||||
max-width: 300px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
min-height: 300px;
|
||||
min-width: 400px;
|
||||
}
|
||||
</style>
|
||||
71
frontend/src/components/dialog/UserSelectBox.vue
Normal file
71
frontend/src/components/dialog/UserSelectBox.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<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>
|
||||
100
frontend/src/components/dialog/VersionHistory.vue
Normal file
100
frontend/src/components/dialog/VersionHistory.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<detail-field-table
|
||||
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 { 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: {
|
||||
DetailFieldTable
|
||||
},
|
||||
props: {
|
||||
app: {
|
||||
type: Object as PropType<IAppDisplay>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selected = ref<IVersionDisplay[]>();
|
||||
|
||||
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: (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 },
|
||||
];
|
||||
|
||||
const formatDate = (dateStr: string) => {
|
||||
return date.formatDate(dateStr, 'YYYY/MM/DD HH:mm:ss');
|
||||
};
|
||||
|
||||
const toUserDisplay = (user: IUser) => {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return {
|
||||
fetchVersionHistory,
|
||||
columns,
|
||||
selected,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
61
frontend/src/components/dialog/VersionInput.vue
Normal file
61
frontend/src/components/dialog/VersionInput.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<q-input
|
||||
ref="nameRef"
|
||||
v-model="versionInfo.name"
|
||||
filled
|
||||
autofocus
|
||||
label="バージョン名"
|
||||
:rules="[
|
||||
val => !!val || 'バージョン名を入力してください。',
|
||||
(val) => !val || val.length <= 80 || '80字以内で入力ください'
|
||||
]"
|
||||
/>
|
||||
<q-input
|
||||
ref="commentRef"
|
||||
v-model="versionInfo.comment"
|
||||
filled
|
||||
type="textarea"
|
||||
:rules="[(val) => !val || val.length <= 300 || '300字以内で入力ください']"
|
||||
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;
|
||||
};
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: IVersionSubmit;
|
||||
}>();
|
||||
|
||||
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,
|
||||
() => {
|
||||
emit('update:modelValue', { ...versionInfo.value });
|
||||
},
|
||||
{ immediate: true, deep: true }
|
||||
);
|
||||
</script>
|
||||
@@ -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);
|
||||
|
||||
@@ -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} 削除`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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=()=>{
|
||||
|
||||
@@ -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',
|
||||
|
||||
33
frontend/src/components/main/domainTable.vue
Normal file
33
frontend/src/components/main/domainTable.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<q-table :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="addRow" />
|
||||
<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-name="p">
|
||||
<q-td class="flex justify-between items-center" :props="p">
|
||||
{{ p.row.name }}
|
||||
<q-badge v-if="!p.row.domainActive" color="grey">未启用</q-badge>
|
||||
<q-badge v-if="p.row.id == currendDomainId" color="primary">現在</q-badge>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export default defineComponent({
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
watchEffect(()=>{
|
||||
emit("update:modelValue",numValue.value);
|
||||
emit('update:modelValue',numValue.value);
|
||||
});
|
||||
return {
|
||||
numValue,
|
||||
|
||||
@@ -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){
|
||||
|
||||
@@ -40,8 +40,7 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||
},
|
||||
props: {
|
||||
actionNode:{
|
||||
type:Object as PropType<IActionNode>,
|
||||
required:true
|
||||
type:Object as PropType<IActionNode>
|
||||
},
|
||||
drawerRight:{
|
||||
type:Boolean,
|
||||
@@ -55,7 +54,7 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||
setup(props,{emit}) {
|
||||
const showPanel =ref(props.drawerRight);
|
||||
|
||||
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
|
||||
const cloneProps = (actionProps:IActionProperty[]|undefined):IActionProperty[]|null=>{
|
||||
if(!actionProps){
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,7 +32,7 @@ export class FlowCtrl {
|
||||
* @returns
|
||||
*/
|
||||
async UpdateFlow(jsonData: any): Promise<boolean> {
|
||||
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
|
||||
const result = await api.put('api/flow', jsonData);
|
||||
console.info(result.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
||||
</q-toolbar-title>
|
||||
<domain-selector></domain-selector>
|
||||
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
|
||||
<user-info-button />
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
@@ -19,10 +19,6 @@
|
||||
</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>
|
||||
@@ -34,20 +30,27 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { computed, onMounted, reactive, getCurrentInstance } 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()
|
||||
const noDomain = computed(() => !authStore.hasDomain);
|
||||
|
||||
const essentialLinks: EssentialLinkProps[] = [
|
||||
const essentialLinks: EssentialLinkProps[] = reactive([
|
||||
{
|
||||
title: 'ホーム',
|
||||
caption: '設計書から導入する',
|
||||
icon: 'home',
|
||||
link: '/',
|
||||
target: '_self'
|
||||
target: '_self',
|
||||
disable: noDomain,
|
||||
permission: MenuMapping.home
|
||||
},
|
||||
// {
|
||||
// title: 'フローエディター',
|
||||
@@ -57,11 +60,13 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
// target: '_self'
|
||||
// },
|
||||
{
|
||||
title: 'アプリ管理',
|
||||
caption: 'アプリを管理する',
|
||||
title: 'アプリ',
|
||||
caption: 'アプリのカスタマイズ',
|
||||
icon: 'widgets',
|
||||
link: '/#/app',
|
||||
target: '_self'
|
||||
target: '_self',
|
||||
disable: noDomain,
|
||||
permission: MenuMapping.app
|
||||
},
|
||||
// {
|
||||
// title: '条件エディター',
|
||||
@@ -74,6 +79,40 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
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',
|
||||
@@ -92,47 +131,17 @@ const essentialLinks: EssentialLinkProps[] = [
|
||||
// link:'https://cybozu.dev/ja/kintone/docs/',
|
||||
// icon:'help_outline'
|
||||
// },
|
||||
];
|
||||
|
||||
const domainLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
title: 'ドメイン管理',
|
||||
caption: 'kintoneのドメイン設定',
|
||||
icon: 'domain',
|
||||
link: '/#/domain',
|
||||
target: '_self'
|
||||
},
|
||||
// {
|
||||
// title: 'ドメイン適用',
|
||||
// caption: 'ユーザー使用可能なドメインの設定',
|
||||
// icon: 'assignment_ind',
|
||||
// link: '/#/userDomain',
|
||||
// target: '_self'
|
||||
// },
|
||||
];
|
||||
|
||||
const adminLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
title: 'ユーザー管理',
|
||||
caption: 'ユーザーを管理する',
|
||||
icon: 'manage_accounts',
|
||||
link: '/#/user',
|
||||
target: '_self'
|
||||
},
|
||||
]
|
||||
]);
|
||||
|
||||
const version = process.env.version;
|
||||
const productName = process.env.productName;
|
||||
|
||||
onMounted(() => {
|
||||
authStore.toggleLeftMenu();
|
||||
authStore.setLeftMenu(!route.path.startsWith('/FlowChart/'));
|
||||
});
|
||||
|
||||
function toggleLeftDrawer() {
|
||||
getCurrentInstance();
|
||||
authStore.toggleLeftMenu();
|
||||
}
|
||||
function isAdmin(){
|
||||
const permission = authStore.permissions;
|
||||
return permission === 'admin'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<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 title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn disabled color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="showAddAppDialog" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
@@ -16,6 +16,11 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template v-slot:body-cell-name="prop">
|
||||
<q-td :props="prop">
|
||||
<q-btn flat dense :label="prop.row.name" @click="toEditFlowPage(prop.row)" ></q-btn>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-url="prop">
|
||||
<q-td :props="prop">
|
||||
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
||||
@@ -23,39 +28,67 @@
|
||||
</a>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<template v-slot:body-cell-version="p">
|
||||
<q-td :props="p">
|
||||
<q-btn-group flat>
|
||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editFlow(p.row)" />
|
||||
<q-btn disabled flat color="primary" padding="xs" size="1em" icon="history" @click="showHistory(p.row)" />
|
||||
<q-btn disabled flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||
</q-btn-group>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeSelectAppDialog" 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>
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, reactive } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useAppStore } from 'stores/useAppStore';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { router } from 'src/router';
|
||||
import { date } from 'quasar'
|
||||
import { IManagedApp } from 'src/types/AppTypes';
|
||||
|
||||
interface IAppDisplay{
|
||||
id:string;
|
||||
name:string;
|
||||
url:string;
|
||||
user:string;
|
||||
version:string;
|
||||
updatetime:string;
|
||||
}
|
||||
import { IAppDisplay } from 'src/types/AppTypes';
|
||||
import ShowDialog from 'src/components/ShowDialog.vue';
|
||||
import AppSelectBox from 'src/components/AppSelectBox.vue';
|
||||
import TableActionMenu from 'components/TableActionMenu.vue';
|
||||
|
||||
const appStore = useAppStore();
|
||||
const authStore = useAuthStore();
|
||||
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
|
||||
|
||||
@@ -63,61 +96,95 @@ 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: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true},
|
||||
{ name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true},
|
||||
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true},
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
{ name: 'updateUser', label: '最後更新者', field: '', 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: 'actions', label: '', field: 'actions' }
|
||||
];
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||
const loading = ref(false);
|
||||
const filter = ref('');
|
||||
const rows = ref<IAppDisplay[]>([]);
|
||||
const dgFilter = ref('');
|
||||
const rows = computed(() => appStore.apps);
|
||||
const targetRow = ref<IAppDisplay>();
|
||||
|
||||
const store = useFlowEditorStore();
|
||||
const appDialog = ref();
|
||||
const showSelectApp=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 },
|
||||
{ separator: true },
|
||||
{ label: '削除', icon: 'delete_outline', class: 'text-red', action: removeRow },
|
||||
];
|
||||
|
||||
const getApps = async () => {
|
||||
loading.value = true;
|
||||
const result = await api.get('api/apps');
|
||||
rows.value = result.data.map((item: IManagedApp) => {
|
||||
return {
|
||||
id: item.appid,
|
||||
name: item.appname,
|
||||
url: `${item.domainurl}/k/${item.appid}`,
|
||||
user: `${item.user.first_name} ${item.user.last_name}` ,
|
||||
updatetime:date.formatDate(item.update_time, 'YYYY/MM/DD HH:mm'),
|
||||
version: Number(item.version)
|
||||
}
|
||||
}).sort((a: IAppDisplay, b: IAppDisplay) => numberStringSorting(a.id, b.id)); // set default order
|
||||
await appStore.loadApps();
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function isVersionEditing(app: IAppDisplay) {
|
||||
return !!app.versionChanged;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
authStore.setLeftMenu(false);
|
||||
await getApps();
|
||||
});
|
||||
|
||||
watch(() => authStore.currentDomain.id, async () => {
|
||||
await getApps();
|
||||
});
|
||||
|
||||
const addRow = () => {
|
||||
return
|
||||
const filterInitRows = (row: {id: string}) => {
|
||||
return !appStore.rowIds.has(row.id);
|
||||
}
|
||||
|
||||
const removeRow = (app:IAppDisplay) => {
|
||||
return
|
||||
const showAddAppDialog = () => {
|
||||
showSelectApp.value = true;
|
||||
dgFilter.value = ''
|
||||
}
|
||||
|
||||
const showHistory = (app:IAppDisplay) => {
|
||||
return
|
||||
const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
|
||||
showSelectApp.value = true;
|
||||
if (val == 'OK' && appDialog.value.selected[0]) {
|
||||
isAdding.value = true;
|
||||
toEditFlowPage(appDialog.value.selected[0]);
|
||||
}
|
||||
showSelectApp.value = false;
|
||||
isAdding.value = false;
|
||||
}
|
||||
|
||||
const editFlow = (app:IAppDisplay) => {
|
||||
function removeRow(app:IAppDisplay) {
|
||||
targetRow.value = app;
|
||||
deleteDialog.value = true;
|
||||
}
|
||||
|
||||
const deleteApp = async () => {
|
||||
if (targetRow.value?.id) {
|
||||
deleteUserLoading.value = true;
|
||||
await appStore.deleteApp(targetRow.value)
|
||||
await getApps();
|
||||
deleteUserLoading.value = false;
|
||||
deleteDialog.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function toVersionHistoryPage(app:IAppDisplay) {
|
||||
await router.push('/app/version/' + app.id).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
async function toEditFlowPage(app:IAppDisplay) {
|
||||
store.setApp({
|
||||
appId: app.id,
|
||||
name: app.name
|
||||
});
|
||||
store.selectFlow(undefined);
|
||||
router.push('/FlowChart/' + app.id);
|
||||
await router.push('/FlowChart/' + app.id).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
207
frontend/src/pages/AppVersionManagement.vue
Normal file
207
frontend/src/pages/AppVersionManagement.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<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>
|
||||
@@ -14,6 +14,15 @@
|
||||
<q-space></q-space>
|
||||
<q-btn-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" >
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup @click="onSaveVersion">
|
||||
<q-item-section avatar >
|
||||
<q-icon name="bookmark_border"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>保存して新バージョン</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="onSaveFlow">
|
||||
<q-item-section avatar >
|
||||
<q-icon name="save" color="primary"></q-icon>
|
||||
@@ -42,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"
|
||||
@@ -75,6 +84,10 @@
|
||||
</template>
|
||||
<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>
|
||||
<q-inner-loading
|
||||
:showing="initLoading"
|
||||
color="primary"
|
||||
@@ -86,11 +99,12 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||
import { IManagedApp } from 'src/types/AppTypes';
|
||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty, AppInfo } from 'src/types/ActionTypes';
|
||||
import { IAppDisplay, IManagedApp, IVersionSubmit } 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';
|
||||
@@ -98,6 +112,7 @@ import ShowDialog from 'components/ShowDialog.vue';
|
||||
import ActionSelect from 'components/ActionSelect.vue';
|
||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||
import EventTree from 'components/left/EventTree.vue';
|
||||
import VersionInput from 'components/dialog/VersionInput.vue';
|
||||
import { FlowCtrl } from '../control/flowctrl';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
@@ -105,21 +120,25 @@ const deployLoading = ref(false);
|
||||
const saveLoading = ref(false);
|
||||
const initLoading = ref(true);
|
||||
const drawerLeft = ref(false);
|
||||
const versionSubmit = ref<IVersionSubmit>({} as IVersionSubmit);
|
||||
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();
|
||||
@@ -129,11 +148,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) => {
|
||||
@@ -176,12 +195,12 @@ const onDeleteAllNextNodes = (node: IActionNode) => {
|
||||
store.currentFlow?.removeAllNext(node.id);
|
||||
}
|
||||
const closeDg = (val: any) => {
|
||||
console.log("Dialog closed->", val);
|
||||
if (val == 'OK') {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -208,8 +227,8 @@ const onDeploy = async () => {
|
||||
if (store.appInfo === undefined || store.flows?.length === 0) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `設定されたフローがありません。`
|
||||
caption: 'エラー',
|
||||
message: '設定されたフローがありません。'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -219,16 +238,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;
|
||||
@@ -239,19 +258,40 @@ 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 }
|
||||
saveVersionAction.value = true;
|
||||
}
|
||||
|
||||
const closeSaveVersionDg = async (val: 'OK'|'CANCEL') => {
|
||||
saveVersionAction.value = true;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const onSaveFlow = async () => {
|
||||
const targetFlow = store.selectedFlow;
|
||||
if (targetFlow === undefined) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: `選択中のフローがありません。`
|
||||
message: '選択中のフローがありません。'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -261,7 +301,7 @@ const onSaveFlow = async () => {
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
caption: '通知',
|
||||
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -269,7 +309,7 @@ const onSaveFlow = async () => {
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
caption: 'エラー',
|
||||
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||
})
|
||||
}
|
||||
@@ -284,7 +324,7 @@ const onSaveAllFlow= async ()=>{
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: `設定されたフローがありません。`
|
||||
message: '設定されたフローがありません。'
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -298,8 +338,8 @@ const onSaveAllFlow= async ()=>{
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `すべてのフロー設定を保存しました。`
|
||||
caption: '通知',
|
||||
message: 'すべてのフロー設定を保存しました。'
|
||||
});
|
||||
saveLoading.value = false;
|
||||
}catch (error) {
|
||||
@@ -307,8 +347,8 @@ const onSaveAllFlow= async ()=>{
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `フローの設定の保存が失敗しました。`
|
||||
caption: 'エラー',
|
||||
message: 'フローの設定の保存が失敗しました。'
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -316,11 +356,9 @@ const onSaveAllFlow= async ()=>{
|
||||
const fetchData = async () => {
|
||||
initLoading.value = true;
|
||||
if (store.appInfo === undefined && route?.params?.id !== undefined) {
|
||||
const { appid, appname } = await fetchAppById(route.params.id as string);
|
||||
store.setApp({
|
||||
appId: appid,
|
||||
name: appname
|
||||
});
|
||||
// only for page refreshed
|
||||
const app = await fetchAppById(route.params.id as string);
|
||||
store.setApp(app);
|
||||
};
|
||||
await store.loadFlow();
|
||||
initLoading.value = false
|
||||
@@ -328,17 +366,46 @@ const fetchData = async () => {
|
||||
}
|
||||
|
||||
const fetchAppById = async(id: string) => {
|
||||
const result = await api.get('api/apps');
|
||||
return result.data.find((item: IManagedApp) => item.appid === id ) as IManagedApp;
|
||||
let result = await api.get('api/apps');
|
||||
const app = result.data?.data?.find((item: IManagedApp) => item.appid === id ) as IManagedApp;
|
||||
if (app) {
|
||||
return convertManagedAppToAppInfo(app);
|
||||
}
|
||||
|
||||
result = await api.get(`api/v1/app?app=${id}`);
|
||||
const kApp = result?.data as IAppDisplay | KErrorMsg;
|
||||
if (isErrorMsg(kApp)) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: kApp.message,
|
||||
});
|
||||
}
|
||||
return kApp;
|
||||
}
|
||||
|
||||
type KErrorMsg = {
|
||||
message: string;
|
||||
}
|
||||
|
||||
const isErrorMsg = (e: IAppDisplay | KErrorMsg): e is KErrorMsg => {
|
||||
return 'message' in e;
|
||||
};
|
||||
|
||||
const convertManagedAppToAppInfo = (app: IManagedApp): AppInfo => {
|
||||
return {
|
||||
appId: app.appid,
|
||||
name: app.appname
|
||||
}
|
||||
};
|
||||
|
||||
const onClearFilter=()=>{
|
||||
filter.value='';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
authStore.setLeftMenu(false);
|
||||
fetchData();
|
||||
await fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<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>
|
||||
@@ -16,28 +15,26 @@
|
||||
</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>
|
||||
|
||||
@@ -48,20 +48,20 @@
|
||||
</q-layout>>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { useQuasar } from 'quasar'
|
||||
import { useQuasar, QForm } 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(null);
|
||||
const loginForm = ref<QForm>();
|
||||
const loading = ref(false);
|
||||
let title = ref('ログイン');
|
||||
let email = ref('');
|
||||
let password = ref('');
|
||||
let visibility = ref(false);
|
||||
let passwordFieldType = ref('password');
|
||||
let passwordFieldType = ref<'text'|'password'>('password');
|
||||
let visibilityIcon = ref('visibility');
|
||||
const required = (val:string) => {
|
||||
return (val && val.length > 0 || '必須項目')
|
||||
@@ -78,35 +78,41 @@ import { useAuthStore } from 'stores/useAuthStore';
|
||||
passwordFieldType.value = visibility.value ? 'text' : 'password'
|
||||
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
|
||||
}
|
||||
const submit = async () =>{
|
||||
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;
|
||||
$q.notify({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ログイン失敗'
|
||||
});
|
||||
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;
|
||||
$q.notify({
|
||||
icon: 'error',
|
||||
color: 'negative',
|
||||
message: 'ログイン失敗'
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
250
frontend/src/pages/RoleManagement.vue
Normal file
250
frontend/src/pages/RoleManagement.vue
Normal file
@@ -0,0 +1,250 @@
|
||||
<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>
|
||||
@@ -66,8 +66,8 @@ interface Props {
|
||||
actions: string[];
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title: "ルールエディター",
|
||||
actions: () => ["フィールド制御", "一覧画面", "その他"]
|
||||
title: 'ルールエディター',
|
||||
actions: () => ['フィールド制御', '一覧画面', 'その他']
|
||||
});
|
||||
function onItemClick(evt: Event) {
|
||||
return;
|
||||
|
||||
@@ -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 title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
<q-table :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="addRow" />
|
||||
<q-btn v-permissions="Actions.domain.add" color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
@@ -17,12 +17,30 @@
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-active="p">
|
||||
<q-th auto-width :props="p">
|
||||
<q-select class="filter-header" v-model="activeFilter" :options="activeOptions" @update:model-value="activeFilterUpdate" borderless
|
||||
dense options-dense hide-bottom-space/>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-active="p">
|
||||
<q-td auto-width :props="p">
|
||||
<q-badge v-if="!p.row.domainActive" color="grey">未使用</q-badge>
|
||||
<q-badge v-if="p.row.id == currentDomainId" color="primary">既定</q-badge>
|
||||
</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">
|
||||
<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>
|
||||
<table-action-menu :row="p.row" :actions="actionList" />
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -38,15 +56,11 @@
|
||||
<q-card-section class="q-pt-none q-mt-none">
|
||||
<div class="q-gutter-lg">
|
||||
|
||||
|
||||
<q-input filled v-model="tenantid" label="テナントID" hint="テナントIDを入力してください。" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'テナントIDを入力してください。']" />
|
||||
|
||||
<q-input filled v-model="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
||||
|
||||
<q-input filled type="url" v-model="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0, isDomain || 'KintoneのURLを入力してください']" />
|
||||
<q-input filled type="url" v-model.trim="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'KintoneのURLを入力してください']" />
|
||||
|
||||
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
||||
@@ -60,6 +74,15 @@
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>ドメインの有効化</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="domainActive" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<div class="q-gutter-y-md" v-if="!isCreate">
|
||||
<q-separator />
|
||||
|
||||
@@ -86,7 +109,7 @@
|
||||
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||
<q-btn label="保存" type="submit" color="primary" />
|
||||
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
@@ -96,70 +119,159 @@
|
||||
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<!-- -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-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" v-close-popup @click="deleteDomain()" />
|
||||
<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-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" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
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 TableActionMenu from 'components/TableActionMenu.vue';
|
||||
import { IDomain, IDomainDisplay, IDomainOwnerDisplay, IDomainSubmit } from '../types/DomainTypes';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const inactiveRowClass = (row: IDomainOwnerDisplay) => row.domainActive ? '' : 'inactive-row';
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{
|
||||
name: 'tenantid',
|
||||
required: true,
|
||||
label: 'テナントID',
|
||||
field: row => row.tenantid,
|
||||
format: val => `${val}`,
|
||||
align: 'left',
|
||||
sortable: true
|
||||
},
|
||||
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
||||
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', },
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||
// {
|
||||
// name: 'tenantid',
|
||||
// required: true,
|
||||
// label: 'テナントID',
|
||||
// field: 'tenantid',
|
||||
// align: 'left',
|
||||
// sortable: true,
|
||||
// classes: inactiveRowClass
|
||||
// },
|
||||
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true, classes: inactiveRowClass },
|
||||
{ name: 'active', label: '', 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: '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 filter = ref('');
|
||||
const rows = ref([]);
|
||||
const rows = ref<IDomainOwnerDisplay[]>([]);
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const resetPsw = ref(false);
|
||||
|
||||
const tenantid = ref(authStore.currentDomain.id);
|
||||
const currentDomainId = computed(() => authStore.currentDomain.id);
|
||||
// const tenantid = ref(authStore.currentDomain.id);
|
||||
const name = ref('');
|
||||
const url = ref('');
|
||||
const isPwd = ref(true);
|
||||
const kintoneuser = ref('');
|
||||
const kintonepwd = ref('');
|
||||
const kintonepwdBK = ref('');
|
||||
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 getDomain = async () => {
|
||||
const activeOptions = [
|
||||
{ value: 0, label: 'すべて' },
|
||||
{ value: 1, label: '使用' },
|
||||
{ value: 2, label: '未使用'}
|
||||
]
|
||||
const activeFilter = ref(activeOptions[0]);
|
||||
|
||||
const activeFilterUpdate = (option: {value: number}) => {
|
||||
switch (option.value) {
|
||||
case 1:
|
||||
getDomain((row) => row.domainActive)
|
||||
break;
|
||||
case 2:
|
||||
getDomain((row) => !row.domainActive)
|
||||
break;
|
||||
default:
|
||||
getDomain()
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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)}
|
||||
},
|
||||
{ separator: true },
|
||||
{ label: '削除', icon: 'delete_outline', permission: Actions.domain.delete, 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 userId = authStore.userId;
|
||||
const result = await api.get(`api/domain?userId=${userId}`);
|
||||
rows.value = result.data.map((item) => {
|
||||
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
|
||||
});
|
||||
const { data } = await api.get<{data:IDomain[]}>('api/domains');
|
||||
rows.value = data.data.map((item) => {
|
||||
return {
|
||||
id: item.id,
|
||||
tenantid: item.tenantid,
|
||||
domainActive: item.is_active,
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
user: item.kintoneuser,
|
||||
password: item.kintonepwd,
|
||||
owner: {
|
||||
id: item.owner.id,
|
||||
firstName: item.owner.first_name,
|
||||
lastName: item.owner.last_name,
|
||||
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
||||
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
||||
email: item.owner.email,
|
||||
isActive: item.owner.is_active,
|
||||
isSuperuser: item.owner.is_superuser,
|
||||
}
|
||||
}
|
||||
}).filter(filter || (() => true));
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -174,27 +286,38 @@ const addRow = () => {
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
const removeRow = (row) => {
|
||||
async function removeRow(row: IDomainOwnerDisplay) {
|
||||
confirm.value = true;
|
||||
deleteLoadingState.value = -1;
|
||||
editId.value = row.id;
|
||||
|
||||
const { data } = await api.get(`/api/domainshareduser/${row.id}`);
|
||||
deleteLoadingState.value = data.data.length;
|
||||
}
|
||||
|
||||
const deleteDomain = () => {
|
||||
api.delete(`api/domain/${editId.value}`).then(() => {
|
||||
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;
|
||||
|
||||
editId.value = 0; // set in removeRow()
|
||||
};
|
||||
|
||||
const editRow = (row) => {
|
||||
function editRow(row: any) {
|
||||
isCreate.value = false
|
||||
editId.value = row.id;
|
||||
tenantid.value = row.tenantid;
|
||||
// tenantid.value = row.tenantid;
|
||||
name.value = row.name;
|
||||
url.value = row.url;
|
||||
kintoneuser.value = row.user;
|
||||
kintonepwd.value = row.password;
|
||||
domainActive.value = row.domainActive;
|
||||
isPwd.value = true;
|
||||
show.value = true;
|
||||
};
|
||||
@@ -214,37 +337,43 @@ const closeDg = () => {
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
if (editId.value !== 0) {
|
||||
api.put(`api/domain`, {
|
||||
'id': editId.value,
|
||||
'tenantid': tenantid.value,
|
||||
'name': name.value,
|
||||
'url': url.value,
|
||||
'kintoneuser': kintoneuser.value,
|
||||
'kintonepwd': isCreate.value || resetPsw.value ? kintonepwd.value : ''
|
||||
}).then(() => {
|
||||
addEditLoading.value = true;
|
||||
const method = editId.value !== 0 ? 'put' : 'post';
|
||||
const param: IDomainSubmit = {
|
||||
'id': editId.value,
|
||||
'tenantid': '1', // TODO: テナントIDを取得する
|
||||
'name': name.value,
|
||||
'url': url.value,
|
||||
'kintoneuser': kintoneuser.value,
|
||||
'kintonepwd': ((isCreate.value && editId.value == 0) || resetPsw.value) ? kintonepwd.value : '',
|
||||
'is_active': domainActive.value,
|
||||
'ownerid': authStore.userId || ''
|
||||
}
|
||||
// for search: api.put(`api/domain`)、api.post(`api/domain`)
|
||||
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();
|
||||
closeDg();
|
||||
onReset();
|
||||
addEditLoading.value = false;
|
||||
})
|
||||
}
|
||||
else {
|
||||
api.post(`api/domain`, {
|
||||
'id': 0,
|
||||
'tenantid': tenantid.value,
|
||||
'name': name.value,
|
||||
'url': url.value,
|
||||
'kintoneuser': kintoneuser.value,
|
||||
'kintonepwd': kintonepwd.value
|
||||
}).then(() => {
|
||||
getDomain();
|
||||
closeDg();
|
||||
onReset();
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function openShareDg(type: typeof SHARE_MANAGE|typeof SHARE_USE, 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;
|
||||
}
|
||||
};
|
||||
|
||||
const onReset = () => {
|
||||
name.value = '';
|
||||
url.value = '';
|
||||
@@ -253,6 +382,24 @@ const onReset = () => {
|
||||
isPwd.value = true;
|
||||
editId.value = 0;
|
||||
isCreate.value = true;
|
||||
domainActive.value = true;
|
||||
resetPsw.value = false
|
||||
addEditLoading.value = false;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.filter-header .q-field__native {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.filter-header .q-icon {
|
||||
width: 12px;
|
||||
}
|
||||
.q-table td.inactive-row {
|
||||
color: #aaa;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
.q-table tr > td.inactive-row:last-child {
|
||||
color: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
<template>
|
||||
|
||||
<div class="q-pa-lg">
|
||||
<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>
|
||||
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
||||
|
||||
<q-table :loading="initLoading" grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
||||
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
||||
|
||||
<template v-slot:top>
|
||||
|
||||
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
||||
|
||||
<q-space />
|
||||
<div class="row q-gutter-md">
|
||||
<q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()">
|
||||
<q-item-section>
|
||||
<q-item-label>適用するユーザ : </q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
{{ currentUserName }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
||||
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
@@ -38,67 +29,25 @@
|
||||
|
||||
<template v-slot:item="props">
|
||||
<div class="q-pa-sm">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">Domain</div>
|
||||
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">URL</div>
|
||||
<div class="q-table__grid-item-value" style="width: 22rem;">{{ props.row.url }}</div>
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">Account</div>
|
||||
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-actions align="right">
|
||||
<div style="width: 98%;">
|
||||
<div class="row items-center justify-between">
|
||||
<div class="q-table__grid-item-value"
|
||||
:class="isActive(props.row.id) ? 'text-positive' : 'text-negative'">{{
|
||||
isActive(props.row.id)?'既定':'' }}</div>
|
||||
<div class="col-auto">
|
||||
<q-btn v-if="!isActive(props.row.id)" flat
|
||||
@click="activeDomain(props.row.id)">既定にする</q-btn>
|
||||
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
<domain-card :item="props.row" :active-id="activeDomainId">
|
||||
<template v-slot:actions>
|
||||
<q-card-actions align="right">
|
||||
<q-chip class="no-border" v-if="isActive(props.row.id)" outline color="primary" text-color="white" icon="done">
|
||||
既定
|
||||
</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-card-actions>
|
||||
</template>
|
||||
</domain-card>
|
||||
</div>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
|
||||
<domain-select ref="addDomainRef" name="ドメイン" type="multiple"></domain-select>
|
||||
</show-dialog>
|
||||
|
||||
<show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense placeholder="検索" v-model="switchUserFilter">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<div class="q-gutter-md">
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>他のユーザーを選択する</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="useOtherUser" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<div v-if="useOtherUser">
|
||||
<user-list ref="switchUserRef" name="ドメイン" :filter="switchUserFilter" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished" :ok-btn-loading="addUserDomainLoading" :ok-btn-auto-close="false">
|
||||
<domain-select ref="addDomainRef" name="ドメイン" type="single" :filterInitRowsFunc="filterAddDgInitRows"></domain-select>
|
||||
</show-dialog>
|
||||
|
||||
<q-dialog v-model="showDeleteConfirm" persistent>
|
||||
@@ -123,49 +72,64 @@
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import { IDomainOwnerDisplay } from '../types/DomainTypes';
|
||||
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import DomainCard from 'components/UserDomain/DomainCard.vue';
|
||||
import DomainSelect from 'components/DomainSelect.vue';
|
||||
import UserList from 'components/UserList.vue';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
||||
const rows = ref([] as any[]);
|
||||
const rows = ref<IDomainOwnerDisplay[]>([]);
|
||||
const rowIds = new Set<string>();
|
||||
const initLoading = ref(true);
|
||||
const addUserDomainLoading = ref(false);
|
||||
const activeDomainLoadingId = ref<number|undefined>(undefined);
|
||||
const deleteDomainLoadingId = ref<number|undefined>(undefined);
|
||||
|
||||
const columns = [
|
||||
{ name: 'id' },
|
||||
{ name: 'name', required: true, label: 'Name', align: 'left', field: 'name', sortable: true },
|
||||
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
||||
{ name: 'kintonepwd' },
|
||||
{ name: 'active', field: 'active' }
|
||||
{ name: 'kintoneuser', label: 'User', field: 'user', sortable: true },
|
||||
];
|
||||
const userDomainTableFilter = ref();
|
||||
|
||||
const currentUserName = ref('');
|
||||
const useOtherUser = ref(false);
|
||||
const otherUserId = ref('');
|
||||
|
||||
let editId = ref(0);
|
||||
|
||||
const showAddDomainDg = ref(false);
|
||||
const addDomainRef = ref();
|
||||
|
||||
const filterAddDgInitRows = (row: {domainActive: boolean, id: string}) => {
|
||||
return row.domainActive && !rowIds.has(row.id);
|
||||
}
|
||||
|
||||
const clickAddDomain = () => {
|
||||
editId.value = 0;
|
||||
showAddDomainDg.value = true;
|
||||
};
|
||||
|
||||
const addUserDomainFinished = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
let dodmainids = [];
|
||||
let domains = JSON.parse(JSON.stringify(addDomainRef.value.selected));
|
||||
for (var key in domains) {
|
||||
dodmainids.push(domains[key].id);
|
||||
const addUserDomainFinished = async (val: string) => {
|
||||
showAddDomainDg.value = true;
|
||||
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,
|
||||
});
|
||||
if (rows.value.length === 0 && data.data) {
|
||||
const domain = data.data;
|
||||
await authStore.setCurrentDomain({
|
||||
id: domain.id,
|
||||
kintoneUrl: domain.url,
|
||||
domainName: domain.name
|
||||
});
|
||||
}
|
||||
api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}`, dodmainids)
|
||||
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); });
|
||||
await getDomain();
|
||||
}
|
||||
addUserDomainLoading.value = false;
|
||||
showAddDomainDg.value = false;
|
||||
};
|
||||
|
||||
const showDeleteConfirm = ref(false);
|
||||
@@ -175,16 +139,26 @@ const clickDeleteConfirm = (row: any) => {
|
||||
editId.value = row.id;
|
||||
};
|
||||
|
||||
const deleteDomainFinished = () => {
|
||||
api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(() => {
|
||||
getDomain(useOtherUser.value ? otherUserId.value : undefined);
|
||||
})
|
||||
const deleteDomainFinished = async () => {
|
||||
deleteDomainLoadingId.value = editId.value;
|
||||
const { data } = await api.delete(`api/domain/${editId.value}/${authStore.userId}`)
|
||||
if (data.msg == 'OK' && authStore.currentDomain.id === editId.value) {
|
||||
authStore.setCurrentDomain();
|
||||
}
|
||||
editId.value = 0;
|
||||
await getDomain();
|
||||
deleteDomainLoadingId.value = undefined;
|
||||
};
|
||||
|
||||
const activeDomain = (id: number) => {
|
||||
api.put(`api/activedomain/${id}${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`)
|
||||
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); })
|
||||
const activeDomain = async (domain: any) => {
|
||||
activeDomainLoadingId.value = domain.id;
|
||||
await authStore.setCurrentDomain({
|
||||
id: domain.id,
|
||||
kintoneUrl: domain.url,
|
||||
domainName: domain.name
|
||||
});
|
||||
await getDomain();
|
||||
activeDomainLoadingId.value = undefined;
|
||||
};
|
||||
|
||||
let activeDomainId = ref(0);
|
||||
@@ -193,44 +167,54 @@ const isActive = computed(() => (id: number) => {
|
||||
return id == activeDomainId.value;
|
||||
});
|
||||
|
||||
|
||||
const showSwitchUserDd = ref(false);
|
||||
const switchUserRef = ref();
|
||||
const switchUserFilter = ref('')
|
||||
|
||||
const clickSwitchUser = () => {
|
||||
showSwitchUserDd.value = true;
|
||||
useOtherUser.value = false;
|
||||
};
|
||||
|
||||
const switchUserFinished = async (val: string) => {
|
||||
if (val == 'OK') {
|
||||
if (useOtherUser.value) {
|
||||
const user = switchUserRef.value.selected[0]
|
||||
currentUserName.value = user.email;
|
||||
otherUserId.value = user.id
|
||||
await getDomain(user.id)
|
||||
} else {
|
||||
currentUserName.value = authStore.userInfo.email
|
||||
await getDomain();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const getDomain = async (userId? : string) => {
|
||||
const resp = await api.get(`api/activedomain${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`);
|
||||
activeDomainId.value = resp?.data?.id;
|
||||
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
|
||||
rowIds.clear();
|
||||
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 domains = domainResult.data as any[];
|
||||
rows.value = domains.map((item) => {
|
||||
return { id: item.id, name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd }
|
||||
});
|
||||
rows.value = domains.sort((a, b) => a.id - b.id).reduce((acc, item) => {
|
||||
rowIds.add(item.id);
|
||||
if (item.is_active) {
|
||||
acc.push({
|
||||
id: item.id,
|
||||
tenantid: item.tenantid,
|
||||
domainActive: item.is_active,
|
||||
name: item.name,
|
||||
url: item.url,
|
||||
user: item.kintoneuser,
|
||||
password: item.kintonepwd,
|
||||
owner: {
|
||||
id: item.owner.id,
|
||||
firstName: item.owner.first_name,
|
||||
lastName: item.owner.last_name,
|
||||
fullNameSearch: (item.owner.last_name + item.owner.first_name).toLowerCase(),
|
||||
fullName: item.owner.last_name + ' ' + item.owner.first_name,
|
||||
email: item.owner.email,
|
||||
isActive: item.owner.is_active,
|
||||
isSuperuser: item.owner.is_superuser,
|
||||
}
|
||||
})
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
currentUserName.value = authStore.userInfo.email
|
||||
initLoading.value = true;
|
||||
await getDomain();
|
||||
initLoading.value = false;
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.domain-card {
|
||||
width: 22rem;
|
||||
word-break: break-word;
|
||||
|
||||
.smaller-font-size {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -9,7 +9,7 @@
|
||||
:pagination="pagination" >
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-btn v-permissions="Actions.user.add" 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,30 +27,34 @@
|
||||
<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: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"
|
||||
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>
|
||||
|
||||
<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="options" @update:model-value="updateStatusFilter" borderless
|
||||
<q-select v-model="statusFilter" :options="statusFilterOptions" borderless
|
||||
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
||||
</div>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<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 auto-width :props="p">
|
||||
<table-action-menu :row="p.row" minWidth="180px" max-width="200px" :actions="actionList" />
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
@@ -65,10 +69,10 @@
|
||||
<q-card-section class="q-pt-none q-mt-none">
|
||||
<div class="q-gutter-lg">
|
||||
|
||||
<q-input filled v-model="firstName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
||||
<q-input filled v-model="lastName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
||||
|
||||
<q-input filled v-model="lastName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
||||
<q-input filled v-model="firstName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
||||
|
||||
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
||||
@@ -127,7 +131,7 @@
|
||||
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||
<q-btn label="保存" type="submit" color="primary" />
|
||||
<q-btn :loading="addEditLoading" label="保存" type="submit" color="primary" />
|
||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
@@ -153,24 +157,39 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
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();
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '苗字', field: 'lastName', 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' }
|
||||
{ name: 'roles', label: 'ロール', field: '', align: 'left' },
|
||||
{ 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('全データ');
|
||||
const rows = ref([]);
|
||||
const statusFilter = ref(statusFilterOptions[0]);
|
||||
const rows = computed(() => userStore.users.filter(statusFilter.value.filter));
|
||||
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const resetPsw = ref(false);
|
||||
@@ -183,69 +202,47 @@ const isActive = ref(true);
|
||||
|
||||
const isPwd = ref(true);
|
||||
const pwd = ref('');
|
||||
const isCreate = ref(true);
|
||||
let editId = ref(0);
|
||||
const editId = ref(0);
|
||||
const isCreate = computed(() => editId.value <= 0);
|
||||
|
||||
const getUsers = async (filter = () => true) => {
|
||||
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 () => {
|
||||
loading.value = true;
|
||||
const result = await api.get(`api/v1/users`);
|
||||
rows.value = result.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);
|
||||
await userStore.loadUsers();
|
||||
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;
|
||||
}
|
||||
|
||||
const removeRow = (row) => {
|
||||
function showDeleteUserConfirm(row: IUserDisplay) {
|
||||
confirm.value = true;
|
||||
editId.value = row.id;
|
||||
}
|
||||
|
||||
const deleteUser = () => {
|
||||
api.delete(`api/v1/users/${editId.value}`).then(() => {
|
||||
getUsers();
|
||||
})
|
||||
editId.value = 0;
|
||||
const deleteUser = async () => {
|
||||
await userStore.deleteUser(editId.value);
|
||||
getUsers();
|
||||
onReset();
|
||||
};
|
||||
|
||||
const editRow = (row) => {
|
||||
isCreate.value = false
|
||||
function editRow(row: IUserDisplay) {
|
||||
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;
|
||||
@@ -259,37 +256,25 @@ const closeDg = () => {
|
||||
onReset();
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
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();
|
||||
})
|
||||
const onSubmit = async () => {
|
||||
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
|
||||
}
|
||||
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();
|
||||
})
|
||||
if (isCreate.value) {
|
||||
await userStore.addUser(param);
|
||||
} else {
|
||||
await userStore.editUser(param);
|
||||
}
|
||||
|
||||
getUsers();
|
||||
closeDg();
|
||||
onReset();
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
@@ -301,7 +286,7 @@ const onReset = () => {
|
||||
isSuperuser.value = false;
|
||||
isPwd.value = true;
|
||||
editId.value = 0;
|
||||
isCreate.value = true;
|
||||
resetPsw.value = false
|
||||
resetPsw.value = false;
|
||||
addEditLoading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
createWebHashHistory,
|
||||
createWebHistory,
|
||||
} from 'vue-router';
|
||||
import { Dialog } from 'quasar'
|
||||
|
||||
import routes from './routes';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
@@ -33,13 +34,14 @@ const routerInstance = createRouter({
|
||||
|
||||
export default route(function (/* { store, ssrContext } */) {
|
||||
|
||||
routerInstance.beforeEach(async (to) => {
|
||||
routerInstance.beforeEach(async (to, from) => {
|
||||
// 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 publicPages = ['/login'];
|
||||
const loginPage = '/login';
|
||||
const publicPages = [loginPage];
|
||||
const authRequired = !publicPages.includes(to.path);
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@@ -47,6 +49,22 @@ export default route(function (/* { store, ssrContext } */) {
|
||||
authStore.returnUrl = to.fullPath;
|
||||
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'];
|
||||
if (!authStore.hasDomain && !domainPages.includes(to.path)) {
|
||||
Dialog.create({
|
||||
title: '注意',
|
||||
message: '既定/利用可能なドメインはありません。<br>接続先管理ページに遷移して処理します。',
|
||||
html: true,
|
||||
persistent: true,
|
||||
})
|
||||
return '/domain';
|
||||
}
|
||||
});
|
||||
return routerInstance;
|
||||
});
|
||||
|
||||
@@ -16,18 +16,18 @@ const routes: RouteRecordRaw[] = [
|
||||
path: '/',
|
||||
component: () => import('layouts/MainLayout.vue'),
|
||||
children: [
|
||||
{ path: '', component: () => import('pages/IndexPage.vue') },
|
||||
{ path: '', component: () => import('pages/IndexPage.vue'), props: { app: '' } },
|
||||
{ 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: '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') }
|
||||
],
|
||||
},
|
||||
|
||||
8
frontend/src/shims-vue.d.ts
vendored
8
frontend/src/shims-vue.d.ts
vendored
@@ -8,3 +8,11 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user