Skip to content

Commit 15a049d

Browse files
committed
textfsm_online(feature): 增加加载功能
1 parent 1577882 commit 15a049d

4 files changed

Lines changed: 244 additions & 8 deletions

File tree

demo/fastapi_demo/textfsm_demo.py

Lines changed: 99 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1+
import tempfile
2+
import csv
3+
import os
4+
import logging
5+
import textfsm
6+
from importlib.resources import path as importresources_path
17
from fastapi import FastAPI
28
from fastapi.middleware.cors import CORSMiddleware
39
from pydantic import BaseModel
4-
import textfsm
5-
import tempfile
610

711

8-
def str_to_file_obj(text):
9-
f_obj = tempfile.NamedTemporaryFile()
12+
base_path = os.path.dirname(__file__)
13+
parent_path = os.path.dirname(base_path)
14+
logging.basicConfig(
15+
level=logging.DEBUG,
16+
format="%(asctime)s %(filename)s[line:%(lineno)d ] %(levelname)s %(message)s", # 时间 文件名 line:行号 levelname logn内容
17+
datefmt="%d %b %Y,%a %H:%M:%S", # 日 月 年 ,星期 时 分 秒
18+
filename=parent_path + "/log/gunicorn_access.log",
19+
filemode="w",
20+
)
21+
22+
23+
def str_to_file_obj(text, mode="wb+"):
24+
f_obj = tempfile.NamedTemporaryFile(mode=mode)
1025
f_obj.write(text)
1126
# 确保 string 立即写入文件
1227
f_obj.flush()
@@ -30,13 +45,62 @@ def textfsm_parser(raw_text: str, template_text: str) -> str:
3045
return textfsm_data
3146

3247
except textfsm.parser.Error as tfte:
33-
return str(tfte)
48+
return {"parse_error": str(tfte)}
49+
50+
51+
def get_ntc_path():
52+
with importresources_path(
53+
package="ntc_templates", resource="templates"
54+
) as posix_path:
55+
# Example: /opt/venv/netmiko/lib/python3.8/site-packages/ntc_templates/templates
56+
template_dir = str(posix_path)
57+
58+
index = os.path.join(template_dir, "index")
59+
if not os.path.isdir(template_dir) or not os.path.isfile(index):
60+
raise ValueError("Using `pip install ntc-templates` to install.")
61+
return os.path.abspath(template_dir)
62+
63+
64+
def get_tmpl_by_platform(platform, template_list):
65+
tmpl_list = []
66+
for tmpl in template_list:
67+
if platform and platform in tmpl["Platform"]:
68+
tmpl_list.append(tmpl["Template"])
69+
return tmpl_list
70+
71+
72+
def parse_csv_to_list(index_file):
73+
template_list = []
74+
platform_list = []
75+
with open(index_file, "r") as f:
76+
buf = ""
77+
for line in f:
78+
if line.startswith("#") or line.startswith("\n"):
79+
continue
80+
lst = [l.strip() for l in line.split(",")]
81+
buf += ",".join(lst) + "\n"
82+
reader = csv.DictReader(str_to_file_obj(buf, mode="w+"))
83+
for i in reader:
84+
template_list.append(i)
85+
platform_list.append(i.get("Platform"))
86+
return set(platform_list), template_list
87+
88+
89+
def load_template(base_dir, tmpl_name):
90+
try:
91+
with open(base_dir + "/" + tmpl_name, "r") as f:
92+
content = f.read()
93+
except:
94+
content = ""
95+
return content
3496

3597

3698
app = FastAPI()
3799

38100
origins = [
39101
"http://localhost:8080",
102+
"http://localhost:80",
103+
"http://textfsm.xdai.vip",
40104
]
41105

42106
app.add_middleware(
@@ -46,17 +110,46 @@ def textfsm_parser(raw_text: str, template_text: str) -> str:
46110
allow_headers=["*"],
47111
)
48112

113+
49114
class TextFSMBody(BaseModel):
50115
raw_text: str
51116
template_text: str
52117

53118

119+
platform_list, template_list = parse_csv_to_list(get_ntc_path() + "/index")
120+
121+
54122
@app.post("/parser")
55123
async def parse_textfsm(textfsm_body: TextFSMBody):
56-
return textfsm_parser(**dict(textfsm_body))
124+
textfsm_body = dict(textfsm_body)
125+
result = textfsm_parser(**textfsm_body)
126+
if isinstance(result, list) and len(result) > 0:
127+
textfsm_body["result"] = result
128+
logging.info(textfsm_body)
129+
return result
130+
131+
132+
@app.get("/parser/getPlatformList")
133+
def get_platform_list():
134+
return {"data": {"platform_list": platform_list}}
135+
136+
137+
@app.get("/parser/getTemplateList")
138+
def get_template_list(platform):
139+
tmpl_list = get_tmpl_by_platform(platform, template_list)
140+
return {"data": {"platform": platform, "template_list": tmpl_list}}
141+
142+
143+
@app.get("/parser/loadTemplate")
144+
def load_tmpl(template):
145+
base_dir = get_ntc_path()
146+
content = load_template(base_dir, template)
147+
print(content)
148+
return {"data": {"template": template, "content": content}}
57149

58150

59151
if __name__ == "__main__":
60152
import uvicorn
61153

62154
uvicorn.run(app, host="127.0.0.1", port=8000)
155+
# uvicorn.run(app, host="0.0.0.0", port=8000)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<template>
2+
<el-select v-model="value" filterable clearable :placeholder="placeholder">
3+
<el-option
4+
v-for="item in options"
5+
:key="item.value"
6+
:label="item.label"
7+
:value="item.value"
8+
>
9+
</el-option>
10+
</el-select>
11+
</template>
12+
13+
<script>
14+
export default {
15+
data () {
16+
return {
17+
placeholder: '',
18+
options: [
19+
{
20+
value: '选项1',
21+
label: '黄金糕'
22+
},
23+
{
24+
value: '选项2',
25+
label: '双皮奶'
26+
},
27+
{
28+
value: '选项3',
29+
label: '蚵仔煎'
30+
},
31+
{
32+
value: '选项4',
33+
label: '龙须面'
34+
},
35+
{
36+
value: '选项5',
37+
label: '北京烤鸭'
38+
}
39+
],
40+
value: ''
41+
}
42+
}
43+
}
44+
</script>

demo/vue_demo/textfsm_online/src/main.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Vue from 'vue'
22
import App from './App.vue'
33
import router from './router'
44
import VueCodemirror from 'vue-codemirror'
5-
import { Container, Footer, Switch, Col, Main } from 'element-ui'
5+
import { Container, Footer, Switch, Col, Main, Select, Option, Button } from 'element-ui'
66
import 'element-ui/lib/theme-chalk/index.css'
77
// import ElementUI from 'element-ui'
88

@@ -12,6 +12,9 @@ Vue.use(Main)
1212
Vue.use(Footer)
1313
Vue.use(Switch)
1414
Vue.use(Col)
15+
Vue.use(Select)
16+
Vue.use(Option)
17+
Vue.use(Button)
1518
// Vue.use(ElementUI)
1619

1720
Vue.config.productionTip = false

demo/vue_demo/textfsm_online/src/views/HomeView.vue

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
11
<template>
22
<el-container v-bind:class="{ darkMode: isDark}">
3+
<div style="padding-top: 15px;text-align:center;">
4+
<span>加载已有模板:</span>
5+
<el-select
6+
style="width:15%;"
7+
v-model="platform_value"
8+
filterable placeholder="选择 Platform"
9+
size="small"
10+
>
11+
<el-option
12+
v-for="item in platform_options"
13+
:key="item"
14+
:label="item"
15+
:value="item"
16+
>
17+
</el-option>
18+
</el-select>
19+
<el-select style="width:20%;"
20+
v-model="template_value"
21+
filterable placeholder="选择 TextFSM 模板"
22+
@focus="getTemplateList()"
23+
no-data-text="请先选择 Platform"
24+
size="small"
25+
>
26+
<el-option
27+
v-for="item in template_options"
28+
:key="item"
29+
:label="item"
30+
:value="item"
31+
>
32+
</el-option>
33+
</el-select>
34+
<el-button id="loadTemplate"
35+
type="info" plain
36+
size="small"
37+
@click="loadTemplate()" icon="el-icon-plus
38+
"></el-button>
39+
</div>
340
<el-main>
441
<el-col :span="8">
542
<div class="grid-content">
@@ -49,6 +86,7 @@ import 'codemirror/mode/javascript/javascript'
4986
import 'codemirror/addon/display/placeholder.js'
5087
import 'codemirror/theme/idea.css'
5188
import 'codemirror/theme/darcula.css'
89+
// import FilterableSelect from '../components/FilterableSelect.vue'
5290
5391
export default {
5492
data () {
@@ -62,11 +100,17 @@ export default {
62100
mode: 'javascript',
63101
lineNumbers: true,
64102
line: true
65-
}
103+
},
104+
platform_options: [],
105+
platform_value: '',
106+
template_options: [],
107+
template_value: ''
66108
}
67109
},
110+
created () { this.getPlatformList() },
68111
methods: {
69112
textFSMParser () {
113+
// 这里上线的时候需要改一下,防止跨域问题
70114
axios.post('/parser', {
71115
raw_text: this.raw_text,
72116
template_text: this.template_text
@@ -78,6 +122,43 @@ export default {
78122
this.result = this.jsonFormat(JSON.stringify(error))
79123
})
80124
},
125+
getPlatformList () {
126+
axios.get('/parser/getPlatformList')
127+
.then(response => {
128+
this.platform_options = response.data.data.platform_list
129+
})
130+
.catch((error) => {
131+
console.log('Loding platform failed: ', error)
132+
})
133+
},
134+
getTemplateList () {
135+
const platform = this.platform_value
136+
axios.get('/parser/getTemplateList', {
137+
params: {
138+
platform: platform
139+
}
140+
})
141+
.then(response => {
142+
this.template_options = response.data.data.template_list
143+
})
144+
.catch((error) => {
145+
console.log('Loding template failed: ', error)
146+
})
147+
},
148+
loadTemplate () {
149+
const template = this.template_value
150+
axios.get('/parser/loadTemplate', {
151+
params: {
152+
template: template
153+
}
154+
})
155+
.then(response => {
156+
this.template_text = response.data.data.content
157+
})
158+
.catch((error) => {
159+
console.log('Loding template failed: ', error)
160+
})
161+
},
81162
jsonFormat (jsonStr) {
82163
const beautifyJS = require('js-beautify').js_beautify
83164
const formattedJSON = beautifyJS(jsonStr, { indent_size: 2, brace_style: 'expand' })
@@ -88,6 +169,9 @@ export default {
88169
this.cmOptions.theme = 'darcula'
89170
} else { this.cmOptions.theme = 'idea' }
90171
}
172+
},
173+
components: {
174+
// 'filter-select': FilterableSelect
91175
}
92176
}
93177
</script>
@@ -102,6 +186,15 @@ export default {
102186
.darkMode a {
103187
color: #999;
104188
}
189+
.darkMode span {
190+
color: #999;
191+
}
192+
.darkMode .el-select .el-input__inner {
193+
background-color: #1e1e1e;
194+
}
195+
.darkMode .el-button {
196+
background-color: #1e1e1e !important;
197+
}
105198
a {
106199
color: #1e1e1e;
107200
margin-right: 5px
@@ -140,4 +233,7 @@ export default {
140233
.CodeMirror-scroll {
141234
height: 100%;
142235
}
236+
.el-select{
237+
margin-right: 15px;
238+
}
143239
</style>

0 commit comments

Comments
 (0)